diff --git a/acceptance/tests/ticket_6907_use_provider_in_same_run_it_becomes_suitable.rb b/acceptance/tests/ticket_6907_use_provider_in_same_run_it_becomes_suitable.rb new file mode 100644 index 000000000..a9b0b8856 --- /dev/null +++ b/acceptance/tests/ticket_6907_use_provider_in_same_run_it_becomes_suitable.rb @@ -0,0 +1,40 @@ +test_name "providers should be useable in the same run they become suitable" + +dir = "/tmp/#{$$}-6907" + +on agents, "mkdir -p #{dir}/lib/puppet/{type,provider/test6907}" +on agents, "cat > #{dir}/lib/puppet/type/test6907.rb", :stdin => < true) + + newproperty(:file) +end +TYPE + +on agents, "cat > #{dir}/lib/puppet/provider/test6907/only.rb", :stdin => < "#{dir}/must_exist" + require 'fileutils' + + def file + 'not correct' + end + + def file=(value) + FileUtils.touch(value) + end +end +PROVIDER + +on agents, puppet_apply("--libdir #{dir}/lib --trace"), :stdin => < "#{dir}/test_file", + } + + file { "#{dir}/must_exist": + ensure => file, + mode => 0755, + } +MANIFEST + +on agents, "ls #{dir}/test_file" diff --git a/ext/upload_facts.rb b/ext/upload_facts.rb new file mode 100755 index 000000000..3a9903378 --- /dev/null +++ b/ext/upload_facts.rb @@ -0,0 +1,120 @@ +#!/usr/bin/env ruby + +require 'net/https' +require 'openssl' +require 'openssl/x509' +require 'optparse' +require 'pathname' +require 'yaml' + +require 'puppet' +require 'puppet/network/http_pool' + +class Puppet::Application::UploadFacts < Puppet::Application + should_parse_config + run_mode :master + + option('--debug', '-d') + option('--verbose', '-v') + + option('--logdest DEST', '-l DEST') do |arg| + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + end + + option('--minutes MINUTES', '-m MINUTES') do |minutes| + options[:time_limit] = 60 * minutes.to_i + end + + def help + print <] + [-l|--logdest syslog||console] + += Description + +This command will read YAML facts from the puppet master's YAML directory, and +save them to the configured facts_terminus. It is intended to be used with the +facts_terminus set to inventory_service, in order to ensure facts which have +been cached locally due to a temporary failure are still uploaded to the +inventory service. + += Usage Notes + +upload_facts is intended to be run from cron, with the facts_terminus set to +inventory_service. The +--minutes+ argument should be set to the length of time +between upload_facts runs. This will ensure that only new YAML files are +uploaded. + += Options + +Note that any configuration parameter that's valid in the configuration file +is also a valid long argument. For example, 'server' is a valid configuration +parameter, so you can specify '--server ' as an argument. + +See the configuration file documentation at +http://docs.puppetlabs.com/references/stable/configuration.html for +the full list of acceptable parameters. A commented list of all +configuration options can also be generated by running puppet agent with +'--genconfig'. + +minutes:: + Limit the upload only to YAML files which have been added within the last n + minutes. +HELP + + exit + end + + def setup + # Handle the logging settings. + if options[:debug] or options[:verbose] + if options[:debug] + Puppet::Util::Log.level = :debug + else + Puppet::Util::Log.level = :info + end + + Puppet::Util::Log.newdestination(:console) unless options[:setdest] + end + + exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? + end + + def main + dir = Pathname.new(Puppet[:yamldir]) + 'facts' + + cutoff = options[:time_limit] ? Time.now - options[:time_limit] : Time.at(0) + + files = dir.children.select do |file| + file.extname == '.yaml' && file.mtime > cutoff + end + + failed = false + + terminus = Puppet::Node::Facts.indirection.terminus + + files.each do |file| + facts = YAML.load_file(file) + + request = Puppet::Indirector::Request.new(:facts, :save, facts) + + # The terminus warns for us if we fail. + if terminus.save(request) + Puppet.info "Uploaded facts for #{facts.name} to inventory service" + else + failed = true + end + end + + exit !failed + end +end + +Puppet::Application::UploadFacts.new.run diff --git a/install.rb b/install.rb index 8ff9a5131..d015c4d3f 100755 --- a/install.rb +++ b/install.rb @@ -1,462 +1,457 @@ #! /usr/bin/env ruby #-- # Copyright 2004 Austin Ziegler # Install utility. Based on the original installation script for rdoc by the # Pragmatic Programmers. # # This program is free software. It may be redistributed and/or modified under # the terms of the GPL version 2 (or later) or the Ruby licence. # # Usage # ----- # In most cases, if you have a typical project layout, you will need to do # absolutely nothing to make this work for you. This layout is: # # bin/ # executable files -- "commands" # lib/ # the source of the library # tests/ # unit tests # # The default behaviour: # 1) Run all unit test files (ending in .rb) found in all directories under # tests/. # 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), # all .rb files in lib/, ./README, ./ChangeLog, and ./Install. # 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), # and all .rb files in lib/. This is disabled by default on Microsoft Windows. # 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a # if a corresponding batch file (.bat or .cmd) exists in the bin directory, # it will be copied over as well. Otherwise, a batch file (always .bat) will # be created to run the specified command. # 5) Install all library files ending in .rb from lib/ into Ruby's # site_lib/version directory. # #++ require 'rbconfig' require 'find' require 'fileutils' begin require 'ftools' # apparently on some system ftools doesn't get loaded $haveftools = true rescue LoadError puts "ftools not found. Using FileUtils instead.." $haveftools = false end require 'optparse' require 'ostruct' begin require 'rdoc/rdoc' $haverdoc = true rescue LoadError puts "Missing rdoc; skipping documentation" $haverdoc = false end PREREQS = %w{openssl facter xmlrpc/client xmlrpc/server cgi} MIN_FACTER_VERSION = 1.5 InstallOptions = OpenStruct.new def glob(list) g = list.map { |i| Dir.glob(i) } g.flatten! g.compact! g.reject! { |e| e =~ /\.svn/ } g end # Set these values to what you want installed. configs = glob(%w{conf/auth.conf}) sbins = glob(%w{sbin/*}) bins = glob(%w{bin/*}) rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w{bin/*.rb sbin/* lib/**/*.rb}).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man[0-9]/*}) libs = glob(%w{lib/**/*.rb lib/**/*.erb lib/**/*.py lib/puppet/util/command_line/*}) tests = glob(%w{test/**/*.rb}) def do_configs(configs, target, strip = 'conf/') Dir.mkdir(target) unless File.directory? target configs.each do |cf| ocf = File.join(InstallOptions.config_dir, cf.gsub(/#{strip}/, '')) if $haveftools File.install(cf, ocf, 0644, true) else FileUtils.install(cf, ocf, {:mode => 0644, :verbose => true}) end end end def do_bins(bins, target, strip = 's?bin/') Dir.mkdir(target) unless File.directory? target bins.each do |bf| obf = bf.gsub(/#{strip}/, '') install_binfile(bf, obf, target) end end def do_libs(libs, strip = 'lib/') libs.each do |lf| olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) op = File.dirname(olf) if $haveftools File.makedirs(op, true) File.chmod(0755, op) File.install(lf, olf, 0644, true) else FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) end end end def do_man(man, strip = 'man/') man.each do |mf| omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) om = File.dirname(omf) if $haveftools File.makedirs(om, true) File.chmod(0755, om) File.install(mf, omf, 0644, true) else FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, om) FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) end gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} end end # Verify that all of the prereqs are installed def check_prereqs PREREQS.each { |pre| begin require pre if pre == "facter" # to_f isn't quite exact for strings like "1.5.1" but is good # enough for this purpose. facter_version = Facter.version.to_f if facter_version < MIN_FACTER_VERSION puts "Facter version: #{facter_version}; minimum required: #{MIN_FACTER_VERSION}; cannot install" exit -1 end end rescue LoadError puts "Could not load #{pre}; cannot install" exit -1 end } end ## # Prepare the file installation. # def prepare_installation $operatingsystem = Facter["operatingsystem"].value InstallOptions.configs = true # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true InstallOptions.ri = $operatingsystem != "windows" else InstallOptions.rdoc = false InstallOptions.ri = false end InstallOptions.tests = true ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| InstallOptions.rdoc = onrdoc end opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| InstallOptions.ri = onri end opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| InstallOptions.tests = ontest end opts.on('--[no-]configs', 'Prevents the installation of config files', 'Default off.') do |ontest| InstallOptions.configs = ontest end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end opts.on('--configdir[=OPTIONAL]', 'Installation directory for config files', 'Default /etc/puppet') do |configdir| InstallOptions.configdir = configdir end opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| InstallOptions.sbindir = sbindir end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir end opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false InstallOptions.tests = false InstallOptions.configs = true end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true InstallOptions.ri = true InstallOptions.tests = true InstallOptions.configs = true end opts.separator("") opts.on_tail('--help', "Shows this help text.") do $stderr.puts opts exit end opts.parse! end tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") libdir = File.join(Config::CONFIG["libdir"], "ruby", version) # Mac OS X 10.5 and higher declare bindir and sbindir as # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ Config::CONFIG['bindir'] = "/usr/bin" Config::CONFIG['sbindir'] = "/usr/sbin" end if not InstallOptions.configdir.nil? configdir = InstallOptions.configdir elsif $operatingsystem == "windows" begin require 'win32/dir' rescue LoadError => e puts "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir & win32-service gems: #{e}" exit -1 end configdir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") else configdir = "/etc/puppet" end if not InstallOptions.bindir.nil? bindir = InstallOptions.bindir else bindir = Config::CONFIG['bindir'] end if not InstallOptions.sbindir.nil? sbindir = InstallOptions.sbindir else sbindir = Config::CONFIG['sbindir'] end if not InstallOptions.sitelibdir.nil? sitelibdir = InstallOptions.sitelibdir else sitelibdir = Config::CONFIG["sitelibdir"] if sitelibdir.nil? sitelibdir = $LOAD_PATH.find { |x| x =~ /site_ruby/ } if sitelibdir.nil? sitelibdir = File.join(libdir, "site_ruby") elsif sitelibdir !~ Regexp.quote(version) sitelibdir = File.join(sitelibdir, version) end end end if not InstallOptions.mandir.nil? mandir = InstallOptions.mandir else mandir = Config::CONFIG['mandir'] end # This is the new way forward if not InstallOptions.destdir.nil? destdir = InstallOptions.destdir # To be deprecated once people move over to using --destdir option elsif not ENV['DESTDIR'].nil? destdir = ENV['DESTDIR'] warn "DESTDIR is deprecated. Use --destdir instead." else destdir = '' end configdir = join(destdir, configdir) bindir = join(destdir, bindir) sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(configdir) if InstallOptions.configs FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) tmpdirs << bindir InstallOptions.tmp_dirs = tmpdirs.compact InstallOptions.site_dir = sitelibdir InstallOptions.config_dir = configdir InstallOptions.bin_dir = bindir InstallOptions.sbin_dir = sbindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir end ## # Join two paths. On Windows, dir must be converted to a relative path, # by stripping the drive letter, but only if the basedir is not empty. # def join(basedir, dir) return "#{basedir}#{dir[2..-1]}" if $operatingsystem == "windows" and basedir.length > 0 and dir.length > 2 "#{basedir}#{dir}" end ## # Build the rdoc documentation. Also, try to build the RI documentation. # def build_rdoc(files) return unless $haverdoc begin r = RDoc::RDoc.new r.document(["--main", "README", "--title", "Puppet -- Site Configuration Management", "--line-numbers"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" end end def build_ri(files) return unless $haverdoc begin ri = RDoc::RDoc.new #ri.document(["--ri-site", "--merge"] + files) ri.document(["--ri-site"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build Ri documentation\n#{e.message}" $stderr.puts "Continuing with install..." end end def run_tests(test_list) require 'test/unit/ui/console/testrunner' $LOAD_PATH.unshift "lib" test_list.each do |test| next if File.directory?(test) require test end tests = [] ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } tests.delete_if { |o| o == Test::Unit::TestCase } tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } $LOAD_PATH.shift rescue LoadError puts "Missing testrunner library; skipping tests" end ## # Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) tmp_dir = nil InstallOptions.tmp_dirs.each do |t| if File.directory?(t) and File.writable?(t) tmp_dir = t break end end fail "Cannot find a temporary directory" unless tmp_dir tmp_file = File.join(tmp_dir, '_tmp') ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) File.open(from) do |ip| File.open(tmp_file, "w") do |op| ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" contents = ip.readlines contents.shift if contents[0] =~ /^#!/ op.write contents.join end end if $operatingsystem == "windows" installed_wrapper = false if File.exists?("#{from}.bat") FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) installed_wrapper = true end if File.exists?("#{from}.cmd") FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) installed_wrapper = true end if not installed_wrapper tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') cwn = File.join(Config::CONFIG['bindir'], op_file) - regex_safe_ruby = Regexp.escape(ruby.gsub(%r{/}) { "\\" }) - regex_safe_cwn = Regexp.escape(cwn.gsub(%r{/}) { "\\" }) - - cwv = CMD_WRAPPER.gsub('', regex_safe_ruby).gsub('', regex_safe_cwn) - File.open(tmp_file2, "wb") { |cw| cw.puts cwv } + cwv = <<-EOS +@echo off +if "%OS%"=="Windows_NT" goto WinNT +#{ruby} -x "#{cwn}" %1 %2 %3 %4 %5 %6 %7 %8 %9 +goto done +:WinNT +#{ruby} -x "#{cwn}" %* +goto done +:done +EOS + File.open(tmp_file2, "w") { |cw| cw.puts cwv } FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) File.unlink(tmp_file2) installed_wrapper = true end end FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) File.unlink(tmp_file) end -CMD_WRAPPER = <<-EOS -@echo off -if "%OS%"=="Windows_NT" goto WinNT - -x "" %1 %2 %3 %4 %5 %6 %7 %8 %9 -goto done -:WinNT - -x "" %* -goto done -:done -EOS - check_prereqs prepare_installation #run_tests(tests) if InstallOptions.tests #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri do_configs(configs, InstallOptions.config_dir) if InstallOptions.configs do_bins(sbins, InstallOptions.sbin_dir) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) do_man(man) unless $operatingsystem == "windows" diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index b8645c3ce..a3854a8fd 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -1,493 +1,490 @@ require 'puppet/application' class Puppet::Application::Agent < Puppet::Application should_parse_config run_mode :agent attr_accessor :args, :agent, :daemon, :host def preinit # Do an initial trap, so that cancels don't get a stack trace. Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end { :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, :enable => false, :disable => false, :client => true, :fqdn => nil, :serve => [], :digest => :MD5, :fingerprint => false, }.each do |opt,val| options[opt] = val end @args = {} require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end option("--centrallogging") option("--disable") option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") option("--test","-t") option("--verbose","-v") option("--fingerprint") option("--digest DIGEST") option("--serve HANDLER", "-s") do |arg| if Puppet::Network::Handler.handler(arg) options[:serve] << arg.to_sym else raise "Could not find handler for #{arg}" end end option("--no-client") do |arg| options[:client] = false end option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail puts detail.backtrace if Puppet[:debug] $stderr.puts detail.to_s end end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i end option("--port PORT","-p") do |arg| @args[:Port] = arg end def help <<-HELP puppet-agent(8) -- The puppet agent daemon ======== SYNOPSIS -------- Retrieves the client configuration from the puppet master and applies it to the local host. This service may be run as a daemon, run periodically using cron (or something similar), or run interactively for testing purposes. USAGE ----- puppet agent [--certname ] [-D|--daemonize|--no-daemonize] [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable] [--enable] [--fingerprint] [-h|--help] [-l|--logdest syslog||console] [--no-client] [--noop] [-o|--onetime] [--serve ] [-t|--test] [-v|--verbose] [-V|--version] [-w|--waitforcert ] DESCRIPTION ----------- This is the main puppet client. Its job is to retrieve the local machine's configuration from a remote server and apply it. In order to successfully communicate with the remote server, the client must have a certificate signed by a certificate authority that the server trusts; the recommended method for this, at the moment, is to run a certificate authority as part of the puppet server (which is the default). The client will connect and request a signed certificate, and will continue connecting until it receives one. Once the client has a signed certificate, it will retrieve its configuration and apply it. USAGE NOTES ----------- 'puppet agent' does its best to find a compromise between interactive use and daemon use. Run with no arguments and no configuration, it will go into the background, attempt to get a signed certificate, and retrieve and apply its configuration every 30 minutes. Some flags are meant specifically for interactive use -- in particular, 'test', 'tags' or 'fingerprint' are useful. 'test' enables verbose logging, causes the daemon to stay in the foreground, exits if the server's configuration is invalid (this happens if, for instance, you've left a syntax error on the server), and exits after running the configuration once (rather than hanging around as a long-running process). 'tags' allows you to specify what portions of a configuration you want to apply. Puppet elements are tagged with all of the class or definition names that contain them, and you can use the 'tags' flag to specify one of these names, causing only configuration elements contained within that class or definition to be applied. This is very useful when you are testing new configurations -- for instance, if you are just starting to manage 'ntpd', you would put all of the new elements into an 'ntpd' class, and call puppet with '--tags ntpd', which would only apply that small portion of the configuration during your testing, rather than applying the whole thing. 'fingerprint' is a one-time flag. In this mode 'puppet agent' will run once and display on the console (and in the log) the current certificate (or certificate request) fingerprint. Providing the '--digest' option allows to use a different digest algorithm to generate the fingerprint. The main use is to verify that before signing a certificate request on the master, the certificate request the master received is the same as the one the client sent (to prevent against man-in-the-middle attacks when signing certificates). OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet agent with '--genconfig'. * --certname: Set the certname (unique ID) of the client. The master reads this unique identifying string, which is usually set to the node's fully-qualified domain name, to determine which configurations the node will receive. Use this option to debug setup problems or implement unusual node identification schemes. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --digest: Change the certificate fingerprinting digest algorithm. The default is MD5. Valid values depends on the version of OpenSSL installed, but should always at least contain MD5, MD2, SHA1 and SHA256. * --disable: Disable working on the local system. This puts a lock file in place, causing 'puppet agent' not to work on the system until the lock file is removed. This is useful if you are testing a configuration and do not want the central configuration to override the local state until everything is tested and committed. 'puppet agent' uses the same lock file while it is running, so no more than one 'puppet agent' process is working at a time. 'puppet agent' exits after executing this. * --enable: Enable working on the local system. This removes any lock file, causing 'puppet agent' to start managing the local system again (although it will continue to use its normal scheduling, so it might not start for another half hour). 'puppet agent' exits after executing this. * --fingerprint: Display the current certificate or certificate signing request fingerprint and then exit. Use the '--digest' option to change the digest algorithm used. * --help: Print this help message * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --no-client: Do not create a config client. This will cause the daemon to run without ever checking for its configuration automatically, and only makes sense when puppet agent is being run with listen = true in puppet.conf or was started with the `--listen` option. * --noop: Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. * --onetime: Run the configuration once. Runs a single (normally daemonized) Puppet run. Useful for interactively running puppet agent when used in conjunction with the --no-daemonize option. * --serve: Start another type of server. By default, 'puppet agent' will start a service handler that allows authenticated and authorized remote nodes to trigger the configuration to be pulled down and applied. You can specify any handler here that does not require configuration, e.g., filebucket, ca, or resource. The handlers are in 'lib/puppet/network/handler', and the names must match exactly, both in the call to 'serve' and in 'namespaceauth.conf'. * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', 'detailed-exit-codes', 'no-splay', and 'show_diff'. * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. * --waitforcert: This option only matters for daemons that do not yet have certificates and it is enabled by default, with a value of 120 (seconds). This causes 'puppet agent' to connect to the server every 2 minutes and ask it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0. EXAMPLE ------- $ puppet agent --server puppet.domain.com DIAGNOSTICS ----------- Puppet agent accepts the following signals: * SIGHUP: Restart the puppet agent daemon. * SIGINT and SIGTERM: Shut down the puppet agent daemon. * SIGUSR1: Immediately retrieve and apply configurations from the puppet master. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command return fingerprint if options[:fingerprint] return onetime if Puppet[:onetime] main end def fingerprint unless cert = host.certificate || host.certificate_request $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" exit(1) return end unless fingerprint = cert.fingerprint(options[:digest]) raise ArgumentError, "Could not get fingerprint for digest '#{options[:digest]}'" end puts fingerprint end def onetime unless options[:client] $stderr.puts "onetime is specified but there is no client" exit(43) return end @daemon.set_signal_traps begin report = @agent.run rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail.to_s end if not report exit(1) elsif options[:detailed_exitcodes] then exit(report.exit_status) else exit(0) end end def main Puppet.notice "Starting Puppet client version #{Puppet.version}" @daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--ignorecache") Puppet.settings.handlearg("--no-usecacheonfailure") Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") Puppet.settings.handlearg("--no-daemonize") options[:verbose] = true Puppet[:onetime] = true options[:detailed_exitcodes] = true end # Handle the logging settings. def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] end def enable_disable_client(agent) if options[:enable] agent.enable elsif options[:disable] agent.disable end exit(0) end def setup_listen unless FileTest.exists?(Puppet[:rest_authconfig]) Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" exit(14) end handlers = nil if options[:serve].empty? handlers = [:Runner] else handlers = options[:serve] end require 'puppet/network/server' # No REST handlers yet. server = Puppet::Network::Server.new(:xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) @daemon.server = server end def setup_host @host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) cert = @host.wait_for_cert(waitforcert) unless options[:fingerprint] end def setup setup_test if options[:test] setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? - # If noop is set, then also enable diffs - Puppet[:show_diff] = true if Puppet[:noop] - args[:Server] = Puppet[:server] if options[:fqdn] args[:FQDN] = options[:fqdn] Puppet[:certname] = options[:fqdn] end if options[:centrallogs] logdest = args[:Server] logdest += ":" + args[:Port] if args.include?(:Port) Puppet::Util::Log.newdestination(logdest) end Puppet.settings.use :main, :agent, :ssl # Always ignoreimport for agent. It really shouldn't even try to import, # but this is just a temporary band-aid. Puppet[:ignoreimport] = true # We need to specify a ca location for all of the SSL-related i # indirected classes to work; in fingerprint mode we just need # access to the local files and we don't need a ca. Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote Puppet::Transaction::Report.indirection.terminus_class = :rest # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml # Override the default; puppetd needs this, usually. # You can still override this on the command-line with, e.g., :compiler. Puppet[:catalog_terminus] = :rest # Override the default. Puppet[:facts_terminus] = :facter Puppet::Resource::Catalog.indirection.cache_class = :yaml # We need tomake the client either way, we just don't start it # if --no-client is set. require 'puppet/agent' require 'puppet/configurer' @agent = Puppet::Agent.new(Puppet::Configurer) enable_disable_client(@agent) if options[:enable] or options[:disable] @daemon.agent = agent if options[:client] # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. @daemon.daemonize if Puppet[:daemonize] setup_host @objects = [] # This has to go after the certs are dealt with. if Puppet[:listen] unless Puppet[:onetime] setup_listen else Puppet.notice "Ignoring --listen on onetime run" end end end end diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index 200309b7d..5df00d76b 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -1,255 +1,252 @@ require 'puppet/application' class Puppet::Application::Apply < Puppet::Application should_parse_config option("--debug","-d") option("--execute EXECUTE","-e") do |arg| options[:code] = arg end option("--loadclasses","-L") option("--verbose","-v") option("--use-nodes") option("--detailed-exitcodes") option("--apply catalog", "-a catalog") do |arg| options[:catalog] = arg end option("--logdest LOGDEST", "-l") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:logset] = true rescue => detail $stderr.puts detail.to_s end end option("--parseonly") do puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def help <<-HELP puppet-apply(8) -- Apply Puppet manifests locally ======== SYNOPSIS -------- Applies a standalone Puppet manifest to the local system. USAGE ----- puppet apply [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-e|--execute] [--detailed-exitcodes] [-l|--logdest ] [--apply ] DESCRIPTION ----------- This is the standalone puppet execution tool; use it to apply individual manifests. When provided with a modulepath, via command line or config file, puppet apply can effectively mimic the catalog that would be served by puppet master with access to the same modules, although there are some subtle differences. When combined with scheduling and an automated system for pushing manifests, this can be used to implement a serverless Puppet site. Most users should use 'puppet agent' and 'puppet master' for site-wide manifests. OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'modulepath' is a valid configuration parameter, so you can specify '--tags ,' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with '--genconfig'. * --debug: Enable full debugging. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --help: Print this help message * --loadclasses: Load any stored classes. 'puppet agent' caches configured classes (usually at /etc/puppet/classes.txt), and setting this option causes all of those classes to be set in your puppet manifest. * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to the console. * --execute: Execute a specific piece of Puppet code * --verbose: Print extra information. * --apply: Apply a JSON catalog (such as one generated with 'puppet master --compile'). You can either specify a JSON file or pipe in JSON from standard input. EXAMPLE ------- $ puppet apply -l /tmp/manifest.log manifest.pp $ puppet apply --modulepath=/root/dev/modules -e "include ntpd::server" AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command if options[:catalog] apply else main end end def apply if options[:catalog] == "-" text = $stdin.read else text = File.read(options[:catalog]) end begin catalog = Puppet::Resource::Catalog.convert_from(Puppet::Resource::Catalog.default_format,text) catalog = Puppet::Resource::Catalog.pson_create(catalog) unless catalog.is_a?(Puppet::Resource::Catalog) rescue => detail raise Puppet::Error, "Could not deserialize catalog from pson: #{detail}" end catalog = catalog.to_ral require 'puppet/configurer' configurer = Puppet::Configurer.new configurer.run :catalog => catalog end def main # Set our code or file to use. if options[:code] or command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift raise "Could not find file #{manifest}" unless File.exist?(manifest) Puppet.warning("Only one file can be applied per run. Skipping #{command_line.args.join(', ')}") if command_line.args.size > 0 Puppet[:manifest] = manifest end # Collect our facts. unless facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value]) raise "Could not find facts for #{Puppet[:node_name_value]}" end unless Puppet[:node_name_fact].empty? Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end # Find our Node unless node = Puppet::Node.indirection.find(Puppet[:node_name_value]) raise "Could not find node #{Puppet[:node_name_value]}" end # Merge in the facts. node.merge(facts.values) # Allow users to load the classes that puppet agent creates. if options[:loadclasses] file = Puppet[:classfile] if FileTest.exists?(file) unless FileTest.readable?(file) $stderr.puts "#{file} is not readable" exit(63) end node.classes = File.read(file).split(/[\s\n]+/) end end begin # Compile our catalog starttime = Time.now catalog = Puppet::Resource::Catalog.indirection.find(node.name, :use_node => node) # Translate it to a RAL catalog catalog = catalog.to_ral catalog.finalize catalog.retrieval_duration = Time.now - starttime require 'puppet/configurer' configurer = Puppet::Configurer.new report = configurer.run(:skip_plugin_download => true, :catalog => catalog) if not report exit(1) elsif options[:detailed_exitcodes] then exit(report.exit_status) else exit(0) end rescue => detail puts detail.backtrace if Puppet[:trace] $stderr.puts detail.message exit(1) end end def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? - # If noop is set, then also enable diffs - Puppet[:show_diff] = true if Puppet[:noop] - Puppet::Util::Log.newdestination(:console) unless options[:logset] client = nil server = nil Signal.trap(:INT) do $stderr.puts "Exiting" exit(1) end # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] Puppet::Util::Log.level = :info end end end diff --git a/lib/puppet/application/queue.rb b/lib/puppet/application/queue.rb index b1a354965..2a9d3e7c3 100644 --- a/lib/puppet/application/queue.rb +++ b/lib/puppet/application/queue.rb @@ -1,164 +1,184 @@ require 'puppet/application' require 'puppet/util' class Puppet::Application::Queue < Puppet::Application should_parse_config attr_accessor :daemon def preinit require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup - Puppet::Util::Log.newdestination(:console) # Do an initial trap, so that cancels don't get a stack trace. # This exits with exit code 1 Signal.trap(:INT) do $stderr.puts "Caught SIGINT; shutting down" exit(1) end # This is a normal shutdown, so code 0 Signal.trap(:TERM) do $stderr.puts "Caught SIGTERM; shutting down" exit(0) end { :verbose => false, :debug => false }.each do |opt,val| options[opt] = val end end option("--debug","-d") option("--verbose","-v") def help <<-HELP puppet-queue(8) -- Queuing daemon for asynchronous storeconfigs ======== SYNOPSIS -------- Retrieves serialized storeconfigs records from a queue and processes them in order. USAGE ----- puppet queue [-d|--debug] [-v|--verbose] DESCRIPTION ----------- This application runs as a daemon and processes storeconfigs data, retrieving the data from a stomp server message queue and writing it to a database. For more information, including instructions for properly setting up your puppet master and message queue, see the documentation on setting up asynchronous storeconfigs at: http://projects.puppetlabs.com/projects/1/wiki/Using_Stored_Configuration OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet queue with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. EXAMPLE ------- $ puppet queue AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end + option("--logdest DEST", "-l DEST") do |arg| + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + puts detail.backtrace if Puppet[:debug] + $stderr.puts detail.to_s + end + end + + option("--logdest DEST", "-l DEST") do |arg| + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + puts detail.backtrace if Puppet[:debug] + $stderr.puts detail.to_s + end + end + def main require 'puppet/indirector/catalog/queue' # provides Puppet::Indirector::Queue.subscribe Puppet.notice "Starting puppetqd #{Puppet.version}" Puppet::Resource::Catalog::Queue.subscribe do |catalog| # Once you have a Puppet::Resource::Catalog instance, passing it to save should suffice # to put it through to the database via its active_record indirector (which is determined # by the terminus_class = :active_record setting above) Puppet::Util.benchmark(:notice, "Processing queued catalog for #{catalog.name}") do begin Puppet::Resource::Catalog.indirection.save(catalog) rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not save queued catalog for #{catalog.name}: #{detail}" end end end Thread.list.each { |thread| thread.join } end # Handle the logging settings. def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end + Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] end def setup unless Puppet.features.stomp? raise ArgumentError, "Could not load the 'stomp' library, which must be present for queueing to work. You must install the required library." end setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? require 'puppet/resource/catalog' Puppet::Resource::Catalog.indirection.terminus_class = :store_configs daemon.daemonize if Puppet[:daemonize] # We want to make sure that we don't have a cache # class set up, because if storeconfigs is enabled, # we'll get a loop of continually caching the catalog # for storage again. Puppet::Resource::Catalog.indirection.cache_class = nil end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 09d735672..f1b1ac74b 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,941 +1,942 @@ # The majority of the system configuration parameters are set in this file. module Puppet setdefaults(:main, :confdir => [Puppet.run_mode.conf_dir, "The main Puppet configuration directory. The default for this parameter is calculated based on the user. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, it defaults to being in the user's home directory."], :vardir => [Puppet.run_mode.var_dir, "Where Puppet stores dynamic and growing data. The default for this parameter is calculated specially, like `confdir`_."], :name => [Puppet.application_name.to_s, "The name of the application, if we are running as one. The default is essentially $0 without the path or `.rb`."], :run_mode => [Puppet.run_mode.name.to_s, "The effective 'run mode' of the application: master, agent, or user."] ) setdefaults(:main, :logdir => Puppet.run_mode.logopts) setdefaults(:main, :trace => [false, "Whether to print stack traces on some errors"], :autoflush => { :default => false, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => ["daemon", "What syslog facility to use when logging to syslog. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up."], :statedir => { :default => "$vardir/state", :mode => 01755, :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => Puppet.run_mode.run_dir, :mode => 01777, :desc => "Where Puppet PID files are kept." }, :genconfig => [false, "Whether to just print a configuration to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :genmanifest => [false, "Whether to just print a manifest to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :configprint => ["", "Print the value of a specific configuration parameter. If a parameter is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'. This feature is only available in Puppet versions higher than 0.18.4."], :color => { :default => (Puppet.features.microsoft_windows? ? "false" : "ansi"), :type => :setting, :desc => "Whether to use colors when logging to the console. Valid values are `ansi` (equivalent to `true`), `html` (mostly used during testing with TextMate), and `false`, which produces no color.", }, :mkusers => [false, "Whether to create the necessary user and group that puppet agent will run as."], :manage_internal_file_permissions => [true, "Whether Puppet should manage the owner, group, and mode of files it uses internally" ], :onetime => {:default => false, :desc => "Run the configuration once, rather than as a long-running daemon. This is useful for interactively running puppetd.", :short => 'o' }, :path => {:default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process.", :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) %w{/usr/sbin /sbin}.each do |path| ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) end value end }, :libdir => {:default => "$vardir/lib", :desc => "An extra search path for Puppet. This is only useful for those files that Puppet will load on demand, and is only guaranteed to work for those cases. In fact, the autoload mechanism is responsible for making sure this directory is in Ruby's search path", :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => [false, "A parameter that can be used in commit hooks, since it enables you to parse-check a single file rather than requiring that all files exist."], :authconfig => [ "$confdir/namespaceauth.conf", "The configuration file that defines the rights to the different namespaces and methods. This can be used as a coarse-grained authorization system for both `puppet agent` and `puppet master`." ], :environment => {:default => "production", :desc => "The environment Puppet is running in. For clients (e.g., `puppet agent`) this determines the environment itself, which is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes we know nothing about." }, :diff_args => ["-u", "Which arguments to pass to the diff command when printing differences between files."], :diff => ["diff", "Which diff command to use when printing differences between files."], :show_diff => [false, "Whether to print a contextual diff when files are being replaced. The diff is printed on stdout, so this option is meaningless unless you are running Puppet interactively. This feature currently requires the `diff/lcs` Ruby library."], :daemonize => { :default => (Puppet.features.microsoft_windows? ? false : true), :desc => "Send the process into the background. This is the default.", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? raise "Cannot daemonize on Windows" end end }, :maximum_uid => [4294967290, "The maximum allowed UID. Some platforms use negative UIDs but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens."], :route_file => ["$confdir/routes.yaml", "The YAML file containing indirector route configuration."], :node_terminus => ["plain", "Where to find information about nodes."], :catalog_terminus => ["compiler", "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."], :facts_terminus => { :default => Puppet.application_name.to_s == "master" ? 'yaml' : 'facter', :desc => "The node facts terminus.", :hook => proc do |value| require 'puppet/node/facts' - if value.to_s == "rest" + # Cache to YAML if we're uploading facts away + if %w[rest inventory_service].include? value.to_s Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => [ "$facts_terminus", "Should usually be the same as the facts terminus" ], :httplog => { :default => "$logdir/http.log", :owner => "root", :mode => 0640, :desc => "Where the puppet agent web server logs." }, :http_proxy_host => ["none", "The HTTP proxy host to use for outgoing connections. Note: You may need to use a FQDN for the server hostname when using a proxy."], :http_proxy_port => [3128, "The HTTP proxy port to use for outgoing connections"], :filetimeout => [ 15, "The minimum time to wait (in seconds) between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk." ], :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], :queue_source => ["stomp://localhost:61613/", "Which type of queue to use for asynchronous processing. If your stomp server requires authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1"], :async_storeconfigs => {:default => false, :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppetqd` be running and that 'PSON' support for ruby be installed.", :hook => proc do |value| if value # This reconfigures the terminii for Node, Facts, and Catalog Puppet.settings[:storeconfigs] = true # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => {:default => false, :desc => "Boolean; wether storeconfigs store in the database only the facts and exported resources. If true, then storeconfigs performance will be higher and still allow exported/collected resources, but other usage external to Puppet might not work", :hook => proc do |value| Puppet.settings[:storeconfigs] = true if value end }, :config_version => ["", "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server."], :zlib => [true, "Boolean; whether to use the zlib library", ], :prerun_command => ["", "A command to run before every agent run. If this command returns a non-zero return code, the entire Puppet run will fail."], :postrun_command => ["", "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run."], :freeze_main => [false, "Freezes the 'main' class, disallowing any code to be added to it. This essentially means that you can't have any code outside of a node, class, or definition other than in the site manifest."] ) hostname = Facter["hostname"].value domain = Facter["domain"].value if domain and domain != "" fqdn = [hostname, domain].join(".") else fqdn = hostname end Puppet.setdefaults( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for # manipulating naming. :certname => {:default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults to the fully qualified domain name.", :call_on_define => true, # Call our hook with the default value, so we're always downcased :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, :certdnsnames => { :default => '', :hook => proc do |value| unless value.nil? or value == '' then Puppet.warning < < { :default => '', :desc => < { :default => "$ssldir/certs", :owner => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :mode => 0771, :owner => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :owner => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :owner => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :mode => 0750, :owner => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :mode => 0750, :owner => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :mode => 0640, :owner => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :mode => 0600, :owner => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :mode => 0644, :owner => "service", :desc => "Where each client stores the CA certificate." }, :hostcrl => { :default => "$ssldir/crl.pem", :mode => 0644, :owner => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :certificate_revocation => [true, "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) to all clients. If enabled, CA chaining will almost definitely not work."] ) setdefaults( :ca, :ca_name => ["Puppet CA: $certname", "The name to use the Certificate Authority certificate."], :cadir => { :default => "$ssldir/ca", :owner => "service", :group => "service", :mode => 0770, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :owner => "service", :group => "service", :mode => 0660, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :owner => "service", :group => "service", :mode => 0660, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :owner => "service", :group => "service", :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :owner => "service", :group => "service", :mode => 0664, :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", :hook => proc do |value| if value == 'false' Puppet.warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing" end end }, :caprivatedir => { :default => "$cadir/private", :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :owner => "service", :group => "service", :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :owner => "service", :group => "service", :mode => 0660, :desc => "Where the CA stores the password for the private key" }, :serial => { :default => "$cadir/serial", :owner => "service", :group => "service", :mode => 0644, :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", :mode => 0644, :desc => "Whether to enable autosign. Valid values are true (which autosigns any key request, and is a very bad idea), false (which never autosigns any key request), and the path to a file, which uses that configuration file to determine which keys to sign."}, :allow_duplicate_certs => [false, "Whether to allow a new certificate request to overwrite an existing certificate."], :ca_days => ["", "How long a certificate should be valid. This parameter is deprecated, use ca_ttl instead"], :ca_ttl => ["5y", "The default TTL for new certificates; valid values must be an integer, optionally followed by one of the units 'y' (years of 365 days), 'd' (days), 'h' (hours), or 's' (seconds). The unit defaults to seconds. If this parameter is set, ca_days is ignored. Examples are '3600' (one hour) and '1825d', which is the same as '5y' (5 years) "], :ca_md => ["md5", "The type of hash used in certificates."], :req_bits => [2048, "The bit length of the certificates."], :keylength => [1024, "The bit length of keys."], :cert_inventory => { :default => "$cadir/inventory.txt", :mode => 0644, :owner => "service", :group => "service", :desc => "A Complete listing of all certificates" } ) # Define the config default. setdefaults( Puppet.settings[:name], :config => ["$confdir/puppet.conf", "The configuration file for #{Puppet[:name]}."], :pidfile => ["$rundir/$name.pid", "The pid file"], :bindaddress => ["", "The address a listening server should bind to. Mongrel servers default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."], :servertype => {:default => "webrick", :desc => "The type of server to use. Currently supported options are webrick and mongrel. If you use mongrel, you will need a proxy in front of the process or processes, since Mongrel cannot speak SSL.", :call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set. :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" } } ) setdefaults(:master, :user => ["puppet", "The user puppet master should run as."], :group => ["puppet", "The group puppet master should run as."], :manifestdir => ["$confdir/manifests", "Where puppet master looks for its manifests."], :manifest => ["$manifestdir/site.pp", "The entry-point manifest for puppet master."], :code => ["", "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet executable"], :masterlog => { :default => "$logdir/puppetmaster.log", :owner => "service", :group => "service", :mode => 0660, :desc => "Where puppet master logs. This is generally not used, since syslog is the default log destination." }, :masterhttplog => { :default => "$logdir/masterhttp.log", :owner => "service", :group => "service", :mode => 0660, :create => true, :desc => "Where the puppet master web server logs." }, :masterport => [8140, "Which port puppet master listens on."], :node_name => ["cert", "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)"], :bucketdir => { :default => "$vardir/bucket", :mode => 0750, :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :rest_authconfig => [ "$confdir/auth.conf", "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained authorization system for `puppet master`." ], :ca => [true, "Wether the master should function as a certificate authority."], :modulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :desc => "The search path for modules as a list of directories separated by the '#{File::PATH_SEPARATOR}' character.", :type => :setting # We don't want this to be considered a file, since it's multiple files. }, :ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated client's SSL DN. Only used with Mongrel. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], :ssl_client_verify_header => ["HTTP_X_CLIENT_VERIFY", "The header containing the status message of the client verification. Only used with Mongrel. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). :yamldir => {:default => "$vardir/yaml", :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, :server_datadir => {:default => "$vardir/server_data", :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => ["store", "The list of reports to generate. All reports are looked for in `puppet/reports/name.rb`, and multiple report names should be comma-separated (whitespace is okay)." ], :reportdir => {:default => "$vardir/reports", :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, :reporturl => ["http://localhost:3000/reports/upload", "The URL used by the http reports processor to send reports"], :fileserverconfig => ["$confdir/fileserver.conf", "Where the fileserver configuration is stored."], :strict_hostname_checking => [false, "Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs."] ) setdefaults(:metrics, :rrddir => {:default => "$vardir/rrd", :mode => 0750, :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, :rrdinterval => ["$runinterval", "How often RRD should expect data. This should match how often the hosts report back to the server."] ) setdefaults(:device, :devicedir => {:default => "$vardir/devices", :mode => "750", :desc => "The root directory of devices' $vardir"}, :deviceconfig => ["$confdir/device.conf","Path to the device config file for puppet device"] ) setdefaults(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :localconfig => { :default => "$statedir/localconfig", :owner => "root", :mode => 0660, :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, :statefile => { :default => "$statedir/state.yaml", :mode => 0660, :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, :clientyamldir => {:default => "$vardir/client_yaml", :mode => "750", :desc => "The directory in which client-side YAML data is stored."}, :client_datadir => {:default => "$vardir/client_data", :mode => "750", :desc => "The directory in which serialized data is stored on the client."}, :classfile => { :default => "$statedir/classes.txt", :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, :resourcefile => { :default => "$statedir/resources.txt", :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :puppetdlog => { :default => "$logdir/puppetd.log", :owner => "root", :mode => 0640, :desc => "The log file for puppet agent. This is generally not used." }, :server => ["puppet", "The server to which server puppet agent should connect"], :ignoreschedules => [false, "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs."], :puppetport => [8139, "Which port puppet agent listens on."], :noop => [false, "Whether puppet agent should be run in noop mode."], :runinterval => [1800, # 30 minutes "How often puppet agent applies the client configuration; in seconds. Note that a runinterval of 0 means \"run continuously\" rather than \"never run.\" If you want puppet agent to never run, you should start it with the `--no-client` option."], :listen => [false, "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to requests on the `run`, `facts`, `certificate`, and `resource` endpoints."], :ca_server => ["$server", "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale."], :ca_port => ["$masterport", "The port to use for the certificate authority."], :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings[:preferred_serialization_format] = value end } }, :preferred_serialization_format => ["pson", "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it."], :puppetdlockfile => [ "$statedir/puppetdlock", "A lock file to temporarily stop puppet agent from doing anything."], :usecacheonfailure => [true, "Whether to use the cached configuration when the remote configuration will not compile. This option is useful for testing new configurations, where you want to fix the broken configuration rather than reverting to a known-good one." ], :use_cached_catalog => [false, "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired."], :ignorecache => [false, "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts change or if the server changes." ], :downcasefacts => [false, "Whether facts should be made all lowercase when sent to the server."], :dynamicfacts => ["memorysize,memoryfree,swapsize,swapfree", "Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be comma-separated."], :splaylimit => ["$runinterval", "The maximum time to delay before runs. Defaults to being the same as the run interval."], :splay => [false, "Whether to sleep for a pseudo-random (but consistent) amount of time before a run."], :clientbucketdir => { :default => "$vardir/clientbucket", :mode => 0750, :desc => "Where FileBucket files are stored locally." }, :configtimeout => [120, "How long the client should wait for the configuration to be retrieved before considering it a failure. This can help reduce flapping if too many clients contact the server at one time." ], :reportserver => { :default => "$server", :call_on_define => false, :desc => "(Deprecated for 'report_server') The server to which to send transaction reports.", :hook => proc do |value| Puppet.settings[:report_server] = value if value end }, :report_server => ["$server", "The server to send transaction reports to." ], :report_port => ["$masterport", "The port to communicate with the report_server." ], :inventory_server => ["$server", "The server to send facts to." ], :inventory_port => ["$masterport", "The port to communicate with the inventory_server." ], :report => [true, "Whether to send reports after every transaction." ], :lastrunfile => { :default => "$statedir/last_run_summary.yaml", :mode => 0660, :desc => "Where puppet agent stores the last run report summary in yaml format." }, :lastrunreport => { :default => "$statedir/last_run_report.yaml", :mode => 0660, :desc => "Where puppet agent stores the last run report in yaml format." }, :graph => [false, "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick)."], :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."], :http_compression => [false, "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs."] ) setdefaults(:inspect, :archive_files => [false, "During an inspect run, whether to archive files whose contents are audited to a file bucket."], :archive_file_server => ["$server", "During an inspect run, the file bucket server to archive files to if archive_files is set."] ) # Plugin information. setdefaults( :main, :plugindest => ["$libdir", "Where Puppet should store plugins that it pulls down from the central server."], :pluginsource => ["puppet://$server/plugins", "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here."], :pluginsync => [false, "Whether plugins should be synced with the central server."], :pluginsignore => [".svn CVS .git", "What files to ignore when pulling down plugins."] ) # Central fact information. setdefaults( :main, :factpath => {:default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should be colon-separated, like normal PATH variables.", :call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. :type => :setting, # Don't consider it a file, because it could be multiple colon-separated files :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }}, :factdest => ["$vardir/facts/", "Where Puppet should store facts that it pulls down from the central server."], :factsource => ["puppet://$server/facts/", "From where to retrieve facts. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here."], :factsync => [false, "Whether facts should be synced with the central server."], :factsignore => [".svn CVS", "What files to ignore when pulling down facts."] ) setdefaults( :tagmail, :tagmap => ["$confdir/tagmail.conf", "The mapping between reporting tags and email addresses."], :sendmail => [which('sendmail') || '', "Where to find the sendmail binary with which to send email."], :reportfrom => ["report@" + [Facter["hostname"].value, Facter["domain"].value].join("."), "The 'from' email address for the reports."], :smtpserver => ["none", "The server through which to send email reports."] ) setdefaults( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :mode => 0660, :owner => "service", :group => "service", :desc => "The database cache for client configurations. Used for querying within the language." }, :dbadapter => [ "sqlite3", "The type of database to use." ], :dbmigrate => [ false, "Whether to automatically migrate the database." ], :dbname => [ "puppet", "The name of the database to use." ], :dbserver => [ "localhost", "The database server for caching. Only used when networked databases are used."], :dbport => [ "", "The database password for caching. Only used when networked databases are used."], :dbuser => [ "puppet", "The database user for caching. Only used when networked databases are used."], :dbpassword => [ "puppet", "The database password for caching. Only used when networked databases are used."], :dbconnections => [ '', "The number of database connections for networked databases. Will be ignored unless the value is a positive integer."], :dbsocket => [ "", "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string."], :railslog => {:default => "$logdir/rails.log", :mode => 0600, :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent" }, :rails_loglevel => ["info", "The log level for Rails connections. The value must be a valid log level within Rails. Production environments normally use `info` and other environments normally use `debug`."] ) setdefaults( :couchdb, :couchdb_url => ["http://127.0.0.1:5984/puppet", "The url where the puppet couchdb database will be created"] ) setdefaults( :transaction, :tags => ["", "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. Values must be comma-separated."], :evaltrace => [false, "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done."], :summarize => [false, "Whether to print a transaction summary." ] ) setdefaults( :main, :external_nodes => ["none", "An external command that can produce node information. The output must be a YAML dump of a hash, and that hash must have one or both of `classes` and `parameters`, where `classes` is an array and `parameters` is a hash. For unknown nodes, the commands should exit with a non-zero exit code. This command makes it straightforward to store your node mapping information in other data sources like databases."]) setdefaults( :ldap, :ldapnodes => [false, "Whether to search for node configurations in LDAP. See http://projects.puppetlabs.com/projects/puppet/wiki/LDAP_Nodes for more information."], :ldapssl => [false, "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side."], :ldaptls => [false, "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side."], :ldapserver => ["ldap", "The LDAP server. Only used if `ldapnodes` is enabled."], :ldapport => [389, "The LDAP port. Only used if `ldapnodes` is enabled."], :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", "The search string used to find an LDAP node."], :ldapclassattrs => ["puppetclass", "The LDAP attributes to use to define Puppet classes. Values should be comma-separated."], :ldapstackedattrs => ["puppetvar", "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values should be comma-separated."], :ldapattrs => ["all", "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns all attributes."], :ldapparentattr => ["parentnode", "The attribute to use to define the parent node."], :ldapuser => ["", "The user to use to connect to LDAP. Must be specified as a full DN."], :ldappassword => ["", "The password to use to connect to LDAP."], :ldapbase => ["", "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory."] ) setdefaults(:master, :storeconfigs => { :default => false, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. :call_on_define => true, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value Puppet.settings[:async_storeconfigs] or Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Node.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) # This doesn't actually work right now. setdefaults( :parser, :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], :templatedir => ["$vardir/templates", "Where Puppet looks for template files. Can be a list of colon-seperated directories." ] ) setdefaults( :puppetdoc, :document_all => [false, "Document all resources"] ) end diff --git a/lib/puppet/indirector/facts/inventory_service.rb b/lib/puppet/indirector/facts/inventory_service.rb new file mode 100644 index 000000000..988e57745 --- /dev/null +++ b/lib/puppet/indirector/facts/inventory_service.rb @@ -0,0 +1,20 @@ +require 'puppet/node/facts' +require 'puppet/indirector/rest' + +class Puppet::Node::Facts::InventoryService < Puppet::Indirector::REST + desc "Find and save facts about nodes using a remote inventory service." + use_server_setting(:inventory_server) + use_port_setting(:inventory_port) + + # We don't want failing to upload to the inventory service to cause any + # failures, so we just suppress them and warn. + def save(request) + begin + super + true + rescue => e + Puppet.warning "Could not upload facts for #{request.key} to inventory service: #{e.to_s}" + false + end + end +end diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index d68c2f163..c530c0e4b 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -1,770 +1,770 @@ # vim: syntax=ruby # the parser class Puppet::Parser::Parser token STRING DQPRE DQMID DQPOST token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN prechigh right NOT nonassoc UMINUS left IN MATCH NOMATCH left TIMES DIV left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left AND left OR preclow rule program: statements_and_declarations | nil statements_and_declarations: statement_or_declaration { result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : []) } | statements_and_declarations statement_or_declaration { if val[1] val[0].push(val[1]) end result = val[0] } # statements is like statements_and_declarations, but it doesn't allow # nested definitions, classes, or nodes. statements: statements_and_declarations { val[0].each do |stmt| if stmt.is_a?(AST::TopLevelConstruct) error "Classes, definitions, and nodes may only appear at toplevel or inside other classes", \ :line => stmt.context[:line], :file => stmt.context[:file] end end result = val[0] } # The main list of valid statements statement_or_declaration: resource | virtualresource | collection | assignment | casestatement | ifstatement_begin | import | fstatement | definition | hostclass | nodedef | resourceoverride | append | relationship relationship: relationship_side edge relationship_side { result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) } | relationship edge relationship_side { result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) } relationship_side: resource | resourceref | collection edge: IN_EDGE | OUT_EDGE | IN_EDGE_SUB | OUT_EDGE_SUB fstatement: NAME LPAREN expressions RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement } | NAME LPAREN expressions COMMA RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement } | NAME LPAREN RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :statement } | NAME funcvalues { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[1], :ftype => :statement } funcvalues: rvalue { result = aryfy(val[0]) } # This rvalue could be an expression | funcvalues COMMA rvalue { val[0].push(val[2]) result = val[0] } expressions: expression { result = aryfy(val[0]) } | expressions comma expression { result = val[0].push(val[2]) } rvalue: quotedtext | name | type | boolean | selector | variable | array | hasharrayaccesses | resourceref | funcrvalue | undef resource: classname LBRACE resourceinstances endsemi RBRACE { @lexer.commentpop result = ast(AST::Resource, :type => val[0], :instances => val[2]) } | classname LBRACE params endcomma RBRACE { # This is a deprecated syntax. error "All resource specifications require names" } | classref LBRACE params endcomma RBRACE { # a defaults setting for a type @lexer.commentpop result = ast(AST::ResourceDefaults, :type => val[0], :parameters => val[2]) } # Override a value set elsewhere in the configuration. resourceoverride: resourceref LBRACE anyparams endcomma RBRACE { @lexer.commentpop result = ast AST::ResourceOverride, :object => val[0], :parameters => val[2] } # Exported and virtual resources; these don't get sent to the client # unless they get collected elsewhere in the db. virtualresource: at resource { type = val[0] if (type == :exported and ! Puppet[:storeconfigs]) Puppet.warning addcontext("You cannot collect without storeconfigs being set") end error "Defaults are not virtualizable" if val[1].is_a? AST::ResourceDefaults method = type.to_s + "=" # Just mark our resource as exported and pass it through. val[1].send(method, true) result = val[1] } at: AT { result = :virtual } | AT AT { result = :exported } # A collection statement. Currently supports no arguments at all, but eventually # will, I assume. collection: classref collectrhand LBRACE anyparams endcomma RBRACE { @lexer.commentpop Puppet.warning addcontext("Collection names must now be capitalized") if val[0] =~ /^[a-z]/ type = val[0].downcase args = {:type => type} if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end args[:override] = val[3] result = ast AST::Collection, args } | classref collectrhand { if val[0] =~ /^[a-z]/ Puppet.warning addcontext("Collection names must now be capitalized") end type = val[0].downcase args = {:type => type } if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args } collectrhand: LCOLLECT collstatements RCOLLECT { if val[1] result = val[1] result.form = :virtual else result = :virtual end } | LLCOLLECT collstatements RRCOLLECT { if val[1] result = val[1] result.form = :exported else result = :exported end } # A mini-language for handling collection comparisons. This is organized # to avoid the need for precedence indications. collstatements: nil | collstatement | collstatements colljoin collstatement { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] } collstatement: collexpr | LPAREN collstatements RPAREN { result = val[1] result.parens = true } colljoin: AND { result=val[0][:value] } | OR { result=val[0][:value] } collexpr: colllval ISEQUAL expression { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val } | colllval NOTEQUAL expression { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val } colllval: variable | name resourceinst: resourcename COLON params endcomma { result = ast AST::ResourceInstance, :title => val[0], :parameters => val[2] } resourceinstances: resourceinst { result = aryfy(val[0]) } | resourceinstances SEMIC resourceinst { val[0].push val[2] result = val[0] } endsemi: # nothing | SEMIC undef: UNDEF { result = ast AST::Undef, :value => :undef } name: NAME { result = ast AST::Name, :value => val[0][:value], :line => val[0][:line] } type: CLASSREF { result = ast AST::Type, :value => val[0][:value], :line => val[0][:line] } resourcename: quotedtext | name | type | selector | variable | array | hasharrayaccesses assignment: VARIABLE EQUALS expression { raise Puppet::ParseError, "Cannot assign to variables in other namespaces" if val[0][:value] =~ /::/ # this is distinct from referencing a variable variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :line => val[0][:line] } | hasharrayaccess EQUALS expression { result = ast AST::VarDef, :name => val[0], :value => val[2] } append: VARIABLE APPENDS expression { variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :append => true, :line => val[0][:line] } params: # nothing { result = ast AST::ASTArray } | param { result = aryfy(val[0]) } | params COMMA param { val[0].push(val[2]) result = val[0] } param: NAME FARROW expression { result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2] } addparam: NAME PARROW expression { result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2], :add => true } anyparam: param | addparam anyparams: # nothing { result = ast AST::ASTArray } | anyparam { result = aryfy(val[0]) } | anyparams COMMA anyparam { val[0].push(val[2]) result = val[0] } # We currently require arguments in these functions. funcrvalue: NAME LPAREN expressions RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :rvalue } | NAME LPAREN RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :rvalue } quotedtext: STRING { result = ast AST::String, :value => val[0][:value], :line => val[0][:line] } | DQPRE dqrval { result = ast AST::Concat, :value => [ast(AST::String,val[0])]+val[1], :line => val[0][:line] } dqrval: expression dqtail { result = [val[0]] + val[1] } dqtail: DQPOST { result = [ast(AST::String,val[0])] } | DQMID dqrval { result = [ast(AST::String,val[0])] + val[1] } boolean: BOOLEAN { result = ast AST::Boolean, :value => val[0][:value], :line => val[0][:line] } resourceref: NAME LBRACK expressions RBRACK { Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") result = ast AST::ResourceReference, :type => val[0][:value], :line => val[0][:line], :title => val[2] } | classref LBRACK expressions RBRACK { result = ast AST::ResourceReference, :type => val[0], :title => val[2] } ifstatement_begin: IF ifstatement { result = val[1] } ifstatement: expression LBRACE statements RBRACE else { @lexer.commentpop args = { :test => val[0], :statements => val[2] } args[:else] = val[4] if val[4] result = ast AST::IfStatement, args } | expression LBRACE RBRACE else { @lexer.commentpop args = { :test => val[0], :statements => ast(AST::Nop) } args[:else] = val[3] if val[3] result = ast AST::IfStatement, args } else: # nothing | ELSIF ifstatement { result = ast AST::Else, :statements => val[1] } | ELSE LBRACE statements RBRACE { @lexer.commentpop result = ast AST::Else, :statements => val[2] } | ELSE LBRACE RBRACE { @lexer.commentpop result = ast AST::Else, :statements => ast(AST::Nop) } # Unlike yacc/bison, it seems racc # gives tons of shift/reduce warnings # with the following syntax: # # expression: ... # | expression arithop expressio { ... } # # arithop: PLUS | MINUS | DIVIDE | TIMES ... # # So I had to develop the expression by adding one rule # per operator :-( expression: rvalue | hash | expression IN expression { result = ast AST::InOperator, :lval => val[0], :rval => val[2] } | expression MATCH regex { result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression NOMATCH regex { result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression PLUS expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression MINUS expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression DIV expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression TIMES expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression LSHIFT expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression RSHIFT expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | MINUS expression =UMINUS { result = ast AST::Minus, :value => val[1] } | expression NOTEQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression ISEQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression GREATERTHAN expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression GREATEREQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression LESSTHAN expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression LESSEQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | NOT expression { result = ast AST::Not, :value => val[1] } | expression AND expression { result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression OR expression { result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | LPAREN expression RPAREN { result = val[1] } casestatement: CASE expression LBRACE caseopts RBRACE { @lexer.commentpop result = ast AST::CaseStatement, :test => val[1], :options => val[3] } caseopts: caseopt { result = aryfy(val[0]) } | caseopts caseopt { val[0].push val[1] result = val[0] } caseopt: casevalues COLON LBRACE statements RBRACE { @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] } | casevalues COLON LBRACE RBRACE { @lexer.commentpop result = ast( AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) ) } casevalues: selectlhand { result = aryfy(val[0]) } | casevalues COMMA selectlhand { val[0].push(val[2]) result = val[0] } selector: selectlhand QMARK svalues { result = ast AST::Selector, :param => val[0], :values => val[2] } svalues: selectval | LBRACE sintvalues endcomma RBRACE { @lexer.commentpop result = val[1] } sintvalues: selectval | sintvalues comma selectval { if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end } selectval: selectlhand FARROW rvalue { result = ast AST::ResourceParam, :param => val[0], :value => val[2] } selectlhand: name | type | quotedtext | variable | funcrvalue | boolean | undef | hasharrayaccess | DEFAULT { result = ast AST::Default, :value => val[0][:value], :line => val[0][:line] } | regex # These are only used for importing, and we don't interpolate there. string: STRING { result = [val[0][:value]] } strings: string | strings COMMA string { result = val[0] += val[2] } import: IMPORT strings { val[1].each do |file| import(file) end result = nil } # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { definition: DEFINE classname argumentlist LBRACE statements RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :code => val[4], :line => val[0][:line])) @lexer.indefine = false #} | DEFINE NAME argumentlist parent LBRACE RBRACE { } | DEFINE classname argumentlist LBRACE RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :line => val[0][:line])) @lexer.indefine = false } #hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { hostclass: CLASS classname argumentlist classparent LBRACE statements_and_declarations RBRACE { @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :code => val[5], :line => val[0][:line])) } | CLASS classname argumentlist classparent LBRACE RBRACE { @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :line => val[0][:line])) } nodedef: NODE hostnames nodeparent LBRACE statements RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :code => val[4], :line => val[0][:line])) } | NODE hostnames nodeparent LBRACE RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :line => val[0][:line])) } classref: CLASSREF { result = val[0][:value] } classname: NAME { result = val[0][:value] } | CLASS { result = "class" } # Multiple hostnames, as used for node names. These are all literal # strings, not AST objects. hostnames: nodename { result = [result] } | hostnames COMMA nodename { result = val[0] result << val[2] } nodename: hostname { result = ast AST::HostName, :value => val[0] } hostname: NAME { result = val[0][:value] } | STRING { result = val[0][:value] } | DEFAULT { result = val[0][:value] } | regex nil: { result = nil } nothing: { result = ast AST::ASTArray, :children => [] } argumentlist: nil | LPAREN nothing RPAREN { result = nil } - | LPAREN arguments RPAREN { + | LPAREN arguments endcomma RPAREN { result = val[1] result = [result] unless result[0].is_a?(Array) } arguments: argument | arguments COMMA argument { result = val[0] result = [result] unless result[0].is_a?(Array) result << val[2] } argument: NAME EQUALS expression { Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0][:value], val[2]] } | NAME { Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0][:value]] } | VARIABLE EQUALS expression { result = [val[0][:value], val[2]] } | VARIABLE { result = [val[0][:value]] } nodeparent: nil | INHERITS hostname { result = val[1] } classparent: nil | INHERITS classnameordefault { result = val[1] } classnameordefault: classname | DEFAULT variable: VARIABLE { result = ast AST::Variable, :value => val[0][:value], :line => val[0][:line] } array: LBRACK expressions RBRACK { result = val[1] } | LBRACK expressions COMMA RBRACK { result = val[1] } | LBRACK RBRACK { result = ast AST::ASTArray } comma: FARROW | COMMA endcomma: # nothing | COMMA { result = nil } regex: REGEX { result = ast AST::Regex, :value => val[0][:value] } hash: LBRACE hashpairs RBRACE { if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end } | LBRACE hashpairs COMMA RBRACE { if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end } | LBRACE RBRACE { result = ast AST::ASTHash } hashpairs: hashpair | hashpairs COMMA hashpair { if val[0].instance_of?(AST::ASTHash) result = val[0].merge(val[2]) else result = ast AST::ASTHash, :value => val[0] result.merge(val[2]) end } hashpair: key FARROW expression { result = ast AST::ASTHash, { :value => { val[0] => val[2] } } } key: NAME { result = val[0][:value] } | quotedtext { result = val[0] } hasharrayaccess: VARIABLE LBRACK expression RBRACK { result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2] } hasharrayaccesses: hasharrayaccess | hasharrayaccesses LBRACK expression RBRACK { result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2] } end ---- header ---- require 'puppet' require 'puppet/util/loadedfile' require 'puppet/parser/lexer' require 'puppet/parser/ast' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end ---- inner ---- # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: # mode: ruby # End: diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index f46d20d9f..00fdf349f 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -1,2333 +1,2343 @@ # # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.6 # from Racc grammer file "". # require 'racc/parser.rb' require 'puppet' require 'puppet/util/loadedfile' require 'puppet/parser/lexer' require 'puppet/parser/ast' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end module Puppet module Parser class Parser < Racc::Parser module_eval(<<'...end grammar.ra/module_eval...', 'grammar.ra', 763) # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: # mode: ruby # End: ...end grammar.ra/module_eval... ##### State transition tables begin ### clist = [ -'222,221,209,61,62,249,204,209,127,323,192,317,322,184,179,109,214,251', -'301,330,126,106,180,182,181,183,126,329,184,179,302,206,245,244,335', -'245,244,180,182,181,183,195,126,210,106,186,185,334,85,173,174,176,175', -'177,178,275,171,172,276,105,186,185,107,170,173,174,176,175,177,178', -'308,171,172,61,62,157,81,94,170,95,80,71,105,61,62,107,162,94,77,95', -'73,161,309,61,62,93,157,94,81,95,-106,-157,114,76,71,93,70,162,288,34', -'63,59,161,287,68,93,70,311,60,92,63,59,58,151,68,91,70,75,60,92,63,59', -'58,71,68,91,157,151,60,92,61,62,58,71,94,91,95,162,314,288,61,62,161', -'71,287,-106,-106,-106,-106,248,61,62,93,157,94,81,95,247,81,80,106,208', -'80,70,162,341,34,63,59,161,81,68,93,70,80,60,92,63,225,58,151,68,91', -'70,132,138,77,63,59,58,71,68,-170,61,62,60,92,64,105,58,71,107,91,61', -'62,61,62,94,323,95,71,322,324,61,62,44,152,94,242,95,71,245,244,61,62', -'93,70,94,81,95,63,59,114,320,68,93,70,299,60,34,63,59,58,195,68,93,70', -'73,60,92,63,59,58,71,68,91,70,198,60,92,63,59,58,71,68,91,61,62,60,92', -'214,212,58,71,117,91,61,62,295,85,94,201,95,71,211,201,61,62,83,84,94', -'292,95,291,171,172,61,62,93,70,94,170,95,63,225,171,172,68,93,70,-156', -'138,170,63,59,58,-154,68,93,70,125,60,92,63,59,58,71,68,91,70,-153,60', -'92,63,59,58,71,68,91,214,346,60,92,61,62,58,71,94,91,95,-155,-151,-150', -'61,62,-152,71,94,124,95,283,117,333,61,62,93,274,94,97,95,214,224,336', -'61,62,93,70,94,250,337,63,59,214,251,68,93,70,338,60,92,63,59,58,127', -'68,91,70,277,60,92,63,59,58,71,68,91,70,208,60,92,63,59,58,71,68,91', -'305,236,60,238,61,62,58,71,94,239,95,238,349,-152,61,62,97,71,94,-150', -'95,86,352,-151,61,62,93,223,94,303,95,214,224,214,251,353,93,70,82,355', -'-153,63,59,125,72,68,93,70,43,60,92,63,59,58,361,68,91,70,362,60,92', -'63,59,58,71,68,91,238,-204,60,92,61,62,58,71,94,91,95,38,39,40,41,209', -'117,71,176,175,71,61,62,171,172,94,93,95,71,35,170,38,39,40,41,369,370', -'70,,61,62,63,59,93,122,68,327,,,60,92,,,58,70,,91,,63,59,,,68,,71,,60', -'92,61,62,58,,70,91,228,,63,225,,,68,71,,,138,61,62,,58,94,,95,176,175', -',,,171,172,71,,70,,,170,63,225,93,,68,61,62,,138,94,,95,58,70,,,,63', -'59,,,68,,71,,60,92,93,,58,61,62,91,,94,,95,,70,,71,,63,59,,,68,,,,60', -'92,93,218,58,61,62,91,,94,,95,,70,,71,,63,59,,,68,,,,60,92,93,,58,61', -'62,91,,94,,95,,70,,71,,63,59,,,68,,,,60,92,93,,58,61,62,91,,94,,,,70', -',71,,63,59,,,68,,,,60,92,,,58,61,62,91,,94,,95,,70,,71,,63,59,,,68,61', -'62,,60,,93,,58,,61,62,,,94,,95,70,,71,,63,59,,,68,,,,60,92,,93,58,70', -',91,,63,225,,,68,70,71,,138,63,59,,58,68,,61,62,60,92,94,,58,,71,91', -'61,62,,,94,,95,71,,,61,62,,,94,131,95,,,,61,62,93,70,94,,95,63,225,', -',68,93,70,,138,,63,59,58,,68,93,70,,60,92,63,59,58,71,68,91,70,,60,92', -'63,59,58,71,68,91,61,62,60,92,,,58,71,,91,61,62,,,94,,,71,,,61,62,,', -'94,,95,,,,61,62,,70,94,,95,63,225,,,68,93,70,,138,,63,137,58,,68,93', -'70,,138,,63,59,58,71,68,,70,,60,92,63,59,58,71,68,91,252,,60,92,,,58', -'71,,91,184,179,-24,-24,-24,-24,,71,,180,182,181,183,,,173,174,176,175', -',61,62,171,172,94,131,95,,,170,,,186,185,,,173,174,176,175,177,178,93', -'171,172,61,62,,,94,170,95,,70,,,,63,59,,,68,,,,60,92,93,,58,61,62,91', -',94,,95,,70,,71,,63,59,,,68,,,,60,92,93,,58,61,62,91,,94,,95,,70,,71', -',63,59,,,68,,,,60,92,93,,58,61,62,91,,94,,95,,70,,71,,63,59,,,68,,,', -'60,92,93,,58,61,62,91,,94,,95,,70,,71,,63,59,,,68,,,,60,92,93,,58,61', -'62,91,,94,,95,,70,,71,,63,59,,,68,,,,60,92,93,,58,61,62,91,,94,,95,', -'70,,71,,63,59,,,68,,,,60,92,93,,58,61,62,91,,94,,95,,70,,71,,63,59,', -',68,,,,60,92,93,,58,61,62,91,,94,,95,,70,,71,,63,59,,,68,,,,60,92,93', -',58,61,62,91,,94,,95,,70,,71,,63,59,,,68,,,,60,92,93,,58,,,91,-22,-22', -'-22,-22,,70,,71,,63,59,,,68,,,169,60,92,,,58,,,91,184,179,,,,,197,71', -',180,182,181,183,,,184,179,,,,,,,,180,182,181,183,,,,,186,185,,,173', -'174,176,175,177,178,,171,172,,,186,185,,170,173,174,176,175,177,178', -',171,172,184,179,,,,170,,,,180,182,181,183,,,184,179,,,,,,,,180,182', -'181,183,,,,,186,185,,,173,174,176,175,177,178,,171,172,,,186,185,,170', -'173,174,176,175,177,178,,171,172,184,179,,,,170,,,,180,182,181,183,', -',184,179,,,,,,,,180,182,181,183,,,,,186,185,,,173,174,176,175,177,178', -',171,172,,,186,185,,170,173,174,176,175,177,178,,171,172,184,179,,,', -'170,,,273,180,182,181,183,,,184,179,,,,,,,,180,182,181,183,,,,,186,185', -',,173,174,176,175,177,178,,171,172,,,186,185,,170,173,174,176,175,177', -'178,,171,172,184,179,,,,170,,,,180,182,181,183,,,184,179,,,,,,,,180', -'182,181,183,,,,,186,185,,,173,174,176,175,177,178,,171,172,,,186,185', -',170,173,174,176,175,177,178,,171,172,184,179,,,,170,,,,180,182,181', -'183,,,184,179,,,,,,,,180,182,181,183,,,,,186,185,,,173,174,176,175,177', -'178,,171,172,,,,,,170,173,174,176,175,177,178,,171,172,184,179,,,,170', -',,,180,182,181,183,,,184,179,,,,,,,,180,182,181,183,,,,,,,,,173,174', -'176,175,177,178,,171,172,,,186,185,,170,173,174,176,175,177,178,,171', -'172,184,179,,,,170,,,,180,182,181,183,,,184,179,,,,,,,,180,182,181,183', -',,,,,,,,173,174,176,175,177,178,,171,172,,,,185,,170,173,174,176,175', -'177,178,,171,172,184,179,,,,170,,,,180,182,181,183,,,,179,,,,,,,,180', -',,,,,,,186,185,179,,173,174,176,175,177,178,180,171,172,,,,,179,170', -'173,174,176,175,177,178,180,171,172,,,,,,170,,179,,173,174,176,175,177', -'178,180,171,172,,,,,,170,173,174,176,175,177,178,,171,172,,332,,,,170', -',,,173,174,176,175,177,178,,171,172,,360,,,28,170,30,31,,26,32,,33,', -'21,,29,368,25,,,34,28,,30,31,,26,32,,33,,21,367,29,,25,,28,34,30,31', -',26,32,,33,,21,343,29,,25,,28,34,30,31,,26,32,,33,,21,255,29,,25,,28', -'34,30,31,,26,32,,33,,21,365,29,,25,,28,34,30,31,,26,32,,33,,21,,29,', -'25,,28,34,30,31,,26,32,,33,,21,,29,28,25,30,31,34,26,32,,33,,21,,29', -'28,25,30,31,34,26,32,,33,,21,,29,28,25,30,31,34,26,32,,33,,21,,29,,25', -',,34,173,174,176,175,177,178,,171,172,,,,,,170,173,174,176,175,177,178', -',171,172,173,174,176,175,,170,,171,172,,,,,,170' ] - racc_action_table = arr = Array.new(2065, nil) +'255,256,225,47,228,48,221,222,175,73,78,342,207,189,193,145,338,211', +'49,343,331,227,184,186,190,192,174,221,243,73,78,215,216,73,78,336,201', +'98,334,101,147,244,45,232,-154,185,188,197,302,195,196,179,180,182,183', +'95,187,191,73,78,221,356,98,181,101,120,72,197,73,78,86,88,215,216,93', +'219,118,309,79,96,95,124,89,218,60,103,73,78,60,225,98,72,207,63,35', +'86,88,211,36,93,120,72,174,79,96,86,250,89,35,93,103,118,36,154,187', +'191,124,89,63,72,132,181,64,86,88,66,64,93,63,66,52,79,73,78,120,89', +'98,311,101,50,51,142,73,78,63,118,63,171,63,231,124,120,73,78,95,262', +'98,35,101,35,35,114,118,36,114,72,316,124,11,86,88,11,317,93,95,72,170', +'79,96,86,250,89,132,93,103,72,-170,154,322,86,88,89,63,93,323,324,325', +'79,96,73,78,89,63,98,103,101,174,35,167,73,78,36,63,98,288,101,-153', +'287,11,73,78,95,52,98,330,101,47,187,191,73,78,95,72,98,181,-106,86', +'88,-152,-150,93,95,72,-151,79,96,86,88,89,205,93,103,72,299,79,96,86', +'88,89,63,93,103,72,298,79,96,86,88,89,63,93,103,73,78,79,296,85,-157', +'89,63,214,73,78,215,216,98,-155,101,144,63,-106,-106,-106,-106,76,48', +'60,336,73,78,334,109,98,95,101,72,142,223,295,86,88,221,222,93,72,105', +'333,79,86,88,95,89,93,289,266,212,79,96,221,260,89,72,63,103,64,86,88', +'66,175,93,63,63,132,79,96,73,78,89,261,98,103,101,221,260,128,142,63', +'63,63,179,180,345,73,78,187,191,98,95,101,-156,225,181,348,195,196,179', +'180,109,72,227,187,191,86,88,95,349,93,181,332,231,79,96,221,222,89', +'72,105,103,233,86,88,67,352,93,227,63,237,79,96,73,78,89,239,98,103', +'101,-153,229,195,196,179,180,63,57,360,187,191,189,193,179,180,95,181', +'170,187,191,184,186,190,192,-152,181,72,362,53,240,86,88,44,364,93,-204', +'-150,38,79,96,-151,370,89,185,188,103,371,195,196,179,180,182,183,63', +'187,191,73,78,,,98,181,101,,,,73,78,,,98,,101,,,,73,78,95,,98,,101,-22', +'-22,-22,-22,,95,72,,,,86,88,,,93,95,72,,79,96,86,88,89,,93,103,72,,79', +'96,86,88,89,63,93,103,,,79,96,73,78,89,63,,103,,341,,,73,78,,63,98,', +'101,,,,73,78,,,98,,101,,,,73,78,95,72,98,,101,86,250,,,93,95,72,,154', +',86,88,89,,93,95,72,,79,96,86,88,89,63,93,103,72,,79,96,86,88,89,63', +'93,103,,,79,96,73,78,89,63,,103,252,,,,73,78,,63,98,,101,,,,73,78,,', +'98,,101,,,,73,78,95,72,98,,101,86,250,,,93,95,72,,154,,86,88,89,,93', +'95,72,,79,96,86,88,89,63,93,103,72,,79,96,86,88,89,63,93,103,,,79,96', +'73,78,89,63,98,103,101,,,,73,78,,63,98,,101,,,,73,78,95,,98,173,101', +'41,42,43,39,,95,72,,,,86,88,,,93,95,72,,79,96,86,88,89,,93,103,72,,79', +'96,86,88,89,63,93,103,,,79,96,73,78,89,63,98,103,101,,,,73,78,,63,98', +'-24,-24,-24,-24,,73,78,95,,98,,101,41,42,43,39,,,72,,,,86,88,,,93,95', +'72,,79,96,86,158,89,,93,103,72,,154,,86,88,89,63,93,,,,79,96,73,78,89', +'63,98,103,101,,,,73,78,,63,98,,101,,,,,,95,,,,,,,,,,95,72,,,,86,88,', +',93,,72,,79,96,86,88,89,,93,103,329,,79,96,,,89,63,,103,189,193,,,,', +',63,,184,186,190,192,,,,,,,,73,78,,,98,,101,,,,,,185,188,,,195,196,179', +'180,182,183,95,187,191,73,78,,,98,181,101,,72,,73,78,86,88,,,93,,,,79', +'96,95,165,89,,,103,73,78,,,98,72,101,63,,86,88,,,93,,72,,79,96,86,250', +'89,95,93,103,73,78,154,,98,,89,63,72,,,,86,88,,,93,63,,,79,96,,,89,73', +'78,103,,98,,101,,72,,63,,86,250,,,93,73,78,,154,98,95,101,89,,,,,,,', +',72,,63,,86,88,95,,93,,,,79,96,,,89,72,,103,,86,88,,,93,,63,,79,96,73', +'78,89,,98,103,101,,,,73,78,,63,98,173,101,,,,73,78,95,,98,,101,,,,73', +'78,95,72,,,,86,88,,,93,95,72,,79,96,86,88,89,,93,103,72,,79,96,86,88', +'89,63,93,103,72,,79,96,86,250,89,63,93,103,,,154,,73,78,89,63,98,,101', +',,,73,78,,63,98,,101,,,,73,78,95,,98,,101,,,,,,95,72,,,,86,88,,,93,95', +'72,,79,96,86,88,89,,93,103,72,,79,96,86,88,89,63,93,103,,,79,96,73,78', +'89,63,98,103,101,,,,73,78,,63,98,,101,,,,73,78,95,,98,,101,,,,,,95,72', +',,,86,88,,,93,95,72,,79,96,86,88,89,,93,103,72,,79,96,86,88,89,63,93', +'103,,194,79,96,,,89,63,,103,189,193,,,,,204,63,,184,186,190,192,,,189', +'193,,,,,,,,184,186,190,192,,,,,185,188,,,195,196,179,180,182,183,,187', +'191,,,185,188,,181,195,196,179,180,182,183,,187,191,189,193,,,,181,', +',,184,186,190,192,,,189,193,,,,,,,,184,186,190,192,,,,,185,188,,,195', +'196,179,180,182,183,,187,191,,,185,188,,181,195,196,179,180,182,183', +',187,191,189,193,,,,181,,,,184,186,190,192,,,189,193,,,,,,,,184,186', +'190,192,,,,,185,188,,,195,196,179,180,182,183,,187,191,,,185,188,,181', +'195,196,179,180,182,183,,187,191,189,193,,,,181,,,,184,186,190,192,', +',189,193,,,,,,,265,184,186,190,192,,,,,185,188,,,195,196,179,180,182', +'183,,187,191,,,185,188,,181,195,196,179,180,182,183,,187,191,189,193', +',,,181,,,,184,186,190,192,,,189,193,,,,,,,,184,186,190,192,,,,,185,188', +',,195,196,179,180,182,183,,187,191,,,,188,,181,195,196,179,180,182,183', +',187,191,189,193,,,,181,,,,184,186,190,192,,,189,193,,,,,,,,184,186', +'190,192,,,,,185,188,,,195,196,179,180,182,183,,187,191,,,,,,181,195', +'196,179,180,182,183,,187,191,189,193,,,,181,,,,184,186,190,192,,,189', +'193,,,,,,,,184,186,190,192,,,,,,,,,195,196,179,180,182,183,,187,191', +',,,,,181,195,196,179,180,182,183,,187,191,189,193,,,,181,,,,184,186', +'190,192,,,189,193,,,,,,,,184,186,190,192,,,,,185,188,,,195,196,179,180', +'182,183,,187,191,,,185,188,,181,195,196,179,180,182,183,,187,191,189', +'193,,,,181,,,,184,186,190,192,,,,193,,,,,,,,184,,,,,,,,185,188,193,', +'195,196,179,180,182,183,184,187,191,,,,,193,181,195,196,179,180,182', +'183,184,187,191,,,,,,181,,193,,195,196,179,180,182,183,184,187,191,', +',,,,181,195,196,179,180,182,183,,187,191,,284,,,,181,,,,195,196,179', +'180,182,183,,187,191,,369,,,26,181,32,1,,8,12,,18,,24,,29,319,2,,,11', +'26,,32,1,,8,12,,18,,24,367,29,,2,,26,11,32,1,,8,12,,18,,24,301,29,,2', +',26,11,32,1,,8,12,,18,,24,363,29,,2,,26,11,32,1,,8,12,,18,,24,351,29', +',2,,26,11,32,1,,8,12,,18,,24,,29,,2,,26,11,32,1,,8,12,,18,,24,,29,26', +'2,32,1,11,8,12,,18,,24,,29,26,2,32,1,11,8,12,,18,,24,,29,26,2,32,1,11', +'8,12,,18,,24,,29,,2,,,11,195,196,179,180,182,183,,187,191,,,,,,181,195', +'196,179,180,182,183,,187,191,,,,,,181' ] + racc_action_table = arr = Array.new(2074, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end clist = [ -'129,129,117,95,95,163,102,137,59,255,95,246,255,129,129,35,217,217,216', -'280,137,33,129,129,129,129,59,280,216,216,217,102,163,163,285,246,246', -'216,216,216,216,95,225,117,204,129,129,285,70,129,129,129,129,129,129', -'191,129,129,191,33,216,216,33,129,216,216,216,216,216,216,229,216,216', -'170,170,243,31,170,216,170,31,33,204,173,173,204,243,173,23,173,23,243', -'235,174,174,170,157,174,37,174,223,67,37,23,204,173,170,157,201,37,170', -'170,157,201,170,174,173,237,170,170,173,173,170,238,173,170,174,23,173', -'173,174,174,173,170,174,173,75,239,174,174,175,175,174,173,175,174,175', -'75,240,335,345,345,75,174,335,223,223,223,223,158,176,176,175,76,176', -'24,176,158,291,24,206,241,291,175,76,291,24,175,175,76,32,175,176,345', -'32,175,175,345,345,175,73,345,175,176,66,345,65,176,176,345,175,176', -'60,21,21,176,176,21,206,176,345,206,176,337,337,276,276,337,320,337', -'176,320,276,177,177,21,74,177,153,177,206,153,153,336,336,337,21,336', -'42,336,21,21,42,254,21,177,337,212,21,42,337,337,21,276,337,336,177', -'78,337,337,177,177,337,21,177,337,336,99,177,177,336,336,177,337,336', -'177,330,330,336,336,121,121,336,177,208,336,178,178,207,26,178,100,178', -'336,121,101,322,322,26,26,322,203,322,202,261,261,28,28,178,330,28,261', -'28,330,330,262,262,330,322,178,57,330,262,178,178,330,56,178,28,322', -'54,178,178,322,322,178,330,322,178,28,52,322,322,28,28,322,178,28,322', -'306,306,28,28,29,29,28,322,29,28,29,50,49,48,179,179,47,28,179,45,179', -'199,43,284,180,180,29,190,180,198,180,190,190,287,308,308,179,29,308', -'164,288,29,29,164,164,29,180,179,289,29,29,179,179,29,114,179,29,180', -'194,179,179,180,180,179,29,180,179,308,116,180,180,308,308,180,179,308', -'180,224,133,308,134,181,181,308,180,181,135,181,313,315,139,182,182', -'30,308,182,140,182,27,323,141,183,183,181,130,183,219,183,130,130,219', -'219,329,182,181,25,331,143,181,181,145,22,181,183,182,20,181,181,182', -'182,181,342,182,181,183,344,182,182,183,183,182,181,183,182,150,346', -'183,183,184,184,183,182,184,183,184,19,19,19,19,151,152,183,260,260', -'172,44,44,260,260,44,184,44,171,1,260,18,18,18,18,364,366,184,,278,278', -'184,184,44,44,184,278,,,184,184,,,184,44,,184,,44,44,,,44,,184,,44,44', -'132,132,44,,278,44,132,,278,278,,,278,44,,,278,277,277,,278,277,,277', -'259,259,,,,259,259,278,,132,,,259,132,132,277,,132,185,185,,132,185', -',185,132,277,,,,277,277,,,277,,132,,277,277,185,,277,126,126,277,,126', -',126,,185,,277,,185,185,,,185,,,,185,185,126,126,185,127,127,185,,127', -',127,,126,,185,,126,126,,,126,,,,126,126,127,,126,125,125,126,,125,', -'125,,127,,126,,127,127,,,127,,,,127,127,125,,127,124,124,127,,124,,', -',125,,127,,125,125,,,125,,,,125,125,,,125,186,186,125,,186,,186,,124', -',125,,124,124,,,124,197,197,,124,,186,,124,,248,248,,,248,,248,186,', -'124,,186,186,,,186,,,,186,186,,248,186,197,,186,,197,197,,,197,248,186', -',197,248,248,,197,248,,236,236,248,248,236,,248,,197,248,62,62,,,62', -',62,248,,,64,64,,,64,64,64,,,,247,247,62,236,247,,247,236,236,,,236', -'64,62,,236,,62,62,236,,62,247,64,,62,62,64,64,62,236,64,62,247,,64,64', -'247,247,64,62,247,64,228,228,247,247,,,247,64,,247,72,72,,,72,,,247', -',,209,209,,,209,,209,,,,222,222,,228,222,,222,228,228,,,228,209,72,', -'228,,72,72,228,,72,222,209,,72,,209,209,72,228,209,,222,,209,209,222', -'222,209,72,222,209,167,,222,222,,,222,209,,222,167,167,7,7,7,7,,222', -',167,167,167,167,,,263,263,263,263,,94,94,263,263,94,94,94,,,263,,,167', -'167,,,167,167,167,167,167,167,94,167,167,77,77,,,77,167,77,,94,,,,94', -'94,,,94,,,,94,94,77,,94,210,210,94,,210,,210,,77,,94,,77,77,,,77,,,', -'77,77,210,,77,83,83,77,,83,,83,,210,,77,,210,210,,,210,,,,210,210,83', -',210,84,84,210,,84,,84,,83,,210,,83,83,,,83,,,,83,83,84,,83,85,85,83', -',85,,85,,84,,83,,84,84,,,84,,,,84,84,85,,84,86,86,84,,86,,86,,85,,84', -',85,85,,,85,,,,85,85,86,,85,91,91,85,,91,,91,,86,,85,,86,86,,,86,,,', -'86,86,91,,86,92,92,86,,92,,92,,91,,86,,91,91,,,91,,,,91,91,92,,91,93', -'93,91,,93,,93,,92,,91,,92,92,,,92,,,,92,92,93,,92,213,213,92,,213,,213', -',93,,92,,93,93,,,93,,,,93,93,213,,93,,,93,5,5,5,5,,213,,93,,213,213', -',,213,,,88,213,213,,,213,,,213,88,88,,,,,96,213,,88,88,88,88,,,96,96', -',,,,,,,96,96,96,96,,,,,88,88,,,88,88,88,88,88,88,,88,88,,,96,96,,88', -'96,96,96,96,96,96,,96,96,300,300,,,,96,,,,300,300,300,300,,,166,166', -',,,,,,,166,166,166,166,,,,,300,300,,,300,300,300,300,300,300,,300,300', -',,166,166,,300,166,166,166,166,166,166,,166,166,165,165,,,,166,,,,165', -'165,165,165,,,168,168,,,,,,,,168,168,168,168,,,,,165,165,,,165,165,165', -'165,165,165,,165,165,,,168,168,,165,168,168,168,168,168,168,,168,168', -'189,189,,,,168,,,189,189,189,189,189,,,358,358,,,,,,,,358,358,358,358', -',,,,189,189,,,189,189,189,189,189,189,,189,189,,,358,358,,189,358,358', -'358,358,358,358,,358,358,357,357,,,,358,,,,357,357,357,357,,,123,123', -',,,,,,,123,123,123,123,,,,,357,357,,,357,357,357,357,357,357,,357,357', -',,123,123,,357,123,123,123,123,123,123,,123,123,326,326,,,,123,,,,326', -'326,326,326,,,319,319,,,,,,,,319,319,319,319,,,,,326,326,,,326,326,326', -'326,326,326,,326,326,,,,,,326,319,319,319,319,319,319,,319,319,318,318', -',,,319,,,,318,318,318,318,,,298,298,,,,,,,,298,298,298,298,,,,,,,,,318', -'318,318,318,318,318,,318,318,,,298,298,,318,298,298,298,298,298,298', -',298,298,271,271,,,,298,,,,271,271,271,271,,,272,272,,,,,,,,272,272', -'272,272,,,,,,,,,271,271,271,271,271,271,,271,271,,,,272,,271,272,272', -'272,272,272,272,,272,272,297,297,,,,272,,,,297,297,297,297,,,,269,,', -',,,,,269,,,,,,,,297,297,268,,297,297,297,297,297,297,268,297,297,,,', -',267,297,269,269,269,269,269,269,267,269,269,,,,,,269,,270,,268,268', -'268,268,268,268,270,268,268,,,,,,268,267,267,267,267,267,267,,267,267', -',283,,,,267,,,,270,270,270,270,270,270,,270,270,,338,,,283,270,283,283', -',283,283,,283,,283,,283,359,283,,,283,338,,338,338,,338,338,,338,,338', -'353,338,,338,,359,338,359,359,,359,359,,359,,359,292,359,,359,,353,359', -'353,353,,353,353,,353,,353,169,353,,353,,292,353,292,292,,292,292,,292', -',292,352,292,,292,,169,292,169,169,,169,169,,169,,169,,169,,169,,352', -'169,352,352,,352,352,,352,,352,,352,0,352,0,0,352,0,0,,0,,0,,0,253,0', -'253,253,0,253,253,,253,,253,,253,2,253,2,2,253,2,2,,2,,2,,2,,2,,,2,266', -'266,266,266,266,266,,266,266,,,,,,266,265,265,265,265,265,265,,265,265', -'264,264,264,264,,265,,264,264,,,,,,264' ] - racc_action_check = arr = Array.new(2065, nil) +'169,169,142,6,134,6,263,263,88,287,287,292,298,169,169,59,287,298,6', +'292,263,164,169,169,169,169,88,166,166,101,101,134,134,50,50,333,101', +'50,333,50,59,166,6,142,83,169,169,287,213,169,169,169,169,169,169,50', +'169,169,170,170,327,327,170,169,170,217,50,101,357,357,50,50,213,213', +'50,127,217,226,50,50,170,217,50,127,18,50,171,171,147,158,171,170,109', +'50,12,170,170,109,12,170,49,357,158,170,170,357,357,170,1,357,170,49', +'1,357,268,268,49,357,170,171,227,268,18,171,171,18,147,171,357,147,8', +'171,174,174,120,171,174,230,174,8,8,231,342,342,18,120,171,82,147,220', +'120,45,175,175,174,174,175,108,175,233,40,108,45,233,40,174,233,45,108', +'174,174,40,235,174,175,342,81,174,174,342,342,174,239,342,174,175,79', +'342,241,175,175,342,174,175,242,243,247,175,175,179,179,175,342,179', +'175,179,250,13,77,180,180,13,175,180,199,180,75,199,13,334,334,179,72', +'334,260,334,70,267,267,325,325,180,179,325,267,261,179,179,90,91,179', +'334,180,94,179,179,180,180,179,107,180,179,334,211,180,180,334,334,180', +'179,334,180,325,209,334,334,325,325,334,180,325,334,24,24,325,208,24', +'69,325,334,122,26,26,122,122,26,68,26,57,325,261,261,261,261,24,55,145', +'284,181,181,284,54,181,26,181,24,53,129,207,24,24,129,129,24,26,205', +'283,24,26,26,181,24,26,202,178,111,26,26,178,178,26,181,24,26,145,181', +'181,145,114,181,191,26,48,181,181,29,29,181,172,29,181,29,172,172,46', +'128,145,187,181,285,285,297,182,182,285,285,182,29,182,87,132,285,300', +'270,270,270,270,37,29,133,270,270,29,29,182,306,29,270,264,139,29,29', +'264,264,29,182,32,29,143,182,182,23,318,182,320,29,146,182,182,183,183', +'182,150,183,182,183,152,137,271,271,271,271,182,15,336,271,271,137,137', +'286,286,183,271,155,286,286,137,137,137,137,159,286,183,343,9,160,183', +'183,5,355,183,356,161,2,183,183,162,366,183,137,137,183,368,137,137', +'137,137,137,137,183,137,137,299,299,,,299,137,299,,,,44,44,,,44,,44', +',,,295,295,299,,295,,295,28,28,28,28,,44,299,,,,299,299,,,299,295,44', +',299,299,44,44,299,,44,299,295,,44,44,295,295,44,299,295,44,,,295,295', +'290,290,295,44,,295,,290,,,47,47,,295,47,,47,,,,289,289,,,289,,289,', +',,184,184,47,290,184,,184,290,290,,,290,289,47,,290,,47,47,290,,47,184', +'289,,47,47,289,289,47,290,289,47,184,,289,289,184,184,289,47,184,289', +',,184,184,167,167,184,289,,184,167,,,,51,51,,184,51,,51,,,,52,52,,,52', +',52,,,,185,185,51,167,185,,185,167,167,,,167,52,51,,167,,51,51,167,', +'51,185,52,,51,51,52,52,51,167,52,51,185,,52,52,185,185,52,51,185,52', +',,185,185,186,186,185,52,186,185,186,,,,188,188,,185,188,,188,,,,98', +'98,186,,98,98,98,33,33,33,33,,188,186,,,,186,186,,,186,98,188,,186,186', +'188,188,186,,188,186,98,,188,188,98,98,188,186,98,188,,,98,98,190,190', +'98,188,190,98,190,,,,67,67,,98,67,34,34,34,34,,192,192,190,,192,,192', +'3,3,3,3,,,190,,,,190,190,,,190,192,67,,190,190,67,67,190,,67,190,192', +',67,,192,192,67,190,192,,,,192,192,193,193,192,67,193,192,193,,,,195', +'195,,192,195,,195,,,,,,193,,,,,,,,,,195,193,,,,193,193,,,193,,195,,193', +'193,195,195,193,,195,193,258,,195,195,,,195,193,,195,258,258,,,,,,195', +',258,258,258,258,,,,,,,,255,255,,,255,,255,,,,,,258,258,,,258,258,258', +'258,258,258,255,258,258,76,76,,,76,258,76,,255,,252,252,255,255,,,255', +',,,255,255,76,76,255,,,255,78,78,,,78,76,78,255,,76,76,,,76,,252,,76', +'76,252,252,76,78,252,76,240,240,252,,240,,252,76,78,,,,78,78,,,78,252', +',,78,78,,,78,196,196,78,,196,,196,,240,,78,,240,240,,,240,232,232,,240', +'232,196,232,240,,,,,,,,,196,,240,,196,196,232,,196,,,,196,196,,,196', +'232,,196,,232,232,,,232,,196,,232,232,225,225,232,,225,232,225,,,,85', +'85,,232,85,85,85,,,,224,224,225,,224,,224,,,,204,204,85,225,,,,225,225', +',,225,224,85,,225,225,85,85,225,,85,225,224,,85,85,224,224,85,225,224', +'85,204,,224,224,204,204,224,85,204,224,,,204,,219,219,204,224,219,,219', +',,,218,218,,204,218,,218,,,,103,103,219,,103,,103,,,,,,218,219,,,,219', +'219,,,219,103,218,,219,219,218,218,219,,218,219,103,,218,218,103,103', +'218,219,103,218,,,103,103,95,95,103,218,95,103,95,,,,96,96,,103,96,', +'96,,,,189,189,95,,189,,189,,,,,,96,95,,,,95,95,,,95,189,96,,95,95,96', +'96,95,,96,95,189,,96,96,189,189,96,95,189,96,,99,189,189,,,189,96,,189', +'99,99,,,,,104,189,,99,99,99,99,,,104,104,,,,,,,,104,104,104,104,,,,', +'99,99,,,99,99,99,99,99,99,,99,99,,,104,104,,99,104,104,104,104,104,104', +',104,104,308,308,,,,104,,,,308,308,308,308,,,344,344,,,,,,,,344,344', +'344,344,,,,,308,308,,,308,308,308,308,308,308,,308,308,,,344,344,,308', +'344,344,344,344,344,344,,344,344,339,339,,,,344,,,,339,339,339,339,', +',313,313,,,,,,,,313,313,313,313,,,,,339,339,,,339,339,339,339,339,339', +',339,339,,,313,313,,339,313,313,313,313,313,313,,313,313,347,347,,,', +'313,,,,347,347,347,347,,,176,176,,,,,,,176,176,176,176,176,,,,,347,347', +',,347,347,347,347,347,347,,347,347,,,176,176,,347,176,176,176,176,176', +'176,,176,176,117,117,,,,176,,,,117,117,117,117,,,273,273,,,,,,,,273', +'273,273,273,,,,,117,117,,,117,117,117,117,117,117,,117,117,,,,273,,117', +'273,273,273,273,273,273,,273,273,307,307,,,,273,,,,307,307,307,307,', +',305,305,,,,,,,,305,305,305,305,,,,,307,307,,,307,307,307,307,307,307', +',307,307,,,,,,307,305,305,305,305,305,305,,305,305,276,276,,,,305,,', +',276,276,276,276,,,304,304,,,,,,,,304,304,304,304,,,,,,,,,276,276,276', +'276,276,276,,276,276,,,,,,276,304,304,304,304,304,304,,304,304,130,130', +',,,304,,,,130,130,130,130,,,136,136,,,,,,,,136,136,136,136,,,,,130,130', +',,130,130,130,130,130,130,,130,130,,,136,136,,130,136,136,136,136,136', +'136,,136,136,135,135,,,,136,,,,135,135,135,135,,,,280,,,,,,,,280,,,', +',,,,135,135,274,,135,135,135,135,135,135,274,135,135,,,,,278,135,280', +'280,280,280,280,280,278,280,280,,,,,,280,,277,,274,274,274,274,274,274', +'277,274,274,,,,,,274,278,278,278,278,278,278,,278,278,,194,,,,278,,', +',277,277,277,277,277,277,,277,277,,362,,,194,277,194,194,,194,194,,194', +',194,,194,237,194,,,194,362,,362,362,,362,362,,362,,362,360,362,,362', +',237,362,237,237,,237,237,,237,,237,212,237,,237,,360,237,360,360,,360', +'360,,360,,360,350,360,,360,,212,360,212,212,,212,212,,212,,212,317,212', +',212,,350,212,350,350,,350,350,,350,,350,,350,,350,,317,350,317,317', +',317,317,,317,,317,,317,0,317,0,0,317,0,0,,0,,0,,0,17,0,17,17,0,17,17', +',17,,17,,17,282,17,282,282,17,282,282,,282,,282,,282,,282,,,282,281', +'281,281,281,281,281,,281,281,,,,,,281,272,272,272,272,272,272,,272,272', +',,,,,272' ] + racc_action_check = arr = Array.new(2074, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end racc_action_pointer = [ - 1952, 522, 1978, nil, nil, 1188, nil, 892, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 461, 441, - 466, 201, 462, 82, 129, 420, 283, 432, 304, 351, - 440, 40, 144, 19, nil, 15, nil, 62, nil, nil, - nil, nil, 202, 331, 512, 356, nil, 342, 339, 338, - 337, nil, 317, nil, 324, nil, 303, 297, nil, 2, - 196, nil, 805, nil, 815, 190, 171, 78, nil, nil, - 42, nil, 878, 150, 218, 112, 139, 996, 249, nil, - nil, nil, nil, 1046, 1071, 1096, 1121, nil, 1259, nil, - nil, 1146, 1171, 1196, 971, 1, 1274, nil, nil, 255, - 267, 271, -6, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 397, nil, 405, -9, nil, nil, - nil, 269, nil, 1514, 701, 676, 626, 651, nil, -4, - 446, nil, 559, 386, 417, 415, nil, -4, nil, 416, - 422, 426, nil, 443, nil, 463, nil, nil, nil, nil, - 481, 497, 469, 182, nil, nil, nil, 72, 141, nil, - nil, nil, nil, -17, 381, 1379, 1334, 936, 1394, 1923, - 71, 459, 451, 81, 91, 138, 158, 221, 284, 361, - 371, 428, 438, 448, 495, 601, 726, nil, nil, 1439, - 369, 46, nil, nil, 396, nil, nil, 743, 376, 362, - nil, 73, 266, 293, 42, nil, 168, 279, 244, 888, - 1021, nil, 222, 1221, nil, nil, 11, 5, nil, 448, - nil, nil, 898, 92, 419, 18, nil, nil, 868, 59, - nil, nil, nil, nil, nil, 83, 795, 108, 83, 97, - 139, 159, nil, 51, nil, nil, -14, 825, 752, nil, - nil, nil, nil, 1965, 234, -22, nil, nil, nil, 530, - 456, 244, 255, 915, 1997, 1988, 1973, 1787, 1772, 1754, - 1805, 1679, 1694, nil, nil, nil, 213, 576, 530, nil, - 7, nil, nil, 1841, 347, 22, nil, 367, 374, 389, - nil, 132, 1907, nil, nil, nil, nil, 1739, 1634, nil, - 1319, nil, nil, nil, nil, nil, 338, nil, 381, nil, - nil, nil, nil, 425, nil, 429, nil, nil, 1619, 1574, - 187, nil, 294, 440, nil, nil, 1559, nil, nil, 453, - 274, 456, nil, nil, nil, 114, 231, 211, 1859, nil, - nil, nil, 471, nil, 475, 148, 485, nil, nil, nil, - nil, nil, 1939, 1891, nil, nil, nil, 1499, 1454, 1875, - nil, nil, nil, nil, 519, nil, 520, nil, nil, nil, - nil ] + 1970, 72, 409, 724, nil, 433, -3, nil, 124, 436, + nil, nil, 58, 171, nil, 423, nil, 1983, 82, nil, + nil, nil, nil, 393, 270, nil, 279, nil, 436, 343, + nil, nil, 394, 657, 712, nil, nil, 353, nil, nil, + 124, nil, nil, nil, 480, 127, 346, 547, 302, 76, + 31, 624, 634, 266, 277, 287, nil, 288, nil, 3, + nil, nil, nil, nil, nil, nil, nil, 768, 263, 254, + 220, nil, 216, nil, nil, 193, 933, 185, 961, 180, + nil, 170, 135, 21, nil, 1085, nil, 346, 2, nil, + 215, 216, nil, nil, 220, 1219, 1229, nil, 711, 1277, + nil, 27, nil, 1172, 1292, nil, nil, 237, 121, 57, + nil, 317, nil, nil, 332, nil, nil, 1517, nil, nil, + 110, nil, 234, nil, nil, nil, nil, 57, 315, 300, + 1697, nil, 359, 367, -18, 1757, 1712, 410, nil, 377, + nil, nil, -9, 361, nil, 294, 398, 86, nil, nil, + 392, nil, 393, nil, nil, 427, nil, nil, 78, 417, + 404, 429, 433, nil, 9, nil, 16, 614, nil, -4, + 56, 84, 341, nil, 130, 150, 1472, nil, 317, 197, + 207, 296, 360, 407, 567, 644, 691, 295, 701, 1239, + 758, 278, 778, 825, 1859, 835, 1011, nil, nil, 205, + nil, nil, 312, nil, 1105, 313, nil, 293, 250, 251, + nil, 238, 1925, 23, nil, nil, nil, 41, 1162, 1152, + 137, nil, nil, nil, 1095, 1075, 68, 80, nil, nil, + 128, 101, 1028, 123, nil, 164, nil, 1893, nil, 142, + 986, 179, 185, 170, nil, nil, nil, 185, nil, nil, + 182, nil, 943, nil, nil, 908, nil, nil, 873, nil, + 217, 227, nil, -5, 381, nil, nil, 167, 54, nil, + 320, 365, 2006, 1532, 1790, nil, 1637, 1823, 1805, nil, + 1772, 1991, 1996, 307, 266, 304, 374, 7, nil, 557, + 537, nil, -1, nil, nil, 490, nil, 336, -23, 470, + 363, nil, nil, nil, 1652, 1592, 376, 1577, 1337, nil, + nil, nil, nil, 1412, nil, nil, nil, 1957, 393, nil, + 392, nil, nil, nil, nil, 227, nil, 49, nil, nil, + nil, nil, nil, 4, 217, nil, 416, nil, nil, 1397, + nil, nil, 140, 435, 1352, nil, nil, 1457, nil, nil, + 1941, nil, nil, nil, nil, 440, 442, 66, nil, nil, + 1909, nil, 1877, nil, nil, nil, 448, nil, 453, nil, + nil, nil ] racc_action_default = [ - -180, -217, -1, -2, -3, -6, -7, -8, -9, -10, - -11, -12, -13, -14, -15, -16, -17, -18, -19, -217, - -23, -171, -217, -217, -217, -53, -217, -217, -217, -217, - -217, -217, -172, -217, -170, -217, -4, -217, -25, -26, - -27, -28, -217, -95, -217, -32, -33, -37, -38, -39, - -40, -41, -42, -43, -44, -45, -46, -47, -75, -76, - -77, -100, -217, -105, -217, -217, -217, -215, -158, -159, - -197, -205, -88, -88, -56, -180, -180, -217, -217, -52, - -171, -172, -54, -217, -217, -217, -217, -108, -217, -115, - -116, -217, -217, -217, -217, -217, -217, -160, -161, -163, - -180, -180, -180, -173, -175, -176, -177, -178, -179, 371, - -21, -22, -23, -24, -171, -20, -203, -217, -93, -94, - -96, -217, -31, -35, -217, -217, -217, -217, -101, -217, - -217, -200, -217, -73, -203, -217, -71, -76, -77, -78, - -79, -80, -81, -82, -83, -84, -89, -154, -155, -156, - -203, -217, -95, -217, -59, -60, -62, -180, -217, -68, - -69, -76, -197, -217, -217, -85, -87, -217, -86, -217, + -180, -217, -53, -217, -9, -217, -217, -10, -217, -23, + -11, -170, -172, -217, -12, -217, -13, -1, -217, -14, + -2, -15, -3, -217, -171, -16, -217, -17, -6, -217, + -18, -7, -217, -19, -8, -172, -171, -180, -54, -28, + -217, -25, -26, -27, -217, -180, -56, -217, -88, -180, + -217, -217, -217, -95, -180, -217, -52, -217, -4, -180, + -177, -173, -175, -205, -176, -179, -178, -88, -40, -215, + -217, -41, -197, -100, -45, -42, -217, -217, -217, -77, + -43, -44, -32, -46, -33, -217, -105, -47, -76, -75, + -37, -38, -159, -158, -39, -217, -217, -115, -217, -217, + -108, -217, -116, -217, -217, -160, -161, -163, -217, -181, + -182, -217, -20, -23, -171, -22, -24, -86, -197, -68, + -180, -59, -217, -60, -76, -62, -69, -217, -95, -217, + -35, -89, -217, -203, -217, -85, -87, -217, -93, -203, + -94, -96, -217, -180, 372, -217, -217, -217, -191, -155, + -217, -81, -82, -83, -77, -84, -154, -156, -76, -78, + -73, -79, -80, -71, -203, -31, -217, -217, -101, -217, + -217, -217, -217, -200, -217, -217, -217, -133, -217, -217, -217, -217, -217, -217, -217, -217, -217, -217, -217, -217, - -217, -217, -217, -217, -217, -217, -217, -126, -133, -217, - -217, -217, -208, -209, -217, -212, -213, -217, -217, -217, - -182, -181, -180, -217, -217, -191, -217, -217, -204, -217, - -217, -29, -202, -217, -201, -34, -217, -217, -99, -217, - -102, -103, -217, -198, -202, -76, -144, -145, -217, -217, - -150, -151, -152, -153, -157, -217, -74, -217, -204, -88, - -217, -203, -57, -217, -64, -65, -217, -217, -217, -58, - -107, -202, -214, -5, -217, -111, -117, -118, -119, -120, - -121, -122, -123, -124, -125, -127, -128, -129, -130, -131, - -132, -134, -135, -136, -198, -206, -217, -217, -217, -138, - -217, -142, -162, -217, -217, -217, -185, -188, -190, -217, - -193, -217, -217, -174, -192, -51, -97, -91, -92, -30, - -36, -216, -98, -106, -104, -199, -203, -147, -217, -48, - -72, -49, -90, -203, -50, -217, -61, -63, -66, -67, - -111, -110, -217, -217, -207, -210, -211, -137, -139, -217, - -217, -217, -165, -183, -184, -217, -217, -217, -217, -194, - -195, -196, -217, -169, -217, -217, -202, -149, -70, -55, - -109, -112, -217, -217, -143, -164, -186, -187, -189, -217, - -167, -168, -146, -148, -217, -114, -217, -141, -166, -113, - -140 ] + -217, -217, -217, -217, -217, -217, -217, -212, -213, -217, + -209, -208, -217, -126, -217, -217, -21, -190, -217, -203, + -185, -188, -217, -217, -57, -65, -64, -217, -217, -217, + -203, -201, -202, -107, -217, -217, -217, -204, -58, -214, + -217, -204, -217, -217, -193, -217, -174, -217, -192, -88, + -74, -217, -217, -202, -29, -157, -153, -217, -144, -145, + -76, -152, -217, -150, -151, -217, -103, -102, -217, -34, + -202, -198, -99, -217, -217, -136, -198, -123, -122, -117, + -124, -125, -128, -135, -130, -118, -134, -132, -129, -119, + -131, -127, -5, -217, -111, -120, -121, -217, -206, -217, + -217, -138, -217, -142, -162, -217, -183, -217, -204, -217, + -217, -165, -63, -61, -66, -67, -217, -36, -91, -50, + -90, -51, -97, -92, -195, -194, -196, -217, -217, -169, + -203, -72, -48, -49, -30, -217, -147, -203, -104, -216, + -199, -98, -106, -111, -217, -110, -217, -210, -207, -211, + -139, -137, -217, -217, -189, -184, -186, -187, -164, -55, + -217, -167, -168, -70, -149, -217, -202, -217, -109, -112, + -217, -143, -217, -166, -146, -148, -217, -114, -217, -141, + -113, -140 ] racc_goto_table = [ - 23, 36, 23, 27, 2, 27, 46, 87, 20, 193, - 20, 139, 149, 103, 148, 140, 108, 98, 160, 160, - 100, 101, 227, 286, 78, 3, 279, 229, 145, 146, - 146, 128, 134, 150, 196, 116, 207, 23, 321, 110, - 153, 163, 23, 113, 115, 112, 121, 133, 113, 79, - 112, 278, 136, 220, 237, 235, 88, 96, 37, 42, - 254, 226, 111, 296, 142, 144, 130, 111, 199, 202, - 240, 232, 149, 306, 148, 230, 345, 99, 316, 164, - 289, 102, 203, 74, 294, 284, 285, 45, 339, 191, - 129, 1, 281, nil, nil, nil, 190, nil, nil, nil, - 160, nil, nil, 350, nil, nil, nil, 328, nil, 215, - nil, 165, 166, 167, 168, nil, nil, nil, 307, 187, - 188, 189, 246, 229, nil, 200, 200, 205, 217, 219, - nil, nil, nil, 147, 141, 234, 232, 149, nil, 148, - 230, nil, nil, nil, 241, nil, nil, nil, nil, nil, - nil, nil, nil, 216, 257, 258, nil, 356, nil, nil, - nil, 315, nil, nil, nil, nil, nil, 232, 149, 23, - 148, 230, 27, 281, 331, 139, 149, 20, 148, 140, - nil, nil, nil, 342, 293, 282, 160, 108, nil, 108, - 325, 304, 145, 147, 231, 312, 146, nil, 256, 313, - 234, 259, 260, 261, 262, 263, 264, 265, 266, 267, - 268, 269, 270, 271, 272, 196, 310, 232, 149, 143, - 148, 230, 159, 159, nil, 354, 344, 290, 142, 144, - nil, 234, nil, 348, nil, 363, nil, 297, 298, nil, - 229, 300, nil, 364, 366, nil, nil, nil, nil, nil, - 129, nil, 36, 23, nil, nil, 27, nil, 147, 231, - nil, 20, nil, nil, nil, nil, nil, nil, nil, 232, - 149, nil, 148, 230, nil, 318, 319, nil, nil, 233, - 340, 234, nil, 23, 232, 149, 27, 148, 230, 147, - 231, 20, 23, 347, nil, 27, nil, 147, 141, nil, - 20, 351, nil, nil, 159, 326, nil, nil, nil, nil, + 6, 5, 58, 17, 9, 100, 84, 119, 133, 138, + 249, 119, 126, 55, 37, 210, 126, 6, 5, 168, + 106, 9, 129, 283, 162, 54, 20, 164, 151, 152, + 163, 61, 291, 157, 161, 149, 139, 156, 153, 226, + 6, 300, 247, 200, 113, 230, 122, 56, 155, 116, + 134, 166, 112, 335, 111, 159, 40, 248, 290, 257, + 172, 65, 241, 110, 160, 312, 318, 327, 357, 107, + 242, 143, 82, 178, 115, 235, 59, 146, 303, 293, + 110, 238, 119, 208, 138, 148, 108, 126, 209, 198, + 46, 315, 199, 15, nil, 326, nil, nil, nil, nil, + nil, nil, 358, nil, nil, nil, nil, nil, 6, nil, + nil, 220, 113, nil, nil, 297, nil, 116, 340, nil, + 206, 213, nil, nil, 254, nil, 306, 247, nil, 246, + nil, nil, nil, 157, 253, 149, nil, 156, nil, nil, + nil, nil, 115, nil, nil, nil, nil, nil, nil, 263, + 264, nil, nil, 259, nil, 251, nil, nil, 236, nil, + nil, 254, nil, nil, nil, 293, 246, nil, 245, 234, + 157, 253, 149, nil, 156, nil, nil, nil, nil, 119, + nil, nil, nil, 310, 126, nil, nil, 138, 65, 366, + 65, 368, 251, 294, 6, 5, 328, 162, 9, 320, + 365, 151, 152, 321, 346, 245, 157, 161, 149, 254, + 156, 153, 6, 5, 246, nil, 9, 361, 157, 253, + 149, 155, 156, nil, 99, nil, 353, 104, 159, 337, + 275, nil, 247, 355, 279, nil, nil, 6, 5, nil, + 251, 9, 117, nil, nil, nil, 314, 254, 135, 136, + 137, nil, 246, 245, nil, nil, 157, 253, 149, nil, + 156, nil, nil, nil, nil, nil, nil, 58, nil, nil, + nil, nil, nil, nil, nil, 198, 169, nil, 251, nil, + nil, nil, 6, 5, nil, nil, 9, nil, nil, nil, + nil, 245, nil, 176, 177, nil, nil, nil, nil, 254, + nil, 203, nil, nil, 246, nil, nil, 354, 157, 253, + 149, nil, 156, 359, 254, nil, nil, 6, 5, 246, + 350, 9, nil, 157, 253, 149, nil, 156, nil, nil, + 251, nil, nil, nil, nil, 58, nil, nil, nil, nil, + nil, nil, nil, 245, nil, 251, nil, nil, nil, nil, + 6, 5, nil, nil, 9, nil, nil, nil, 245, nil, + 6, 5, 6, 5, 9, nil, 9, nil, 258, nil, + nil, nil, nil, nil, nil, nil, nil, 267, 268, 269, + 270, 271, 272, 273, 274, nil, 276, 277, 278, nil, + 280, 281, nil, 285, 286, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 304, 305, nil, nil, + nil, nil, 307, 308, nil, nil, nil, nil, nil, nil, + 313, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 234, nil, nil, nil, nil, 23, 147, - 231, 27, 359, nil, 233, nil, 20, nil, 234, nil, - 88, nil, 23, 23, nil, 27, 27, nil, 36, 23, - 20, 20, 27, nil, 357, 358, nil, 20, nil, nil, - nil, nil, nil, nil, nil, 233, nil, nil, nil, nil, - nil, nil, nil, 143, nil, nil, nil, nil, nil, nil, - 159, 147, 231, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 147, 231, nil, nil, + nil, nil, nil, 169, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 233, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 339, nil, nil, + nil, nil, nil, 344, nil, nil, nil, 347, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 233, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 233 ] + nil, nil, 99 ] racc_goto_check = [ - 43, 4, 43, 54, 2, 54, 25, 60, 22, 84, - 22, 28, 37, 77, 31, 29, 63, 71, 29, 29, - 38, 38, 69, 81, 43, 3, 65, 67, 35, 55, - 55, 58, 41, 41, 28, 44, 42, 43, 61, 20, - 47, 47, 43, 8, 20, 22, 23, 39, 8, 6, - 22, 64, 52, 59, 42, 40, 26, 26, 21, 21, - 5, 68, 6, 57, 32, 34, 23, 6, 73, 73, - 42, 28, 37, 70, 31, 29, 27, 72, 48, 23, - 74, 75, 76, 46, 78, 79, 80, 24, 82, 83, - 26, 1, 67, nil, nil, nil, 23, nil, nil, nil, - 29, nil, nil, 61, nil, nil, nil, 65, nil, 25, - nil, 26, 26, 26, 26, nil, nil, nil, 69, 26, - 26, 26, 47, 67, nil, 3, 3, 3, 23, 23, - nil, nil, nil, 36, 30, 54, 28, 37, nil, 31, - 29, nil, nil, nil, 44, nil, nil, nil, nil, nil, - nil, nil, nil, 26, 63, 63, nil, 81, nil, nil, - nil, 42, nil, nil, nil, nil, nil, 28, 37, 43, - 31, 29, 54, 67, 5, 28, 37, 22, 31, 29, - nil, nil, nil, 5, 77, 71, 29, 63, nil, 63, - 84, 58, 35, 36, 30, 55, 55, nil, 26, 41, - 54, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 28, 52, 28, 37, 33, - 31, 29, 33, 33, nil, 67, 42, 3, 32, 34, - nil, 54, nil, 42, nil, 69, nil, 26, 26, nil, - 67, 26, nil, 5, 5, nil, nil, nil, nil, nil, - 26, nil, 4, 43, nil, nil, 54, nil, 36, 30, - nil, 22, nil, nil, nil, nil, nil, nil, nil, 28, - 37, nil, 31, 29, nil, 26, 26, nil, nil, 33, - 38, 54, nil, 43, 28, 37, 54, 31, 29, 36, - 30, 22, 43, 25, nil, 54, nil, 36, 30, nil, - 22, 60, nil, nil, 33, 26, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 43, 54, 4, 2, 22, 60, 25, 33, 41, 55, + 69, 33, 29, 43, 38, 81, 29, 43, 54, 58, + 71, 22, 23, 5, 30, 38, 3, 41, 32, 33, + 52, 77, 65, 37, 29, 31, 44, 36, 34, 42, + 43, 5, 67, 84, 22, 42, 47, 6, 35, 8, + 47, 23, 20, 61, 73, 28, 21, 68, 64, 59, + 23, 63, 40, 3, 39, 57, 5, 70, 27, 72, + 42, 73, 24, 23, 6, 74, 75, 76, 48, 67, + 3, 78, 33, 79, 55, 3, 21, 29, 80, 28, + 46, 82, 83, 1, nil, 69, nil, nil, nil, nil, + nil, nil, 61, nil, nil, nil, nil, nil, 43, nil, + nil, 44, 22, nil, nil, 42, nil, 8, 65, nil, + 20, 47, nil, nil, 30, nil, 42, 67, nil, 33, + nil, nil, nil, 37, 29, 31, nil, 36, nil, nil, + nil, nil, 6, nil, nil, nil, nil, nil, nil, 23, + 23, nil, nil, 25, nil, 28, nil, nil, 77, nil, + nil, 30, nil, nil, nil, 67, 33, nil, 54, 3, + 37, 29, 31, nil, 36, nil, nil, nil, nil, 33, + nil, nil, nil, 55, 29, nil, nil, 55, 63, 5, + 63, 5, 28, 71, 43, 54, 58, 30, 22, 41, + 69, 32, 33, 52, 81, 54, 37, 29, 31, 30, + 36, 34, 43, 54, 33, nil, 22, 67, 37, 29, + 31, 35, 36, nil, 26, nil, 42, 26, 28, 84, + 63, nil, 67, 42, 63, nil, nil, 43, 54, nil, + 28, 22, 26, nil, nil, nil, 38, 30, 26, 26, + 26, nil, 33, 54, nil, nil, 37, 29, 31, nil, + 36, nil, nil, nil, nil, nil, nil, 4, nil, nil, + nil, nil, nil, nil, nil, 28, 26, nil, 28, nil, + nil, nil, 43, 54, nil, nil, 22, nil, nil, nil, + nil, 54, nil, 26, 26, nil, nil, nil, nil, 30, + nil, 26, nil, nil, 33, nil, nil, 25, 37, 29, + 31, nil, 36, 60, 30, nil, nil, 43, 54, 33, + 2, 22, nil, 37, 29, 31, nil, 36, nil, nil, + 28, nil, nil, nil, nil, 4, nil, nil, nil, nil, + nil, nil, nil, 54, nil, 28, nil, nil, nil, nil, + 43, 54, nil, nil, 22, nil, nil, nil, 54, nil, + 43, 54, 43, 54, 22, nil, 22, nil, 26, nil, + nil, nil, nil, nil, nil, nil, nil, 26, 26, 26, + 26, 26, 26, 26, 26, nil, 26, 26, 26, nil, + 26, 26, nil, 26, 26, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 54, nil, nil, nil, nil, 43, 36, - 30, 54, 2, nil, 33, nil, 22, nil, 54, nil, - 26, nil, 43, 43, nil, 54, 54, nil, 4, 43, - 22, 22, 54, nil, 26, 26, nil, 22, nil, nil, - nil, nil, nil, nil, nil, 33, nil, nil, nil, nil, - nil, nil, nil, 33, nil, nil, nil, nil, nil, nil, - 33, 36, 30, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 36, 30, nil, nil, + nil, nil, nil, nil, nil, nil, 26, 26, nil, nil, + nil, nil, 26, 26, nil, nil, nil, nil, nil, nil, + 26, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 33, nil, nil, nil, nil, + nil, nil, nil, 26, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 26, nil, nil, + nil, nil, nil, 26, nil, nil, nil, 26, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 33, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 33 ] + nil, nil, 26 ] racc_goto_pointer = [ - nil, 91, 4, 25, -1, -109, 25, nil, 6, nil, + nil, 93, 3, 26, -15, -171, 34, nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 2, 40, 8, 2, 66, -15, 28, -230, -61, -57, - 62, -58, -8, 147, -7, -44, 61, -60, -11, -25, - -78, -40, -80, 0, -8, nil, 60, -35, -165, nil, - nil, nil, -20, nil, 3, -43, nil, -145, -31, -76, - -21, -217, nil, -17, -146, -171, nil, -105, -71, -110, - -155, -13, 47, -32, -122, 48, -20, -20, -122, -116, - -115, -178, -203, -6, -86, nil ] + 12, 53, 4, -25, 48, -18, 198, -259, -12, -33, + -43, -32, -39, -38, -29, -19, -30, -34, 13, -3, + -98, -40, -94, 0, -17, nil, 84, 1, -139, nil, + nil, nil, -37, nil, 1, -44, nil, -166, -59, -110, + -21, -231, nil, 43, -146, -172, nil, -125, -110, -157, + -185, -12, 37, 17, -68, 58, 18, 13, -66, -26, + -21, -94, -142, -9, -58, nil ] racc_goto_default = [ - nil, nil, 253, 154, 4, nil, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, - 19, nil, 55, nil, nil, 89, 123, 213, 47, 48, - 49, 50, 51, 52, 53, 54, 56, 57, 22, nil, - nil, nil, nil, 65, nil, 24, nil, nil, 155, 243, - 156, 158, nil, 135, 67, 118, 119, 120, nil, nil, - nil, nil, 90, 69, nil, nil, 280, 66, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 104, nil, - nil, nil, nil, nil, nil, 194 ] + nil, nil, 282, 121, 22, nil, 28, 31, 34, 4, + 7, 10, 14, 16, 19, 21, 25, 27, 30, 33, + 3, nil, 74, nil, nil, 97, 130, 224, 90, 91, + 94, 68, 71, 75, 80, 81, 83, 87, 23, nil, + nil, nil, nil, 70, nil, 13, nil, nil, 123, 217, + 125, 127, nil, 150, 69, 131, 140, 141, nil, nil, + nil, nil, 102, 92, nil, nil, 292, 77, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 62, nil, + nil, nil, nil, nil, nil, 202 ] racc_reduce_table = [ 0, 0, :racc_error, 1, 69, :_reduce_none, 1, 69, :_reduce_none, 1, 70, :_reduce_3, 2, 70, :_reduce_4, 1, 73, :_reduce_5, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 1, 72, :_reduce_none, 3, 87, :_reduce_20, 3, 87, :_reduce_21, 1, 88, :_reduce_none, 1, 88, :_reduce_none, 1, 88, :_reduce_none, 1, 89, :_reduce_none, 1, 89, :_reduce_none, 1, 89, :_reduce_none, 1, 89, :_reduce_none, 4, 81, :_reduce_29, 5, 81, :_reduce_30, 3, 81, :_reduce_31, 2, 81, :_reduce_32, 1, 92, :_reduce_33, 3, 92, :_reduce_34, 1, 91, :_reduce_35, 3, 91, :_reduce_36, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 5, 74, :_reduce_48, 5, 74, :_reduce_49, 5, 74, :_reduce_50, 5, 85, :_reduce_51, 2, 75, :_reduce_52, 1, 113, :_reduce_53, 2, 113, :_reduce_54, 6, 76, :_reduce_55, 2, 76, :_reduce_56, 3, 114, :_reduce_57, 3, 114, :_reduce_58, 1, 115, :_reduce_none, 1, 115, :_reduce_none, 3, 115, :_reduce_61, 1, 116, :_reduce_none, 3, 116, :_reduce_63, 1, 117, :_reduce_64, 1, 117, :_reduce_65, 3, 118, :_reduce_66, 3, 118, :_reduce_67, 1, 119, :_reduce_none, 1, 119, :_reduce_none, 4, 120, :_reduce_70, 1, 107, :_reduce_71, 3, 107, :_reduce_72, 0, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 105, :_reduce_75, 1, 97, :_reduce_76, 1, 98, :_reduce_77, 1, 121, :_reduce_none, 1, 121, :_reduce_none, 1, 121, :_reduce_none, 1, 121, :_reduce_none, 1, 121, :_reduce_none, 1, 121, :_reduce_none, 1, 121, :_reduce_none, 3, 77, :_reduce_85, 3, 77, :_reduce_86, 3, 86, :_reduce_87, 0, 109, :_reduce_88, 1, 109, :_reduce_89, 3, 109, :_reduce_90, 3, 123, :_reduce_91, 3, 124, :_reduce_92, 1, 125, :_reduce_none, 1, 125, :_reduce_none, 0, 112, :_reduce_95, 1, 112, :_reduce_96, 3, 112, :_reduce_97, 4, 104, :_reduce_98, 3, 104, :_reduce_99, 1, 96, :_reduce_100, 2, 96, :_reduce_101, 2, 126, :_reduce_102, 1, 127, :_reduce_103, 2, 127, :_reduce_104, 1, 99, :_reduce_105, 4, 90, :_reduce_106, 4, 90, :_reduce_107, 2, 79, :_reduce_108, 5, 128, :_reduce_109, 4, 128, :_reduce_110, 0, 129, :_reduce_none, 2, 129, :_reduce_112, 4, 129, :_reduce_113, 3, 129, :_reduce_114, 1, 94, :_reduce_none, 1, 94, :_reduce_none, 3, 94, :_reduce_117, 3, 94, :_reduce_118, 3, 94, :_reduce_119, 3, 94, :_reduce_120, 3, 94, :_reduce_121, 3, 94, :_reduce_122, 3, 94, :_reduce_123, 3, 94, :_reduce_124, 3, 94, :_reduce_125, 2, 94, :_reduce_126, 3, 94, :_reduce_127, 3, 94, :_reduce_128, 3, 94, :_reduce_129, 3, 94, :_reduce_130, 3, 94, :_reduce_131, 3, 94, :_reduce_132, 2, 94, :_reduce_133, 3, 94, :_reduce_134, 3, 94, :_reduce_135, 3, 94, :_reduce_136, 5, 78, :_reduce_137, 1, 132, :_reduce_138, 2, 132, :_reduce_139, 5, 133, :_reduce_140, 4, 133, :_reduce_141, 1, 134, :_reduce_142, 3, 134, :_reduce_143, 3, 100, :_reduce_144, 1, 136, :_reduce_none, 4, 136, :_reduce_146, 1, 138, :_reduce_none, 3, 138, :_reduce_148, 3, 137, :_reduce_149, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_none, 1, 135, :_reduce_158, 1, 135, :_reduce_none, 1, 139, :_reduce_160, 1, 140, :_reduce_none, 3, 140, :_reduce_162, 2, 80, :_reduce_163, 6, 82, :_reduce_164, 5, 82, :_reduce_165, 7, 83, :_reduce_166, 6, 83, :_reduce_167, 6, 84, :_reduce_168, 5, 84, :_reduce_169, 1, 111, :_reduce_170, 1, 106, :_reduce_171, 1, 106, :_reduce_172, 1, 143, :_reduce_173, 3, 143, :_reduce_174, 1, 145, :_reduce_175, 1, 146, :_reduce_176, 1, 146, :_reduce_177, 1, 146, :_reduce_178, 1, 146, :_reduce_none, 0, 71, :_reduce_180, 0, 147, :_reduce_181, 1, 141, :_reduce_none, 3, 141, :_reduce_183, - 3, 141, :_reduce_184, + 4, 141, :_reduce_184, 1, 148, :_reduce_none, 3, 148, :_reduce_186, 3, 149, :_reduce_187, 1, 149, :_reduce_188, 3, 149, :_reduce_189, 1, 149, :_reduce_190, 1, 144, :_reduce_none, 2, 144, :_reduce_192, 1, 142, :_reduce_none, 2, 142, :_reduce_194, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 101, :_reduce_197, 3, 102, :_reduce_198, 4, 102, :_reduce_199, 2, 102, :_reduce_200, 1, 95, :_reduce_none, 1, 95, :_reduce_none, 0, 110, :_reduce_none, 1, 110, :_reduce_204, 1, 131, :_reduce_205, 3, 130, :_reduce_206, 4, 130, :_reduce_207, 2, 130, :_reduce_208, 1, 151, :_reduce_none, 3, 151, :_reduce_210, 3, 152, :_reduce_211, 1, 153, :_reduce_212, 1, 153, :_reduce_213, 4, 122, :_reduce_214, 1, 103, :_reduce_none, 4, 103, :_reduce_216 ] racc_reduce_n = 217 -racc_shift_n = 371 +racc_shift_n = 372 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :LBRACK => 6, :RBRACK => 7, :LBRACE => 8, :RBRACE => 9, :SYMBOL => 10, :FARROW => 11, :COMMA => 12, :TRUE => 13, :FALSE => 14, :EQUALS => 15, :APPENDS => 16, :LESSEQUAL => 17, :NOTEQUAL => 18, :DOT => 19, :COLON => 20, :LLCOLLECT => 21, :RRCOLLECT => 22, :QMARK => 23, :LPAREN => 24, :RPAREN => 25, :ISEQUAL => 26, :GREATEREQUAL => 27, :GREATERTHAN => 28, :LESSTHAN => 29, :IF => 30, :ELSE => 31, :IMPORT => 32, :DEFINE => 33, :ELSIF => 34, :VARIABLE => 35, :CLASS => 36, :INHERITS => 37, :NODE => 38, :BOOLEAN => 39, :NAME => 40, :SEMIC => 41, :CASE => 42, :DEFAULT => 43, :AT => 44, :LCOLLECT => 45, :RCOLLECT => 46, :CLASSREF => 47, :NOT => 48, :OR => 49, :AND => 50, :UNDEF => 51, :PARROW => 52, :PLUS => 53, :MINUS => 54, :TIMES => 55, :DIV => 56, :LSHIFT => 57, :RSHIFT => 58, :UMINUS => 59, :MATCH => 60, :NOMATCH => 61, :REGEX => 62, :IN_EDGE => 63, :OUT_EDGE => 64, :IN_EDGE_SUB => 65, :OUT_EDGE_SUB => 66, :IN => 67 } racc_nt_base = 68 racc_use_result_var = true 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", "STRING", "DQPRE", "DQMID", "DQPOST", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "IMPORT", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "LCOLLECT", "RCOLLECT", "CLASSREF", "NOT", "OR", "AND", "UNDEF", "PARROW", "PLUS", "MINUS", "TIMES", "DIV", "LSHIFT", "RSHIFT", "UMINUS", "MATCH", "NOMATCH", "REGEX", "IN_EDGE", "OUT_EDGE", "IN_EDGE_SUB", "OUT_EDGE_SUB", "IN", "$start", "program", "statements_and_declarations", "nil", "statement_or_declaration", "statements", "resource", "virtualresource", "collection", "assignment", "casestatement", "ifstatement_begin", "import", "fstatement", "definition", "hostclass", "nodedef", "resourceoverride", "append", "relationship", "relationship_side", "edge", "resourceref", "expressions", "funcvalues", "rvalue", "expression", "comma", "quotedtext", "name", "type", "boolean", "selector", "variable", "array", "hasharrayaccesses", "funcrvalue", "undef", "classname", "resourceinstances", "endsemi", "params", "endcomma", "classref", "anyparams", "at", "collectrhand", "collstatements", "collstatement", "colljoin", "collexpr", "colllval", "resourceinst", "resourcename", "hasharrayaccess", "param", "addparam", "anyparam", "dqrval", "dqtail", "ifstatement", "else", "hash", "regex", "caseopts", "caseopt", "casevalues", "selectlhand", "svalues", "selectval", "sintvalues", "string", "strings", "argumentlist", "classparent", "hostnames", "nodeparent", "nodename", "hostname", "nothing", "arguments", "argument", "classnameordefault", "hashpairs", "hashpair", "key" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted # reduce 1 omitted # reduce 2 omitted module_eval(<<'.,.,', 'grammar.ra', 34) def _reduce_3(val, _values, result) result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : []) result end .,., module_eval(<<'.,.,', 'grammar.ra', 37) def _reduce_4(val, _values, result) if val[1] val[0].push(val[1]) end result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 46) def _reduce_5(val, _values, result) val[0].each do |stmt| if stmt.is_a?(AST::TopLevelConstruct) error "Classes, definitions, and nodes may only appear at toplevel or inside other classes", \ :line => stmt.context[:line], :file => stmt.context[:file] end end result = val[0] result end .,., # reduce 6 omitted # reduce 7 omitted # reduce 8 omitted # reduce 9 omitted # reduce 10 omitted # reduce 11 omitted # reduce 12 omitted # reduce 13 omitted # reduce 14 omitted # reduce 15 omitted # reduce 16 omitted # reduce 17 omitted # reduce 18 omitted # reduce 19 omitted module_eval(<<'.,.,', 'grammar.ra', 72) def _reduce_20(val, _values, result) result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) result end .,., module_eval(<<'.,.,', 'grammar.ra', 75) def _reduce_21(val, _values, result) result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) result end .,., # reduce 22 omitted # reduce 23 omitted # reduce 24 omitted # reduce 25 omitted # reduce 26 omitted # reduce 27 omitted # reduce 28 omitted module_eval(<<'.,.,', 'grammar.ra', 83) def _reduce_29(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 90) def _reduce_30(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 96) def _reduce_31(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 103) def _reduce_32(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[1], :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 110) def _reduce_33(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 113) def _reduce_34(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 117) def _reduce_35(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 118) def _reduce_36(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 37 omitted # reduce 38 omitted # reduce 39 omitted # reduce 40 omitted # reduce 41 omitted # reduce 42 omitted # reduce 43 omitted # reduce 44 omitted # reduce 45 omitted # reduce 46 omitted # reduce 47 omitted module_eval(<<'.,.,', 'grammar.ra', 133) def _reduce_48(val, _values, result) @lexer.commentpop result = ast(AST::Resource, :type => val[0], :instances => val[2]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 136) def _reduce_49(val, _values, result) # This is a deprecated syntax. error "All resource specifications require names" result end .,., module_eval(<<'.,.,', 'grammar.ra', 139) def _reduce_50(val, _values, result) # a defaults setting for a type @lexer.commentpop result = ast(AST::ResourceDefaults, :type => val[0], :parameters => val[2]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 146) def _reduce_51(val, _values, result) @lexer.commentpop result = ast AST::ResourceOverride, :object => val[0], :parameters => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 153) def _reduce_52(val, _values, result) type = val[0] if (type == :exported and ! Puppet[:storeconfigs]) Puppet.warning addcontext("You cannot collect without storeconfigs being set") end error "Defaults are not virtualizable" if val[1].is_a? AST::ResourceDefaults method = type.to_s + "=" # Just mark our resource as exported and pass it through. val[1].send(method, true) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 169) def _reduce_53(val, _values, result) result = :virtual result end .,., module_eval(<<'.,.,', 'grammar.ra', 170) def _reduce_54(val, _values, result) result = :exported result end .,., module_eval(<<'.,.,', 'grammar.ra', 175) def _reduce_55(val, _values, result) @lexer.commentpop Puppet.warning addcontext("Collection names must now be capitalized") if val[0] =~ /^[a-z]/ type = val[0].downcase args = {:type => type} if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end args[:override] = val[3] result = ast AST::Collection, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 194) def _reduce_56(val, _values, result) if val[0] =~ /^[a-z]/ Puppet.warning addcontext("Collection names must now be capitalized") end type = val[0].downcase args = {:type => type } if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 215) def _reduce_57(val, _values, result) if val[1] result = val[1] result.form = :virtual else result = :virtual end result end .,., module_eval(<<'.,.,', 'grammar.ra', 223) def _reduce_58(val, _values, result) if val[1] result = val[1] result.form = :exported else result = :exported end result end .,., # reduce 59 omitted # reduce 60 omitted module_eval(<<'.,.,', 'grammar.ra', 236) def _reduce_61(val, _values, result) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] result end .,., # reduce 62 omitted module_eval(<<'.,.,', 'grammar.ra', 241) def _reduce_63(val, _values, result) result = val[1] result.parens = true result end .,., module_eval(<<'.,.,', 'grammar.ra', 245) def _reduce_64(val, _values, result) result=val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 246) def _reduce_65(val, _values, result) result=val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 249) def _reduce_66(val, _values, result) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val result end .,., module_eval(<<'.,.,', 'grammar.ra', 254) def _reduce_67(val, _values, result) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val result end .,., # reduce 68 omitted # reduce 69 omitted module_eval(<<'.,.,', 'grammar.ra', 263) def _reduce_70(val, _values, result) result = ast AST::ResourceInstance, :title => val[0], :parameters => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 266) def _reduce_71(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 268) def _reduce_72(val, _values, result) val[0].push val[2] result = val[0] result end .,., # reduce 73 omitted # reduce 74 omitted module_eval(<<'.,.,', 'grammar.ra', 276) def _reduce_75(val, _values, result) result = ast AST::Undef, :value => :undef result end .,., module_eval(<<'.,.,', 'grammar.ra', 280) def _reduce_76(val, _values, result) result = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 284) def _reduce_77(val, _values, result) result = ast AST::Type, :value => val[0][:value], :line => val[0][:line] result end .,., # reduce 78 omitted # reduce 79 omitted # reduce 80 omitted # reduce 81 omitted # reduce 82 omitted # reduce 83 omitted # reduce 84 omitted module_eval(<<'.,.,', 'grammar.ra', 296) def _reduce_85(val, _values, result) raise Puppet::ParseError, "Cannot assign to variables in other namespaces" if val[0][:value] =~ /::/ # this is distinct from referencing a variable variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 302) def _reduce_86(val, _values, result) result = ast AST::VarDef, :name => val[0], :value => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 306) def _reduce_87(val, _values, result) variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :append => true, :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 312) def _reduce_88(val, _values, result) result = ast AST::ASTArray result end .,., module_eval(<<'.,.,', 'grammar.ra', 314) def _reduce_89(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 316) def _reduce_90(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 321) def _reduce_91(val, _values, result) result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 325) def _reduce_92(val, _values, result) result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2], :add => true result end .,., # reduce 93 omitted # reduce 94 omitted module_eval(<<'.,.,', 'grammar.ra', 334) def _reduce_95(val, _values, result) result = ast AST::ASTArray result end .,., module_eval(<<'.,.,', 'grammar.ra', 336) def _reduce_96(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 338) def _reduce_97(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 344) def _reduce_98(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :rvalue result end .,., module_eval(<<'.,.,', 'grammar.ra', 349) def _reduce_99(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :rvalue result end .,., module_eval(<<'.,.,', 'grammar.ra', 355) def _reduce_100(val, _values, result) result = ast AST::String, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 356) def _reduce_101(val, _values, result) result = ast AST::Concat, :value => [ast(AST::String,val[0])]+val[1], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 358) def _reduce_102(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 360) def _reduce_103(val, _values, result) result = [ast(AST::String,val[0])] result end .,., module_eval(<<'.,.,', 'grammar.ra', 361) def _reduce_104(val, _values, result) result = [ast(AST::String,val[0])] + val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 364) def _reduce_105(val, _values, result) result = ast AST::Boolean, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 368) def _reduce_106(val, _values, result) Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") result = ast AST::ResourceReference, :type => val[0][:value], :line => val[0][:line], :title => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 371) def _reduce_107(val, _values, result) result = ast AST::ResourceReference, :type => val[0], :title => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 375) def _reduce_108(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 379) def _reduce_109(val, _values, result) @lexer.commentpop args = { :test => val[0], :statements => val[2] } args[:else] = val[4] if val[4] result = ast AST::IfStatement, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 390) def _reduce_110(val, _values, result) @lexer.commentpop args = { :test => val[0], :statements => ast(AST::Nop) } args[:else] = val[3] if val[3] result = ast AST::IfStatement, args result end .,., # reduce 111 omitted module_eval(<<'.,.,', 'grammar.ra', 403) def _reduce_112(val, _values, result) result = ast AST::Else, :statements => val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 406) def _reduce_113(val, _values, result) @lexer.commentpop result = ast AST::Else, :statements => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 410) def _reduce_114(val, _values, result) @lexer.commentpop result = ast AST::Else, :statements => ast(AST::Nop) result end .,., # reduce 115 omitted # reduce 116 omitted module_eval(<<'.,.,', 'grammar.ra', 429) def _reduce_117(val, _values, result) result = ast AST::InOperator, :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 432) def _reduce_118(val, _values, result) result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 435) def _reduce_119(val, _values, result) result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 438) def _reduce_120(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 441) def _reduce_121(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 444) def _reduce_122(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 447) def _reduce_123(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 450) def _reduce_124(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 453) def _reduce_125(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 456) def _reduce_126(val, _values, result) result = ast AST::Minus, :value => val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 459) def _reduce_127(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 462) def _reduce_128(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 465) def _reduce_129(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 468) def _reduce_130(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 471) def _reduce_131(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 474) def _reduce_132(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 477) def _reduce_133(val, _values, result) result = ast AST::Not, :value => val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 480) def _reduce_134(val, _values, result) result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 483) def _reduce_135(val, _values, result) result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 486) def _reduce_136(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 490) def _reduce_137(val, _values, result) @lexer.commentpop result = ast AST::CaseStatement, :test => val[1], :options => val[3] result end .,., module_eval(<<'.,.,', 'grammar.ra', 494) def _reduce_138(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 496) def _reduce_139(val, _values, result) val[0].push val[1] result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 501) def _reduce_140(val, _values, result) @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] result end .,., module_eval(<<'.,.,', 'grammar.ra', 504) def _reduce_141(val, _values, result) @lexer.commentpop result = ast( AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) ) result end .,., module_eval(<<'.,.,', 'grammar.ra', 514) def _reduce_142(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 516) def _reduce_143(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 521) def _reduce_144(val, _values, result) result = ast AST::Selector, :param => val[0], :values => val[2] result end .,., # reduce 145 omitted module_eval(<<'.,.,', 'grammar.ra', 526) def _reduce_146(val, _values, result) @lexer.commentpop result = val[1] result end .,., # reduce 147 omitted module_eval(<<'.,.,', 'grammar.ra', 532) def _reduce_148(val, _values, result) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end result end .,., module_eval(<<'.,.,', 'grammar.ra', 541) def _reduce_149(val, _values, result) result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., # reduce 150 omitted # reduce 151 omitted # reduce 152 omitted # reduce 153 omitted # reduce 154 omitted # reduce 155 omitted # reduce 156 omitted # reduce 157 omitted module_eval(<<'.,.,', 'grammar.ra', 553) def _reduce_158(val, _values, result) result = ast AST::Default, :value => val[0][:value], :line => val[0][:line] result end .,., # reduce 159 omitted module_eval(<<'.,.,', 'grammar.ra', 558) def _reduce_160(val, _values, result) result = [val[0][:value]] result end .,., # reduce 161 omitted module_eval(<<'.,.,', 'grammar.ra', 560) def _reduce_162(val, _values, result) result = val[0] += val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 563) def _reduce_163(val, _values, result) val[1].each do |file| import(file) end result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 573) def _reduce_164(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :code => val[4], :line => val[0][:line])) @lexer.indefine = false #} | DEFINE NAME argumentlist parent LBRACE RBRACE { result end .,., module_eval(<<'.,.,', 'grammar.ra', 581) def _reduce_165(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :line => val[0][:line])) @lexer.indefine = false result end .,., module_eval(<<'.,.,', 'grammar.ra', 589) def _reduce_166(val, _values, result) @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :code => val[5], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 596) def _reduce_167(val, _values, result) @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 605) def _reduce_168(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :code => val[4], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 610) def _reduce_169(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 614) def _reduce_170(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 616) def _reduce_171(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 617) def _reduce_172(val, _values, result) result = "class" result end .,., module_eval(<<'.,.,', 'grammar.ra', 622) def _reduce_173(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'grammar.ra', 625) def _reduce_174(val, _values, result) result = val[0] result << val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 630) def _reduce_175(val, _values, result) result = ast AST::HostName, :value => val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 633) def _reduce_176(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 634) def _reduce_177(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 635) def _reduce_178(val, _values, result) result = val[0][:value] result end .,., # reduce 179 omitted module_eval(<<'.,.,', 'grammar.ra', 639) def _reduce_180(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 643) def _reduce_181(val, _values, result) result = ast AST::ASTArray, :children => [] result end .,., # reduce 182 omitted module_eval(<<'.,.,', 'grammar.ra', 648) def _reduce_183(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 651) def _reduce_184(val, _values, result) result = val[1] result = [result] unless result[0].is_a?(Array) result end .,., # reduce 185 omitted module_eval(<<'.,.,', 'grammar.ra', 657) def _reduce_186(val, _values, result) result = val[0] result = [result] unless result[0].is_a?(Array) result << val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 663) def _reduce_187(val, _values, result) Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0][:value], val[2]] result end .,., module_eval(<<'.,.,', 'grammar.ra', 667) def _reduce_188(val, _values, result) Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0][:value]] result end .,., module_eval(<<'.,.,', 'grammar.ra', 670) def _reduce_189(val, _values, result) result = [val[0][:value], val[2]] result end .,., module_eval(<<'.,.,', 'grammar.ra', 672) def _reduce_190(val, _values, result) result = [val[0][:value]] result end .,., # reduce 191 omitted module_eval(<<'.,.,', 'grammar.ra', 677) def _reduce_192(val, _values, result) result = val[1] result end .,., # reduce 193 omitted module_eval(<<'.,.,', 'grammar.ra', 682) def _reduce_194(val, _values, result) result = val[1] result end .,., # reduce 195 omitted # reduce 196 omitted module_eval(<<'.,.,', 'grammar.ra', 688) def _reduce_197(val, _values, result) result = ast AST::Variable, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 691) def _reduce_198(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 692) def _reduce_199(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 693) def _reduce_200(val, _values, result) result = ast AST::ASTArray result end .,., # reduce 201 omitted # reduce 202 omitted # reduce 203 omitted module_eval(<<'.,.,', 'grammar.ra', 699) def _reduce_204(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 702) def _reduce_205(val, _values, result) result = ast AST::Regex, :value => val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 706) def _reduce_206(val, _values, result) if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end result end .,., module_eval(<<'.,.,', 'grammar.ra', 713) def _reduce_207(val, _values, result) if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end result end .,., module_eval(<<'.,.,', 'grammar.ra', 719) def _reduce_208(val, _values, result) result = ast AST::ASTHash result end .,., # reduce 209 omitted module_eval(<<'.,.,', 'grammar.ra', 724) def _reduce_210(val, _values, result) if val[0].instance_of?(AST::ASTHash) result = val[0].merge(val[2]) else result = ast AST::ASTHash, :value => val[0] result.merge(val[2]) end result end .,., module_eval(<<'.,.,', 'grammar.ra', 733) def _reduce_211(val, _values, result) result = ast AST::ASTHash, { :value => { val[0] => val[2] } } result end .,., module_eval(<<'.,.,', 'grammar.ra', 736) def _reduce_212(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 737) def _reduce_213(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 740) def _reduce_214(val, _values, result) result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2] result end .,., # reduce 215 omitted module_eval(<<'.,.,', 'grammar.ra', 745) def _reduce_216(val, _values, result) result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2] result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Parser end # module Puppet diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index f13e9c8e1..2a54db24f 100755 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -1,134 +1,136 @@ # The standard init-based service type. Many other service types are # customizations of this module. Puppet::Type.type(:service).provide :init, :parent => :base do desc "Standard `init`-style service management." class << self attr_accessor :defpath end case Facter["operatingsystem"].value when "FreeBSD" @defpath = ["/etc/rc.d", "/usr/local/etc/rc.d"] when "HP-UX" @defpath = "/sbin/init.d" + when "Archlinux" + @defpath = "/etc/rc.d" else @defpath = "/etc/init.d" end # We can't confine this here, because the init path can be overridden. #confine :exists => @defpath # List all services of this type. def self.instances get_services(self.defpath) end def self.get_services(defpath, exclude=[]) defpath = [defpath] unless defpath.is_a? Array instances = [] defpath.each do |path| unless FileTest.directory?(path) Puppet.debug "Service path #{path} does not exist" next end check = [:ensure] check << :enable if public_method_defined? :enabled? Dir.entries(path).each do |name| fullpath = File.join(path, name) next if name =~ /^\./ next if exclude.include? name next if not FileTest.executable?(fullpath) instances << new(:name => name, :path => path, :hasstatus => true) end end instances end # Mark that our init script supports 'status' commands. def hasstatus=(value) case value when true, "true"; @parameters[:hasstatus] = true when false, "false"; @parameters[:hasstatus] = false else raise Puppet::Error, "Invalid 'hasstatus' value #{value.inspect}" end end # Where is our init script? def initscript @initscript ||= self.search(@resource[:name]) end def paths @paths ||= @resource[:path].find_all do |path| if File.directory?(path) true else if File.exist?(path) and ! File.directory?(path) self.debug "Search path #{path} is not a directory" else self.debug "Search path #{path} does not exist" end false end end end def search(name) paths.each { |path| fqname = File.join(path,name) begin stat = File.stat(fqname) rescue # should probably rescue specific errors... self.debug("Could not find #{name} in #{path}") next end # if we've gotten this far, we found a valid script return fqname } paths.each { |path| fqname_sh = File.join(path,"#{name}.sh") begin stat = File.stat(fqname_sh) rescue # should probably rescue specific errors... self.debug("Could not find #{name}.sh in #{path}") next end # if we've gotten this far, we found a valid script return fqname_sh } raise Puppet::Error, "Could not find init script for '#{name}'" end # The start command is just the init scriptwith 'start'. def startcmd [initscript, :start] end # The stop command is just the init script with 'stop'. def stopcmd [initscript, :stop] end def restartcmd (@resource[:hasrestart] == :true) && [initscript, :restart] end # If it was specified that the init script has a 'status' command, then # we just return that; otherwise, we return false, which causes it to # fallback to other mechanisms. def statuscmd (@resource[:hasstatus] == :true) && [initscript, :status] end end diff --git a/lib/puppet/provider/sshkey/parsed.rb b/lib/puppet/provider/sshkey/parsed.rb index dcc55b403..e105bbb4c 100755 --- a/lib/puppet/provider/sshkey/parsed.rb +++ b/lib/puppet/provider/sshkey/parsed.rb @@ -1,38 +1,37 @@ require 'puppet/provider/parsedfile' known = nil case Facter.value(:operatingsystem) when "Darwin"; known = "/etc/ssh_known_hosts" else known = "/etc/ssh/ssh_known_hosts" end - Puppet::Type.type(:sshkey).provide( - :parsed, +Puppet::Type.type(:sshkey).provide( + :parsed, :parent => Puppet::Provider::ParsedFile, :default_target => known, - :filetype => :flat ) do desc "Parse and generate host-wide known hosts files for SSH." text_line :comment, :match => /^#/ text_line :blank, :match => /^\s+/ record_line :parsed, :fields => %w{name type key}, :post_parse => proc { |hash| names = hash[:name].split(",", -1) hash[:name] = names.shift hash[:host_aliases] = names }, :pre_gen => proc { |hash| if hash[:host_aliases] names = [hash[:name], hash[:host_aliases]].flatten hash[:name] = [hash[:name], hash[:host_aliases]].flatten.join(",") hash.delete(:host_aliases) end } end diff --git a/lib/puppet/reference/providers.rb b/lib/puppet/reference/providers.rb index 576b00bcb..afd31c0a3 100644 --- a/lib/puppet/reference/providers.rb +++ b/lib/puppet/reference/providers.rb @@ -1,123 +1,119 @@ # This doesn't get stored in trac, since it changes every time. providers = Puppet::Util::Reference.newreference :providers, :title => "Provider Suitability Report", :depth => 1, :dynamic => true, :doc => "Which providers are valid for this machine" do types = [] Puppet::Type.loadall Puppet::Type.eachtype do |klass| next unless klass.providers.length > 0 types << klass end types.sort! { |a,b| a.name.to_s <=> b.name.to_s } command_line = Puppet::Util::CommandLine.new types.reject! { |type| ! command_line.args.include?(type.name.to_s) } unless command_line.args.empty? ret = "Details about this host:\n\n" # Throw some facts in there, so we know where the report is from. ["Ruby Version", "Puppet Version", "Operating System", "Operating System Release"].each do |label| name = label.gsub(/\s+/, '') value = Facter.value(name) ret << option(label, value) end ret << "\n" count = 1 # Produce output for each type. types.each do |type| features = type.features ret << "\n" # add a trailing newline # Now build up a table of provider suitability. headers = %w{Provider Suitable?} + features.collect { |f| f.to_s }.sort table_data = {} functional = false notes = [] - begin - default = type.defaultprovider.name - rescue Puppet::DevError - default = "none" - end + default = type.defaultprovider ? type.defaultprovider.name : 'none' type.providers.sort { |a,b| a.to_s <=> b.to_s }.each do |pname| data = [] table_data[pname] = data provider = type.provider(pname) # Add the suitability note if missing = provider.suitable?(false) and missing.empty? data << "*X*" suit = true functional = true else data << "[#{count}]_" # A pointer to the appropriate footnote suit = false end # Add a footnote with the details about why this provider is unsuitable, if that's the case unless suit details = ".. [#{count}]\n" missing.each do |test, values| case test when :exists details << " - Missing files #{values.join(", ")}\n" when :variable values.each do |name, facts| if Puppet.settings.valid?(name) details << " - Setting #{name} (currently #{Puppet.settings.value(name).inspect}) not in list #{facts.join(", ")}\n" else details << " - Fact #{name} (currently #{Facter.value(name).inspect}) not in list #{facts.join(", ")}\n" end end when :true details << " - Got #{values} true tests that should have been false\n" when :false details << " - Got #{values} false tests that should have been true\n" when :feature details << " - Missing features #{values.collect { |f| f.to_s }.join(",")}\n" end end notes << details count += 1 end # Add a note for every feature features.each do |feature| if provider.features.include?(feature) data << "*X*" else data << "" end end end ret << markdown_header(type.name.to_s + "_", 2) ret << "[#{type.name}](#{"http://docs.puppetlabs.com/references/stable/type.html##{type.name}"})\n\n" ret << option("Default provider", default) ret << doctable(headers, table_data) notes.each do |note| ret << note + "\n" end ret << "\n" end ret << "\n" ret end providers.header = " Puppet resource types are usually backed by multiple implementations called `providers`, which handle variance between platforms and tools. Different providers are suitable or unsuitable on different platforms based on things like the presence of a given tool. Here are all of the provider-backed types and their different providers. Any unmentioned types do not use providers yet. " diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index 468779870..d70fe32c7 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -1,329 +1,331 @@ require 'puppet/indirector' require 'puppet/ssl' require 'puppet/ssl/key' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_revocation_list' # The class that manages all aspects of our SSL certificates -- # private keys, public keys, requests, etc. class Puppet::SSL::Host # Yay, ruby's strange constant lookups. Key = Puppet::SSL::Key CA_NAME = Puppet::SSL::CA_NAME Certificate = Puppet::SSL::Certificate CertificateRequest = Puppet::SSL::CertificateRequest CertificateRevocationList = Puppet::SSL::CertificateRevocationList extend Puppet::Indirector indirects :certificate_status, :terminus_class => :file attr_reader :name attr_accessor :ca attr_writer :key, :certificate, :certificate_request # This accessor is used in instances for indirector requests to hold desired state attr_accessor :desired_state def self.localhost return @localhost if @localhost @localhost = new @localhost.generate unless @localhost.certificate @localhost.key @localhost end def self.reset @localhost = nil end # This is the constant that people will use to mark that a given host is # a certificate authority. def self.ca_name CA_NAME end class << self attr_reader :ca_location end # Configure how our various classes interact with their various terminuses. def self.configure_indirection(terminus, cache = nil) Certificate.indirection.terminus_class = terminus CertificateRequest.indirection.terminus_class = terminus CertificateRevocationList.indirection.terminus_class = terminus host_map = {:ca => :file, :file => nil, :rest => :rest} if term = host_map[terminus] self.indirection.terminus_class = term else self.indirection.reset_terminus_class end if cache # This is weird; we don't actually cache our keys, we # use what would otherwise be the cache as our normal # terminus. Key.indirection.terminus_class = cache else Key.indirection.terminus_class = terminus end if cache Certificate.indirection.cache_class = cache CertificateRequest.indirection.cache_class = cache CertificateRevocationList.indirection.cache_class = cache else # Make sure we have no cache configured. puppet master # switches the configurations around a bit, so it's important # that we specify the configs for absolutely everything, every # time. Certificate.indirection.cache_class = nil CertificateRequest.indirection.cache_class = nil CertificateRevocationList.indirection.cache_class = nil end end CA_MODES = { # Our ca is local, so we use it as the ultimate source of information # And we cache files locally. :local => [:ca, :file], # We're a remote CA client. :remote => [:rest, :file], # We are the CA, so we don't have read/write access to the normal certificates. :only => [:ca], # We have no CA, so we just look in the local file store. :none => [:file] } # Specify how we expect to interact with our certificate authority. def self.ca_location=(mode) modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ") raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode) @ca_location = mode configure_indirection(*CA_MODES[@ca_location]) end # Puppet::SSL::Host is actually indirected now so the original implementation # has been moved into the certificate_status indirector. This method is in-use # in `puppet cert -c `. def self.destroy(name) indirection.destroy(name) end def self.from_pson(pson) instance = new(pson["name"]) if pson["desired_state"] instance.desired_state = pson["desired_state"] end instance end # Puppet::SSL::Host is actually indirected now so the original implementation # has been moved into the certificate_status indirector. This method does not # appear to be in use in `puppet cert -l`. def self.search(options = {}) indirection.search("*", options) end # Is this a ca host, meaning that all of its files go in the CA location? def ca? ca end def key @key ||= Key.indirection.find(name) end # This is the private key; we can create it from scratch # with no inputs. def generate_key @key = Key.new(name) @key.generate begin Key.indirection.save(@key) rescue @key = nil raise end true end def certificate_request @certificate_request ||= CertificateRequest.indirection.find(name) end def this_csr_is_for_the_current_host name == Puppet[:certname].downcase end def this_csr_is_for_the_current_host name == Puppet[:certname].downcase end # Our certificate request requires the key but that's all. def generate_certificate_request(options = {}) generate_key unless key # If this is for the current machine... if this_csr_is_for_the_current_host # ...add our configured dns_alt_names if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' options[:dns_alt_names] ||= Puppet[:dns_alt_names] + elsif Puppet::SSL::CertificateAuthority.ca? and fqdn = Facter.value(:fqdn) and domain = Facter.value(:domain) + options[:dns_alt_names] = "puppet, #{fqdn}, puppet.#{domain}" end end @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key.content, options) begin CertificateRequest.indirection.save(@certificate_request) rescue @certificate_request = nil raise end true end def certificate unless @certificate generate_key unless key # get the CA cert first, since it's required for the normal cert # to be of any use. return nil unless Certificate.indirection.find("ca") unless ca? return nil unless @certificate = Certificate.indirection.find(name) unless certificate_matches_key? raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key" end end @certificate end def certificate_matches_key? return false unless key return false unless certificate certificate.content.check_private_key(key.content) end # Generate all necessary parts of our ssl host. def generate generate_key unless key generate_certificate_request unless certificate_request # If we can get a CA instance, then we're a valid CA, and we # should use it to sign our request; else, just try to read # the cert. if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance ca.sign(self.name, true) end end def initialize(name = nil) @name = (name || Puppet[:certname]).downcase @key = @certificate = @certificate_request = nil @ca = (name == self.class.ca_name) end # Extract the public key from the private key. def public_key key.content.public_key end # Create/return a store that uses our SSL info to validate # connections. def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) unless @ssl_store @ssl_store = OpenSSL::X509::Store.new @ssl_store.purpose = purpose # Use the file path here, because we don't want to cause # a lookup in the middle of setting our ssl connection. @ssl_store.add_file(Puppet[:localcacert]) # If there's a CRL, add it to our store. if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME) @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] @ssl_store.add_crl(crl.content) end return @ssl_store end @ssl_store end def to_pson(*args) my_cert = Puppet::SSL::Certificate.indirection.find(name) pson_hash = { :name => name } my_state = state pson_hash[:state] = my_state pson_hash[:desired_state] = desired_state if desired_state if my_state == 'requested' pson_hash[:fingerprint] = certificate_request.fingerprint else pson_hash[:fingerprint] = my_cert.fingerprint end pson_hash.to_pson(*args) end # Attempt to retrieve a cert, if we don't already have one. def wait_for_cert(time) begin return if certificate generate return if certificate rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not request certificate: #{detail}" if time < 1 puts "Exiting; failed to retrieve certificate and waitforcert is disabled" exit(1) else sleep(time) end retry end if time < 1 puts "Exiting; no certificate found and waitforcert is disabled" exit(1) end while true sleep time begin break if certificate Puppet.notice "Did not receive certificate" rescue StandardError => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not request certificate: #{detail}" end end end def state my_cert = Puppet::SSL::Certificate.indirection.find(name) if certificate_request return 'requested' end begin Puppet::SSL::CertificateAuthority.new.verify(my_cert) return 'signed' rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError return 'revoked' end end end require 'puppet/ssl/certificate_authority' diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index e90ab9657..21dc614d9 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -1,426 +1,487 @@ # the class that actually walks our resource/property tree, collects the changes, # and performs them require 'puppet' require 'puppet/util/tagging' require 'puppet/application' require 'digest/sha1' class Puppet::Transaction require 'puppet/transaction/event' require 'puppet/transaction/event_manager' require 'puppet/transaction/resource_harness' require 'puppet/resource/status' attr_accessor :component, :catalog, :ignoreschedules, :for_network_device attr_accessor :configurator # The report, once generated. attr_reader :report # Routes and stores any events and subscriptions. attr_reader :event_manager # Handles most of the actual interacting with resources attr_reader :resource_harness include Puppet::Util include Puppet::Util::Tagging # Wraps application run state check to flag need to interrupt processing def stop_processing? Puppet::Application.stop_requested? end # Add some additional times for reporting def add_times(hash) hash.each do |name, num| report.add_times(name, num) end end # Are there any failed resources in this transaction? def any_failed? report.resource_statuses.values.detect { |status| status.failed? } end # Apply all changes for a resource def apply(resource, ancestor = nil) status = resource_harness.evaluate(resource) add_resource_status(status) event_manager.queue_events(ancestor || resource, status.events) unless status.failed? rescue => detail resource.err "Could not evaluate: #{detail}" end # Find all of the changed resources. def changed? report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) } end # Find all of the applied resources (including failed attempts). def applied_resources report.resource_statuses.values.collect { |status| catalog.resource(status.resource) } end # Copy an important relationships from the parent to the newly-generated # child resource. def add_conditional_directed_dependency(parent, child, label=nil) relationship_graph.add_vertex(child) edge = parent.depthfirst? ? [child, parent] : [parent, child] if relationship_graph.edge?(*edge.reverse) parent.debug "Skipping automatic relationship to #{child}" else relationship_graph.add_edge(edge[0],edge[1],label) end end # Evaluate a single resource. def eval_resource(resource, ancestor = nil) if skip?(resource) resource_status(resource).skipped = true else resource_status(resource).scheduled = true apply(resource, ancestor) end # Check to see if there are any events queued for this resource event_manager.process_events(resource) end # This method does all the actual work of running a transaction. It # collects all of the changes, executes them, and responds to any # necessary events. def evaluate - prepare + add_dynamically_generated_resources Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version relationship_graph.traverse do |resource| if resource.is_a?(Puppet::Type::Component) Puppet.warning "Somehow left a component in the relationship graph" else seconds = thinmark { eval_resource(resource) } resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config? end end Puppet.debug "Finishing transaction #{object_id}" end def events event_manager.events end def failed?(resource) s = resource_status(resource) and s.failed? end # Does this resource have any failed dependencies? def failed_dependencies?(resource) # First make sure there are no failed dependencies. To do this, # we check for failures in any of the vertexes above us. It's not # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. found_failed = false # When we introduced the :whit into the graph, to reduce the combinatorial # explosion of edges, we also ended up reporting failures for containers # like class and stage. This is undesirable; while just skipping the # output isn't perfect, it is RC-safe. --daniel 2011-06-07 suppress_report = (resource.class == Puppet::Type.type(:whit)) relationship_graph.dependencies(resource).each do |dep| next unless failed?(dep) found_failed = true # See above. --daniel 2011-06-06 unless suppress_report then resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}" end end found_failed end def eval_generate(resource) return false unless resource.respond_to?(:eval_generate) raise Puppet::DevError,"Depthfirst resources are not supported by eval_generate" if resource.depthfirst? begin made = resource.eval_generate.uniq return false if made.empty? made = Hash[made.map(&:name).zip(made)] rescue => detail puts detail.backtrace if Puppet[:trace] resource.err "Failed to generate additional resources using 'eval_generate: #{detail}" return false end made.values.each do |res| begin res.tag(*resource.tags) @catalog.add_resource(res) res.finish rescue Puppet::Resource::Catalog::DuplicateResourceError res.info "Duplicate generated resource; skipping" end end sentinel = Puppet::Type.type(:whit).new(:name => "completed_#{resource.title}", :catalog => resource.catalog) # The completed whit is now the thing that represents the resource is done relationship_graph.adjacent(resource,:direction => :out,:type => :edges).each { |e| # But children run as part of the resource, not after it next if made[e.target.name] add_conditional_directed_dependency(sentinel, e.target, e.label) relationship_graph.remove_edge! e } default_label = Puppet::Resource::Catalog::Default_label made.values.each do |res| # Depend on the nearest ancestor we generated, falling back to the # resource if we have none parent_name = res.ancestors.find { |a| made[a] and made[a] != res } parent = made[parent_name] || resource add_conditional_directed_dependency(parent, res) # This resource isn't 'completed' until each child has run add_conditional_directed_dependency(res, sentinel, default_label) end # This edge allows the resource's events to propagate, though it isn't # strictly necessary for ordering purposes add_conditional_directed_dependency(resource, sentinel, default_label) true end # A general method for recursively generating new resources from a # resource. def generate_additional_resources(resource) return unless resource.respond_to?(:generate) begin made = resource.generate rescue => detail puts detail.backtrace if Puppet[:trace] resource.err "Failed to generate additional resources using 'generate': #{detail}" end return unless made made = [made] unless made.is_a?(Array) made.uniq.each do |res| begin res.tag(*resource.tags) @catalog.add_resource(res) res.finish add_conditional_directed_dependency(resource, res) generate_additional_resources(res) rescue Puppet::Resource::Catalog::DuplicateResourceError res.info "Duplicate generated resource; skipping" end end end - # Collect any dynamically generated resources. This method is called - # before the transaction starts. - def xgenerate + def add_dynamically_generated_resources @catalog.vertices.each { |resource| generate_additional_resources(resource) } end # Should we ignore tags? def ignore_tags? ! (@catalog.host_config? or Puppet[:name] == "puppet") end # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array def initialize(catalog, report = nil) @catalog = catalog @report = report || Puppet::Transaction::Report.new("apply", catalog.version) @event_manager = Puppet::Transaction::EventManager.new(self) @resource_harness = Puppet::Transaction::ResourceHarness.new(self) + + @prefetched_providers = Hash.new { |h,k| h[k] = {} } end - # Prefetch any providers that support it. We don't support prefetching - # types, just providers. - def prefetch - prefetchers = {} - @catalog.vertices.each do |resource| - if provider = resource.provider and provider.class.respond_to?(:prefetch) - prefetchers[provider.class] ||= {} - prefetchers[provider.class][resource.name] = resource - end - end + def resources_by_provider(type_name, provider_name) + unless @resources_by_provider + @resources_by_provider = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } } - # Now call prefetch, passing in the resources so that the provider instances can be replaced. - prefetchers.each do |provider, resources| - Puppet.debug "Prefetching #{provider.name} resources for #{provider.resource_type.name}" - begin - provider.prefetch(resources) - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not prefetch #{provider.resource_type.name} provider '#{provider.name}': #{detail}" + @catalog.vertices.each do |resource| + if resource.class.attrclass(:provider) + prov = resource.provider && resource.provider.class.name + @resources_by_provider[resource.type][prov][resource.name] = resource + end end end + + @resources_by_provider[type_name][provider_name] || {} end - # Prepare to evaluate the resources in a transaction. - def prepare - # Now add any dynamically generated resources - xgenerate + def prefetch_if_necessary(resource) + provider_class = resource.provider.class + return unless provider_class.respond_to?(:prefetch) and !prefetched_providers[resource.type][provider_class.name] - # Then prefetch. It's important that we generate and then prefetch, - # so that any generated resources also get prefetched. - prefetch + resources = resources_by_provider(resource.type, provider_class.name) + + if provider_class == resource.class.defaultprovider + providerless_resources = resources_by_provider(resource.type, nil) + providerless_resources.values.each {|res| res.provider = provider_class.name} + resources.merge! providerless_resources + end + + prefetch(provider_class, resources) end + attr_reader :prefetched_providers + + # Prefetch any providers that support it, yo. We don't support prefetching + # types, just providers. + def prefetch(provider_class, resources) + type_name = provider_class.resource_type.name + return if @prefetched_providers[type_name][provider_class.name] + Puppet.debug "Prefetching #{provider_class.name} resources for #{type_name}" + begin + provider_class.prefetch(resources) + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not prefetch #{type_name} provider '#{provider_class.name}': #{detail}" + end + @prefetched_providers[type_name][provider_class.name] = true + end # We want to monitor changes in the relationship graph of our # catalog but this is complicated by the fact that the catalog # both is_a graph and has_a graph, by the fact that changes to # the structure of the object can have adverse serialization # effects, by threading issues, by order-of-initialization issues, # etc. # # Since the proper lifetime/scope of the monitoring is a transaction # and the transaction is already commiting a mild law-of-demeter # transgression, we cut the Gordian knot here by simply wrapping the # transaction's view of the resource graph to capture and maintain # the information we need. Nothing outside the transaction needs # this information, and nothing outside the transaction can see it # except via the Transaction#relationship_graph class Relationship_graph_wrapper require 'puppet/rb_tree_map' attr_reader :real_graph,:transaction,:ready,:generated,:done,:blockers,:unguessable_deterministic_key def initialize(real_graph,transaction) @real_graph = real_graph @transaction = transaction @ready = Puppet::RbTreeMap.new @generated = {} @done = {} @blockers = {} @unguessable_deterministic_key = Hash.new { |h,k| h[k] = Digest::SHA1.hexdigest("NaCl, MgSO4 (salts) and then #{k.ref}") } + @providerless_types = [] vertices.each do |v| blockers[v] = direct_dependencies_of(v).length enqueue(v) if blockers[v] == 0 end end def method_missing(*args,&block) real_graph.send(*args,&block) end def add_vertex(v) real_graph.add_vertex(v) end def add_edge(f,t,label=nil) key = unguessable_deterministic_key[t] ready.delete(key) real_graph.add_edge(f,t,label) end - # Decrement the blocker count for resource r by 1. If the number of + # Decrement the blocker count for the resource by 1. If the number of # blockers is unknown, count them and THEN decrement by 1. - def unblock(r) - blockers[r] ||= direct_dependencies_of(r).select { |r2| !done[r2] }.length - if blockers[r] > 0 - blockers[r] -= 1 + def unblock(resource) + blockers[resource] ||= direct_dependencies_of(resource).select { |r2| !done[r2] }.length + if blockers[resource] > 0 + blockers[resource] -= 1 else - r.warning "appears to have a negative number of dependencies" + resource.warning "appears to have a negative number of dependencies" end - blockers[r] <= 0 + blockers[resource] <= 0 end - def enqueue(r) - key = unguessable_deterministic_key[r] - ready[key] = r + def enqueue(*resources) + resources.each do |resource| + key = unguessable_deterministic_key[resource] + ready[key] = resource + end + end + def finish(resource) + direct_dependents_of(resource).each do |v| + enqueue(v) if unblock(v) + end + done[resource] = true end def next_resource ready.delete_min end def traverse(&block) real_graph.report_cycles_in_graph - while (r = next_resource) && !transaction.stop_processing? - # If we generated resources, we don't know what they are now - # blocking, so we opt to recompute it, rather than try to track every - # change that would affect the number. - blockers.clear if transaction.eval_generate(r) + deferred_resources = [] + + while (resource = next_resource) && !transaction.stop_processing? + if resource.suitable? + made_progress = true + + transaction.prefetch_if_necessary(resource) + + # If we generated resources, we don't know what they are now + # blocking, so we opt to recompute it, rather than try to track every + # change that would affect the number. + blockers.clear if transaction.eval_generate(resource) + + yield resource + + finish(resource) + else + deferred_resources << resource + end - yield r + if ready.empty? and deferred_resources.any? + if made_progress + enqueue(*deferred_resources) + else + fail_unsuitable_resources(deferred_resources) + end - direct_dependents_of(r).each do |v| - enqueue(v) if unblock(v) + made_progress = false + deferred_resources = [] end - done[r] = true + end + + # Just once per type. No need to punish the user. + @providerless_types.uniq.each do |type| + Puppet.err "Could not find a suitable provider for #{type}" + end + end + + def fail_unsuitable_resources(resources) + resources.each do |resource| + # We don't automatically assign unsuitable providers, so if there + # is one, it must have been selected by the user. + if resource.provider + resource.err "Provider #{resource.provider.class.name} is not functional on this host" + else + @providerless_types << resource.type + end + + transaction.resource_status(resource).failed = true + + finish(resource) end end end def relationship_graph @relationship_graph ||= Relationship_graph_wrapper.new(catalog.relationship_graph,self) end def add_resource_status(status) report.add_resource_status status end def resource_status(resource) report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource)) end # Is the resource currently scheduled? def scheduled?(resource) self.ignoreschedules or resource_harness.scheduled?(resource_status(resource), resource) end # Should this resource be skipped? def skip?(resource) if missing_tags?(resource) resource.debug "Not tagged with #{tags.join(", ")}" elsif ! scheduled?(resource) resource.debug "Not scheduled" elsif failed_dependencies?(resource) # When we introduced the :whit into the graph, to reduce the combinatorial # explosion of edges, we also ended up reporting failures for containers # like class and stage. This is undesirable; while just skipping the # output isn't perfect, it is RC-safe. --daniel 2011-06-07 unless resource.class == Puppet::Type.type(:whit) then resource.warning "Skipping because of failed dependencies" end elsif resource.virtual? resource.debug "Skipping because virtual" elsif resource.appliable_to_device? ^ for_network_device resource.debug "Skipping #{resource.appliable_to_device? ? 'device' : 'host'} resources because running on a #{for_network_device ? 'device' : 'host'}" else return false end true end # The tags we should be checking. def tags self.tags = Puppet[:tags] unless defined?(@tags) super end def handle_qualified_tags( qualified ) # The default behavior of Puppet::Util::Tagging is # to split qualified tags into parts. That would cause # qualified tags to match too broadly here. return end # Is this resource tagged appropriately? def missing_tags?(resource) return false if ignore_tags? return false if tags.empty? not resource.tagged?(*tags) end end require 'puppet/transaction/report' diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 6aaf9e746..94f26cfe5 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -1,1943 +1,1954 @@ require 'puppet' require 'puppet/util/log' require 'puppet/util/metric' require 'puppet/property' require 'puppet/parameter' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/metatype/manager' require 'puppet/util/errors' require 'puppet/util/log_paths' require 'puppet/util/logging' require 'puppet/file_collection/lookup' require 'puppet/util/tagging' # see the bottom of the file for the rest of the inclusions module Puppet class Type include Puppet::Util include Puppet::Util::Errors include Puppet::Util::LogPaths include Puppet::Util::Logging include Puppet::FileCollection::Lookup include Puppet::Util::Tagging ############################### # Code related to resource type attributes. class << self include Puppet::Util::ClassGen include Puppet::Util::Warnings attr_reader :properties end def self.states warnonce "The states method is deprecated; use properties" properties end # All parameters, in the appropriate order. The key_attributes come first, then # the provider, then the properties, and finally the params and metaparams # in the order they were specified in the files. def self.allattrs key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams end # Retrieve an attribute alias, if there is one. def self.attr_alias(param) @attr_aliases[symbolize(param)] end # Create an alias to an existing attribute. This will cause the aliased # attribute to be valid when setting and retrieving values on the instance. def self.set_attr_alias(hash) hash.each do |new, old| @attr_aliases[symbolize(new)] = symbolize(old) end end # Find the class associated with any given attribute. def self.attrclass(name) @attrclasses ||= {} # We cache the value, since this method gets called such a huge number # of times (as in, hundreds of thousands in a given run). unless @attrclasses.include?(name) @attrclasses[name] = case self.attrtype(name) when :property; @validproperties[name] when :meta; @@metaparamhash[name] when :param; @paramhash[name] end end @attrclasses[name] end # What type of parameter are we dealing with? Cache the results, because # this method gets called so many times. def self.attrtype(attr) @attrtypes ||= {} unless @attrtypes.include?(attr) @attrtypes[attr] = case when @validproperties.include?(attr); :property when @paramhash.include?(attr); :param when @@metaparamhash.include?(attr); :meta end end @attrtypes[attr] end def self.eachmetaparam @@metaparams.each { |p| yield p.name } end # Create the 'ensure' class. This is a separate method so other types # can easily call it and create their own 'ensure' values. def self.ensurable(&block) if block_given? self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block) else self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do self.defaultvalues end end end # Should we add the 'ensure' property to this class? def self.ensurable? # If the class has all three of these methods defined, then it's # ensurable. ens = [:exists?, :create, :destroy].inject { |set, method| set &&= self.public_method_defined?(method) } ens end def self.apply_to_device @apply_to = :device end def self.apply_to_host @apply_to = :host end def self.apply_to_all @apply_to = :both end def self.apply_to @apply_to ||= :host end def self.can_apply_to(target) [ target == :device ? :device : :host, :both ].include?(apply_to) end # Deal with any options passed into parameters. def self.handle_param_options(name, options) # If it's a boolean parameter, create a method to test the value easily if options[:boolean] define_method(name.to_s + "?") do val = self[name] if val == :true or val == true return true end end end end # Is the parameter in question a meta-parameter? def self.metaparam?(param) @@metaparamhash.include?(symbolize(param)) end # Find the metaparameter class associated with a given metaparameter name. def self.metaparamclass(name) @@metaparamhash[symbolize(name)] end def self.metaparams @@metaparams.collect { |param| param.name } end def self.metaparamdoc(metaparam) @@metaparamhash[metaparam].doc end # Create a new metaparam. Requires a block and a name, stores it in the # @parameters array, and does some basic checking on it. def self.newmetaparam(name, options = {}, &block) @@metaparams ||= [] @@metaparamhash ||= {} name = symbolize(name) param = genclass( name, :parent => options[:parent] || Puppet::Parameter, :prefix => "MetaParam", :hash => @@metaparamhash, :array => @@metaparams, :attributes => options[:attributes], &block ) # Grr. param.required_features = options[:required_features] if options[:required_features] handle_param_options(name, options) param.metaparam = true param end def self.key_attribute_parameters @key_attribute_parameters ||= ( params = @parameters.find_all { |param| param.isnamevar? or param.name == :name } ) end def self.key_attributes key_attribute_parameters.collect { |p| p.name } end def self.title_patterns case key_attributes.length when 0; [] when 1; identity = lambda {|x| x} [ [ /(.*)/m, [ [key_attributes.first, identity ] ] ] ] else raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes" end end def uniqueness_key self.class.key_attributes.sort_by { |attribute_name| attribute_name.to_s }.map{ |attribute_name| self[attribute_name] } end # Create a new parameter. Requires a block and a name, stores it in the # @parameters array, and does some basic checking on it. def self.newparam(name, options = {}, &block) options[:attributes] ||= {} param = genclass( name, :parent => options[:parent] || Puppet::Parameter, :attributes => options[:attributes], :block => block, :prefix => "Parameter", :array => @parameters, :hash => @paramhash ) handle_param_options(name, options) # Grr. param.required_features = options[:required_features] if options[:required_features] param.isnamevar if options[:namevar] param end def self.newstate(name, options = {}, &block) Puppet.warning "newstate() has been deprecrated; use newproperty(#{name})" newproperty(name, options, &block) end # Create a new property. The first parameter must be the name of the property; # this is how users will refer to the property when creating new instances. # The second parameter is a hash of options; the options are: # * :parent: The parent class for the property. Defaults to Puppet::Property. # * :retrieve: The method to call on the provider or @parent object (if # the provider is not set) to retrieve the current value. def self.newproperty(name, options = {}, &block) name = symbolize(name) # This is here for types that might still have the old method of defining # a parent class. unless options.is_a? Hash raise Puppet::DevError, "Options must be a hash, not #{options.inspect}" end raise Puppet::DevError, "Class #{self.name} already has a property named #{name}" if @validproperties.include?(name) if parent = options[:parent] options.delete(:parent) else parent = Puppet::Property end # We have to create our own, new block here because we want to define # an initial :retrieve method, if told to, and then eval the passed # block if available. prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do # If they've passed a retrieve method, then override the retrieve # method on the class. if options[:retrieve] define_method(:retrieve) do provider.send(options[:retrieve]) end end class_eval(&block) if block end # If it's the 'ensure' property, always put it first. if name == :ensure @properties.unshift prop else @properties << prop end prop end def self.paramdoc(param) @paramhash[param].doc end # Return the parameter names def self.parameters return [] unless defined?(@parameters) @parameters.collect { |klass| klass.name } end # Find the parameter class associated with a given parameter name. def self.paramclass(name) @paramhash[name] end # Return the property class associated with a name def self.propertybyname(name) @validproperties[name] end def self.validattr?(name) name = symbolize(name) return true if name == :name @validattrs ||= {} unless @validattrs.include?(name) @validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name)) end @validattrs[name] end # does the name reflect a valid property? def self.validproperty?(name) name = symbolize(name) @validproperties.include?(name) && @validproperties[name] end # Return the list of validproperties def self.validproperties return {} unless defined?(@parameters) @validproperties.keys end # does the name reflect a valid parameter? def self.validparameter?(name) raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters) !!(@paramhash.include?(name) or @@metaparamhash.include?(name)) end # This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource. def self.valid_parameter?(name) validattr?(name) end # Return either the attribute alias or the attribute. def attr_alias(name) name = symbolize(name) if synonym = self.class.attr_alias(name) return synonym else return name end end # Are we deleting this resource? def deleting? obj = @parameters[:ensure] and obj.should == :absent end # Create a new property if it is valid but doesn't exist # Returns: true if a new parameter was added, false otherwise def add_property_parameter(prop_name) if self.class.validproperty?(prop_name) && !@parameters[prop_name] self.newattr(prop_name) return true end false end # # The name_var is the key_attribute in the case that there is only one. # def name_var key_attributes = self.class.key_attributes (key_attributes.length == 1) && key_attributes.first end # abstract accessing parameters and properties, and normalize # access to always be symbols, not strings # This returns a value, not an object. It returns the 'is' # value, but you can also specifically return 'is' and 'should' # values using 'object.is(:property)' or 'object.should(:property)'. def [](name) name = attr_alias(name) fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name) if name == :name && nv = name_var name = nv end if obj = @parameters[name] # Note that if this is a property, then the value is the "should" value, # not the current value. obj.value else return nil end end # Abstract setting parameters and properties, and normalize # access to always be symbols, not strings. This sets the 'should' # value on properties, and otherwise just sets the appropriate parameter. def []=(name,value) name = attr_alias(name) fail("Invalid parameter #{name}") unless self.class.validattr?(name) if name == :name && nv = name_var name = nv end raise Puppet::Error.new("Got nil value for #{name}") if value.nil? property = self.newattr(name) if property begin # make sure the parameter doesn't have any errors property.value = value rescue => detail error = Puppet::Error.new("Parameter #{name} failed: #{detail}") error.set_backtrace(detail.backtrace) raise error end end nil end # remove a property from the object; useful in testing or in cleanup # when an error has been encountered def delete(attr) attr = symbolize(attr) if @parameters.has_key?(attr) @parameters.delete(attr) else raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}") end end # iterate across the existing properties def eachproperty # properties is a private method properties.each { |property| yield property } end # Create a transaction event. Called by Transaction or by # a property. def event(options = {}) Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options)) end # retrieve the 'should' value for a specified property def should(name) name = attr_alias(name) (prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil end # Create the actual attribute instance. Requires either the attribute # name or class as the first argument, then an optional hash of # attributes to set during initialization. def newattr(name) if name.is_a?(Class) klass = name name = klass.name end unless klass = self.class.attrclass(name) raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}" end if provider and ! provider.class.supports_parameter?(klass) missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) } info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name] return nil end return @parameters[name] if @parameters.include?(name) @parameters[name] = klass.new(:resource => self) end # return the value of a parameter def parameter(name) @parameters[name.to_sym] end def parameters @parameters.dup end # Is the named property defined? def propertydefined?(name) name = name.intern unless name.is_a? Symbol @parameters.include?(name) end # Return an actual property instance by name; to return the value, use 'resource[param]' # LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method, # this one should probably go away at some point. def property(name) (obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)) ? obj : nil end # For any parameters or properties that have defaults and have not yet been # set, set them now. This method can be handed a list of attributes, # and if so it will only set defaults for those attributes. def set_default(attr) return unless klass = self.class.attrclass(attr) return unless klass.method_defined?(:default) return if @parameters.include?(klass.name) return unless parameter = newattr(klass.name) if value = parameter.default and ! value.nil? parameter.value = value else @parameters.delete(parameter.name) end end # Convert our object to a hash. This just includes properties. def to_hash rethash = {} @parameters.each do |name, obj| rethash[name] = obj.value end rethash end def type self.class.name end # Return a specific value for an attribute. def value(name) name = attr_alias(name) (obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil end def version return 0 unless catalog catalog.version end # Return all of the property objects, in the order specified in the # class. def properties self.class.properties.collect { |prop| @parameters[prop.name] }.compact end # Is this type's name isomorphic with the object? That is, if the # name conflicts, does it necessarily mean that the objects conflict? # Defaults to true. def self.isomorphic? if defined?(@isomorphic) return @isomorphic else return true end end def isomorphic? self.class.isomorphic? end # is the instance a managed instance? A 'yes' here means that # the instance was created from the language, vs. being created # in order resolve other questions, such as finding a package # in a list def managed? # Once an object is managed, it always stays managed; but an object # that is listed as unmanaged might become managed later in the process, # so we have to check that every time if @managed return @managed else @managed = false properties.each { |property| s = property.should if s and ! property.class.unmanaged @managed = true break end } return @managed end end ############################### # Code related to the container behaviour. def depthfirst? false end # Remove an object. The argument determines whether the object's # subscriptions get eliminated, too. def remove(rmdeps = true) # This is hackish (mmm, cut and paste), but it works for now, and it's # better than warnings. @parameters.each do |name, obj| obj.remove end @parameters.clear @parent = nil # Remove the reference to the provider. if self.provider @provider.clear @provider = nil end end ############################### # Code related to evaluating the resources. def ancestors [] end # Flush the provider, if it supports it. This is called by the # transaction. def flush self.provider.flush if self.provider and self.provider.respond_to?(:flush) end # if all contained objects are in sync, then we're in sync # FIXME I don't think this is used on the type instances any more, # it's really only used for testing def insync?(is) insync = true if property = @parameters[:ensure] unless is.include? property raise Puppet::DevError, "The is value is not in the is array for '#{property.name}'" end ensureis = is[property] if property.safe_insync?(ensureis) and property.should == :absent return true end end properties.each { |property| unless is.include? property raise Puppet::DevError, "The is value is not in the is array for '#{property.name}'" end propis = is[property] unless property.safe_insync?(propis) property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}") insync = false #else # property.debug("In sync") end } #self.debug("#{self} sync status is #{insync}") insync end # retrieve the current value of all contained properties def retrieve fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable? result = Puppet::Resource.new(type, title) # Provide the name, so we know we'll always refer to a real thing result[:name] = self[:name] unless self[:name] == title if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure)) result[:ensure] = ensure_state = ensure_prop.retrieve else ensure_state = nil end properties.each do |property| next if property.name == :ensure if ensure_state == :absent result[property] = :absent else result[property] = property.retrieve end end result end def retrieve_resource resource = retrieve resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash resource end # Get a hash of the current properties. Returns a hash with # the actual property instance as the key and the current value # as the, um, value. def currentpropvalues # It's important to use the 'properties' method here, as it follows the order # in which they're defined in the class. It also guarantees that 'ensure' # is the first property, which is important for skipping 'retrieve' on # all the properties if the resource is absent. ensure_state = false return properties.inject({}) do | prophash, property| if property.name == :ensure ensure_state = property.retrieve prophash[property] = ensure_state else if ensure_state == :absent prophash[property] = :absent else prophash[property] = property.retrieve end end prophash end end # Are we running in noop mode? def noop? # If we're not a host_config, we're almost certainly part of # Settings, and we want to ignore 'noop' return false if catalog and ! catalog.host_config? if defined?(@noop) @noop else Puppet[:noop] end end def noop noop? end ############################### # Code related to managing resource instances. require 'puppet/transportable' # retrieve a named instance of the current type def self.[](name) raise "Global resource access is deprecated" @objects[name] || @aliases[name] end # add an instance by name to the class list of instances def self.[]=(name,object) raise "Global resource storage is deprecated" newobj = nil if object.is_a?(Puppet::Type) newobj = object else raise Puppet::DevError, "must pass a Puppet::Type object" end if exobj = @objects[name] and self.isomorphic? msg = "Object '#{newobj.class.name}[#{name}]' already exists" msg += ("in file #{object.file} at line #{object.line}") if exobj.file and exobj.line msg += ("and cannot be redefined in file #{object.file} at line #{object.line}") if object.file and object.line error = Puppet::Error.new(msg) raise error else #Puppet.info("adding %s of type %s to class list" % # [name,object.class]) @objects[name] = newobj end end # Create an alias. We keep these in a separate hash so that we don't encounter # the objects multiple times when iterating over them. def self.alias(name, obj) raise "Global resource aliasing is deprecated" if @objects.include?(name) unless @objects[name] == obj raise Puppet::Error.new( "Cannot create alias #{name}: object already exists" ) end end if @aliases.include?(name) unless @aliases[name] == obj raise Puppet::Error.new( "Object #{@aliases[name].name} already has alias #{name}" ) end end @aliases[name] = obj end # remove all of the instances of a single type def self.clear raise "Global resource removal is deprecated" if defined?(@objects) @objects.each do |name, obj| obj.remove(true) end @objects.clear end @aliases.clear if defined?(@aliases) end # Force users to call this, so that we can merge objects if # necessary. def self.create(args) # LAK:DEP Deprecation notice added 12/17/2008 Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new" new(args) end # remove a specified object def self.delete(resource) raise "Global resource removal is deprecated" return unless defined?(@objects) @objects.delete(resource.title) if @objects.include?(resource.title) @aliases.delete(resource.title) if @aliases.include?(resource.title) if @aliases.has_value?(resource) names = [] @aliases.each do |name, otherres| if otherres == resource names << name end end names.each { |name| @aliases.delete(name) } end end # iterate across each of the type's instances def self.each raise "Global resource iteration is deprecated" return unless defined?(@objects) @objects.each { |name,instance| yield instance } end # does the type have an object with the given name? def self.has_key?(name) raise "Global resource access is deprecated" @objects.has_key?(name) end # Retrieve all known instances. Either requires providers or must be overridden. def self.instances raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty? # Put the default provider first, then the rest of the suitable providers. provider_instances = {} providers_by_source.collect do |provider| all_properties = self.properties.find_all do |property| provider.supports_parameter?(property) end.collect do |property| property.name end provider.instances.collect do |instance| # We always want to use the "first" provider instance we find, unless the resource # is already managed and has a different provider set if other = provider_instances[instance.name] Puppet.warning "%s %s found in both %s and %s; skipping the %s version" % [self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name] next end provider_instances[instance.name] = instance result = new(:name => instance.name, :provider => instance) properties.each { |name| result.newattr(name) } result end end.flatten.compact end # Return a list of one suitable provider per source, with the default provider first. def self.providers_by_source # Put the default provider first, then the rest of the suitable providers. sources = [] [defaultprovider, suitableprovider].flatten.uniq.collect do |provider| next if sources.include?(provider.source) sources << provider.source provider end.compact end # Convert a simple hash into a Resource instance. def self.hash2resource(hash) hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result } title = hash.delete(:title) title ||= hash[:name] title ||= hash[key_attributes.first] if key_attributes.length == 1 raise Puppet::Error, "Title or name must be provided" unless title # Now create our resource. resource = Puppet::Resource.new(self.name, title) [:catalog].each do |attribute| if value = hash[attribute] hash.delete(attribute) resource.send(attribute.to_s + "=", value) end end hash.each do |param, value| resource[param] = value end resource end # Create the path for logging and such. def pathbuilder if p = parent [p.pathbuilder, self.ref].flatten else [self.ref] end end ############################### # Add all of the meta parameters. newmetaparam(:noop) do desc "Boolean flag indicating whether work should actually be done." newvalues(:true, :false) munge do |value| case value when true, :true, "true"; @resource.noop = true when false, :false, "false"; @resource.noop = false end end end newmetaparam(:schedule) do desc "On what schedule the object should be managed. You must create a schedule object, and then reference the name of that object to use that for your schedule: schedule { 'daily': period => daily, range => \"2-4\" } exec { \"/usr/bin/apt-get update\": schedule => 'daily' } The creation of the schedule object does not need to appear in the configuration before objects that use it." end newmetaparam(:audit) do desc "Marks a subset of this resource's unmanaged attributes for auditing. Accepts an attribute name, an array of attribute names, or `all`. Auditing a resource attribute has two effects: First, whenever a catalog is applied with puppet apply or puppet agent, Puppet will check whether that attribute of the resource has been modified, comparing its current value to the previous run; any change will be logged alongside any actions performed by Puppet while applying the catalog. Secondly, marking a resource attribute for auditing will include that attribute in inspection reports generated by puppet inspect; see the puppet inspect documentation for more details. Managed attributes for a resource can also be audited, but note that changes made by Puppet will be logged as additional modifications. (I.e. if a user manually edits a file whose contents are audited and managed, puppet agent's next two runs will both log an audit notice: the first run will log the user's edit and then revert the file to the desired state, and the second run will log the edit made by Puppet.)" validate do |list| list = Array(list).collect {|p| p.to_sym} unless list == [:all] list.each do |param| next if @resource.class.validattr?(param) fail "Cannot audit #{param}: not a valid attribute for #{resource}" end end end munge do |args| properties_to_audit(args).each do |param| next unless resource.class.validproperty?(param) resource.newattr(param) end end def all_properties resource.class.properties.find_all do |property| resource.provider.nil? or resource.provider.class.supports_parameter?(property) end.collect do |property| property.name end end def properties_to_audit(list) if !list.kind_of?(Array) && list.to_sym == :all list = all_properties else list = Array(list).collect { |p| p.to_sym } end end end newmetaparam(:check) do desc "Audit specified attributes of resources over time, and report if any have changed. This parameter has been deprecated in favor of 'audit'." munge do |args| resource.warning "'check' attribute is deprecated; use 'audit' instead" resource[:audit] = args end end newmetaparam(:loglevel) do desc "Sets the level that information will be logged. The log levels have the biggest impact when logs are sent to syslog (which is currently the default)." defaultto :notice newvalues(*Puppet::Util::Log.levels) newvalues(:verbose) munge do |loglevel| val = super(loglevel) if val == :verbose val = :info end val end end newmetaparam(:alias) do desc "Creates an alias for the object. Puppet uses this internally when you provide a symbolic title: file { 'sshdconfig': path => $operatingsystem ? { solaris => \"/usr/local/etc/ssh/sshd_config\", default => \"/etc/ssh/sshd_config\" }, source => \"...\" } service { 'sshd': subscribe => File['sshdconfig'] } When you use this feature, the parser sets `sshdconfig` as the title, and the library sets that as an alias for the file so the dependency lookup in `Service['sshd']` works. You can use this metaparameter yourself, but note that only the library can use these aliases; for instance, the following code will not work: file { \"/etc/ssh/sshd_config\": owner => root, group => root, alias => 'sshdconfig' } file { 'sshdconfig': mode => 644 } There's no way here for the Puppet parser to know that these two stanzas should be affecting the same file. See the [Language Guide](http://docs.puppetlabs.com/guides/language_guide.html) for more information. " munge do |aliases| aliases = [aliases] unless aliases.is_a?(Array) raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog aliases.each do |other| if obj = @resource.catalog.resource(@resource.class.name, other) unless obj.object_id == @resource.object_id self.fail("#{@resource.title} can not create alias #{other}: object already exists") end next end # Newschool, add it to the catalog. @resource.catalog.alias(@resource, other) end end end newmetaparam(:tag) do desc "Add the specified tags to the associated resource. While all resources are automatically tagged with as much information as possible (e.g., each class and definition containing the resource), it can be useful to add your own tags to a given resource. Multiple tags can be specified as an array: file {'/etc/hosts': ensure => file, source => 'puppet:///modules/site/hosts', mode => 0644, tag => ['bootstrap', 'minimumrun', 'mediumrun'], } Tags are useful for things like applying a subset of a host's configuration with [the `tags` setting](/references/latest/configuration.html#tags): puppet agent --test --tags bootstrap This way, you can easily isolate the portion of the configuration you're trying to test." munge do |tags| tags = [tags] unless tags.is_a? Array tags.each do |tag| @resource.tag(tag) end end end class RelationshipMetaparam < Puppet::Parameter class << self attr_accessor :direction, :events, :callback, :subclasses end @subclasses = [] def self.inherited(sub) @subclasses << sub end def munge(references) references = [references] unless references.is_a?(Array) references.collect do |ref| if ref.is_a?(Puppet::Resource) ref else Puppet::Resource.new(ref) end end end def validate_relationship @value.each do |ref| unless @resource.catalog.resource(ref.to_s) description = self.class.direction == :in ? "dependency" : "dependent" fail "Could not find #{description} #{ref} for #{resource.ref}" end end end # Create edges from each of our relationships. :in # relationships are specified by the event-receivers, and :out # relationships are specified by the event generator. This # way 'source' and 'target' are consistent terms in both edges # and events -- that is, an event targets edges whose source matches # the event's source. The direction of the relationship determines # which resource is applied first and which resource is considered # to be the event generator. def to_edges @value.collect do |reference| reference.catalog = resource.catalog # Either of the two retrieval attempts could have returned # nil. unless related_resource = reference.resolve self.fail "Could not retrieve dependency '#{reference}' of #{@resource.ref}" end # Are we requiring them, or vice versa? See the method docs # for futher info on this. if self.class.direction == :in source = related_resource target = @resource else source = @resource target = related_resource end if method = self.class.callback subargs = { :event => self.class.events, :callback => method } self.debug("subscribes to #{related_resource.ref}") else # If there's no callback, there's no point in even adding # a label. subargs = nil self.debug("requires #{related_resource.ref}") end rel = Puppet::Relationship.new(source, target, subargs) end end end def self.relationship_params RelationshipMetaparam.subclasses end # Note that the order in which the relationships params is defined # matters. The labelled params (notify and subcribe) must be later, # so that if both params are used, those ones win. It's a hackish # solution, but it works. newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do desc "References to one or more objects that this object depends on. This is used purely for guaranteeing that changes to required objects happen before the dependent object. For instance: # Create the destination directory before you copy things down file { \"/usr/local/scripts\": ensure => directory } file { \"/usr/local/scripts/myscript\": source => \"puppet://server/module/myscript\", mode => 755, require => File[\"/usr/local/scripts\"] } Multiple dependencies can be specified by providing a comma-seperated list of resources, enclosed in square brackets: require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ] Note that Puppet will autorequire everything that it can, and there are hooks in place so that it's easy for resources to add new ways to autorequire objects, so if you think Puppet could be smarter here, let us know. In fact, the above code was redundant --- Puppet will autorequire any parent directories that are being managed; it will automatically realize that the parent directory should be created before the script is pulled down. Currently, exec resources will autorequire their CWD (if it is specified) plus any fully qualified paths that appear in the command. For instance, if you had an `exec` command that ran the `myscript` mentioned above, the above code that pulls the file down would be automatically listed as a requirement to the `exec` code, so that you would always be running againts the most recent version. " end newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do desc "References to one or more objects that this object depends on. This metaparameter creates a dependency relationship like **require,** and also causes the dependent object to be refreshed when the subscribed object is changed. For instance: class nagios { file { 'nagconf': path => \"/etc/nagios/nagios.conf\" source => \"puppet://server/module/nagios.conf\", } service { 'nagios': ensure => running, subscribe => File['nagconf'] } } Currently the `exec`, `mount` and `service` types support refreshing. " end newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do desc %{References to one or more objects that depend on this object. This parameter is the opposite of **require** --- it guarantees that the specified object is applied later than the specifying object: file { "/var/nagios/configuration": source => "...", recurse => true, before => Exec["nagios-rebuid"] } exec { "nagios-rebuild": command => "/usr/bin/make", cwd => "/var/nagios/configuration" } This will make sure all of the files are up to date before the make command is run.} end newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do desc %{References to one or more objects that depend on this object. This parameter is the opposite of **subscribe** --- it creates a dependency relationship like **before,** and also causes the dependent object(s) to be refreshed when this object is changed. For instance: file { "/etc/sshd_config": source => "....", notify => Service['sshd'] } service { 'sshd': ensure => running } This will restart the sshd service if the sshd config file changes.} end newmetaparam(:stage) do desc %{Which run stage a given resource should reside in. This just creates a dependency on or from the named milestone. For instance, saying that this is in the 'bootstrap' stage creates a dependency on the 'bootstrap' milestone. By default, all classes get directly added to the 'main' stage. You can create new stages as resources: stage { ['pre', 'post']: } To order stages, use standard relationships: stage { 'pre': before => Stage['main'] } Or use the new relationship syntax: Stage['pre'] -> Stage['main'] -> Stage['post'] Then use the new class parameters to specify a stage: class { 'foo': stage => 'pre' } Stages can only be set on classes, not individual resources. This will fail: file { '/foo': stage => 'pre', ensure => file } } end ############################### # All of the provider plumbing for the resource types. require 'puppet/provider' require 'puppet/util/provider_features' # Add the feature handling module. extend Puppet::Util::ProviderFeatures attr_reader :provider # the Type class attribute accessors class << self attr_accessor :providerloader attr_writer :defaultprovider end # Find the default provider. def self.defaultprovider - unless @defaultprovider - suitable = suitableprovider + return @defaultprovider if @defaultprovider - # Find which providers are a default for this system. - defaults = suitable.find_all { |provider| provider.default? } + suitable = suitableprovider - # If we don't have any default we use suitable providers - defaults = suitable if defaults.empty? - max = defaults.collect { |provider| provider.specificity }.max - defaults = defaults.find_all { |provider| provider.specificity == max } + # Find which providers are a default for this system. + defaults = suitable.find_all { |provider| provider.default? } - retval = nil - if defaults.length > 1 - Puppet.warning( - "Found multiple default providers for #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}" - ) - retval = defaults.shift - elsif defaults.length == 1 - retval = defaults.shift - else - raise Puppet::DevError, "Could not find a default provider for #{self.name}" - end + # If we don't have any default we use suitable providers + defaults = suitable if defaults.empty? + max = defaults.collect { |provider| provider.specificity }.max + defaults = defaults.find_all { |provider| provider.specificity == max } - @defaultprovider = retval + if defaults.length > 1 + Puppet.warning( + "Found multiple default providers for #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}" + ) end - @defaultprovider + @defaultprovider = defaults.shift unless defaults.empty? end def self.provider_hash_by_type(type) @provider_hashes ||= {} @provider_hashes[type] ||= {} end def self.provider_hash Puppet::Type.provider_hash_by_type(self.name) end # Retrieve a provider by name. def self.provider(name) name = Puppet::Util.symbolize(name) # If we don't have it yet, try loading it. @providerloader.load(name) unless provider_hash.has_key?(name) provider_hash[name] end # Just list all of the providers. def self.providers provider_hash.keys end def self.validprovider?(name) name = Puppet::Util.symbolize(name) (provider_hash.has_key?(name) && provider_hash[name].suitable?) end # Create a new provider of a type. This method must be called # directly on the type that it's implementing. def self.provide(name, options = {}, &block) name = Puppet::Util.symbolize(name) if unprovide(name) Puppet.debug "Reloading #{name} #{self.name} provider" end parent = if pname = options[:parent] options.delete(:parent) if pname.is_a? Class pname else if provider = self.provider(pname) provider else raise Puppet::DevError, "Could not find parent provider #{pname} of #{name}" end end else Puppet::Provider end options[:resource_type] ||= self self.providify provider = genclass( name, :parent => parent, :hash => provider_hash, :prefix => "Provider", :block => block, :include => feature_module, :extend => feature_module, :attributes => options ) provider end # Make sure we have a :provider parameter defined. Only gets called if there # are providers. def self.providify return if @paramhash.has_key? :provider newparam(:provider) do # We're using a hacky way to get the name of our type, since there doesn't # seem to be a correct way to introspect this at the time this code is run. # We expect that the class in which this code is executed will be something # like Puppet::Type::Ssh_authorized_key::ParameterProvider. desc <<-EOT The specific backend to use for this `#{self.to_s.split('::')[2].downcase}` resource. You will seldom need to specify this --- Puppet will usually discover the appropriate provider for your platform. EOT # This is so we can refer back to the type to get a list of # providers for documentation. class << self attr_accessor :parenttype end # We need to add documentation for each provider. def self.doc @doc + " Available providers are:\n\n" + parenttype.providers.sort { |a,b| a.to_s <=> b.to_s }.collect { |i| "* **#{i}**: #{parenttype().provider(i).doc}" }.join("\n") end defaultto { - @resource.class.defaultprovider.name + prov = @resource.class.defaultprovider + prov.name if prov } validate do |provider_class| provider_class = provider_class[0] if provider_class.is_a? Array provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider) unless provider = @resource.class.provider(provider_class) raise ArgumentError, "Invalid #{@resource.class.name} provider '#{provider_class}'" end end munge do |provider| provider = provider[0] if provider.is_a? Array provider = provider.intern if provider.is_a? String @resource.provider = provider if provider.is_a?(Puppet::Provider) provider.class.name else provider end end end.parenttype = self end def self.unprovide(name) if @defaultprovider and @defaultprovider.name == name @defaultprovider = nil end rmclass(name, :hash => provider_hash, :prefix => "Provider") end # Return an array of all of the suitable providers. def self.suitableprovider providerloader.loadall if provider_hash.empty? provider_hash.find_all { |name, provider| provider.suitable? }.collect { |name, provider| provider }.reject { |p| p.name == :fake } # For testing end + def suitable? + # If we don't use providers, then we consider it suitable. + return true unless self.class.paramclass(:provider) + + # We have a provider and it is suitable. + return true if provider && provider.class.suitable? + + # We're using the default provider and there is one. + if !provider and self.class.defaultprovider + self.provider = self.class.defaultprovider.name + return true + end + + # We specified an unsuitable provider, or there isn't any suitable + # provider. + false + end + def provider=(name) if name.is_a?(Puppet::Provider) @provider = name @provider.resource = self elsif klass = self.class.provider(name) @provider = klass.new(self) else raise ArgumentError, "Could not find #{name} provider of #{self.class.name}" end end ############################### # All of the relationship code. # Specify a block for generating a list of objects to autorequire. This # makes it so that you don't have to manually specify things that you clearly # require. def self.autorequire(name, &block) @autorequires ||= {} @autorequires[name] = block end # Yield each of those autorequires in turn, yo. def self.eachautorequire @autorequires ||= {} @autorequires.each { |type, block| yield(type, block) } end # Figure out of there are any objects we can automatically add as # dependencies. def autorequire(rel_catalog = nil) rel_catalog ||= catalog raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog reqs = [] self.class.eachautorequire { |type, block| # Ignore any types we can't find, although that would be a bit odd. next unless typeobj = Puppet::Type.type(type) # Retrieve the list of names from the block. next unless list = self.instance_eval(&block) list = [list] unless list.is_a?(Array) # Collect the current prereqs list.each { |dep| # Support them passing objects directly, to save some effort. unless dep.is_a? Puppet::Type # Skip autorequires that we aren't managing unless dep = rel_catalog.resource(type, dep) next end end reqs << Puppet::Relationship.new(dep, self) } } reqs end # Build the dependencies associated with an individual object. def builddepends # Handle the requires self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.to_edges end end.flatten.reject { |r| r.nil? } end # Define the initial list of tags. def tags=(list) tag(self.class.name) tag(*list) end # Types (which map to resources in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an # 'attribute', but these attributes always take one of three specific # forms: parameters, metaparams, or properties. # In naming methods, I have tried to consistently name the method so # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_writer :title attr_writer :noop include Enumerable # class methods dealing with Type management public # the Type class attribute accessors class << self attr_reader :name attr_accessor :self_refresh include Enumerable, Puppet::Util::ClassGen include Puppet::MetaType::Manager include Puppet::Util include Puppet::Util::Logging end # all of the variables that must be initialized for each subclass def self.initvars # all of the instances of this class @objects = Hash.new @aliases = Hash.new @defaults = {} @parameters ||= [] @validproperties = {} @properties = [] @parameters = [] @paramhash = {} @attr_aliases = {} @paramdoc = Hash.new { |hash,key| key = key.intern if key.is_a?(String) if hash.include?(key) hash[key] else "Param Documentation for #{key} not found" end } @doc ||= "" end def self.to_s if defined?(@name) "Puppet::Type::#{@name.to_s.capitalize}" else super end end # Create a block to validate that our object is set up entirely. This will # be run before the object is operated on. def self.validate(&block) define_method(:validate, &block) #@validate = block end # The catalog that this resource is stored in. attr_accessor :catalog # is the resource exported attr_accessor :exported # is the resource virtual (it should not :-)) attr_accessor :virtual # create a log at specified level def log(msg) Puppet::Util::Log.create( :level => @parameters[:loglevel].value, :message => msg, :source => self ) end # instance methods related to instance intrinsics # e.g., initialize and name public attr_reader :original_parameters # initialize the type instance def initialize(resource) raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject) resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource) # The list of parameter/property instances. @parameters = {} # Set the title first, so any failures print correctly. if resource.type.to_s.downcase.to_sym == self.class.name self.title = resource.title else # This should only ever happen for components self.title = resource.ref end [:file, :line, :catalog, :exported, :virtual].each do |getter| setter = getter.to_s + "=" if val = resource.send(getter) self.send(setter, val) end end @tags = resource.tags @original_parameters = resource.to_hash set_name(@original_parameters) set_default(:provider) set_parameters(@original_parameters) self.validate if self.respond_to?(:validate) end private # Set our resource's name. def set_name(hash) self[name_var] = hash.delete(name_var) if name_var end # Set all of the parameters from a hash, in the appropriate order. def set_parameters(hash) # Use the order provided by allattrs, but add in any # extra attributes from the resource so we get failures # on invalid attributes. no_values = [] (self.class.allattrs + hash.keys).uniq.each do |attr| begin # Set any defaults immediately. This is mostly done so # that the default provider is available for any other # property validation. if hash.has_key?(attr) self[attr] = hash[attr] else no_values << attr end rescue ArgumentError, Puppet::Error, TypeError raise rescue => detail error = Puppet::DevError.new( "Could not set #{attr} on #{self.class.name}: #{detail}") error.set_backtrace(detail.backtrace) raise error end end no_values.each do |attr| set_default(attr) end end public # Set up all of our autorequires. def finish # Make sure all of our relationships are valid. Again, must be done # when the entire catalog is instantiated. self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.validate_relationship end end.flatten.reject { |r| r.nil? } end # For now, leave the 'name' method functioning like it used to. Once 'title' # works everywhere, I'll switch it. def name self[:name] end # Look up our parent in the catalog, if we have one. def parent return nil unless catalog unless defined?(@parent) if parents = catalog.adjacent(self, :direction => :in) # We should never have more than one parent, so let's just ignore # it if we happen to. @parent = parents.shift else @parent = nil end end @parent end # Return the "type[name]" style reference. def ref "#{self.class.name.to_s.capitalize}[#{self.title}]" end def self_refresh? self.class.self_refresh end # Mark that we're purging. def purging @purging = true end # Is this resource being purged? Used by transactions to forbid # deletion when there are dependencies. def purging? if defined?(@purging) @purging else false end end # Retrieve the title of an object. If no title was set separately, # then use the object's name. def title unless @title if self.class.validparameter?(name_var) @title = self[:name] elsif self.class.validproperty?(name_var) @title = self.should(name_var) else self.devfail "Could not find namevar #{name_var} for #{self.class.name}" end end @title end # convert to a string def to_s self.ref end # Convert to a transportable object def to_trans(ret = true) trans = TransObject.new(self.title, self.class.name) values = retrieve_resource values.each do |name, value| name = name.name if name.respond_to? :name trans[name] = value end @parameters.each do |name, param| # Avoid adding each instance name twice next if param.class.isnamevar? and param.value == self.title # We've already got property values next if param.is_a?(Puppet::Property) trans[name] = param.value end trans.tags = self.tags # FIXME I'm currently ignoring 'parent' and 'path' trans end def to_resource # this 'type instance' versus 'resource' distinction seems artificial # I'd like to see it collapsed someday ~JW self.to_trans.to_resource end def virtual?; !!@virtual; end def exported?; !!@exported; end def appliable_to_device? self.class.can_apply_to(:device) end def appliable_to_host? self.class.can_apply_to(:host) end end end require 'puppet/provider' # Always load these types. Puppet::Type.type(:component) diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb index 96d6644cf..b246652a0 100755 --- a/lib/puppet/type/file/mode.rb +++ b/lib/puppet/type/file/mode.rb @@ -1,78 +1,85 @@ # Manage file modes. This state should support different formats # for specification (e.g., u+rwx, or -0011), but for now only supports # specifying the full mode. module Puppet Puppet::Type.type(:file).newproperty(:mode) do desc "Mode the file should be. Currently relatively limited: you must specify the exact mode the file should be. Note that when you set the mode of a directory, Puppet always sets the search/traverse (1) bit anywhere the read (4) bit is set. This is almost always what you want: read allows you to list the entries in a directory, and search/traverse allows you to access (read/write/execute) those entries.) Because of this feature, you can recursively make a directory and all of the files in it world-readable by setting e.g.: file { '/some/dir': mode => 644, recurse => true, } In this case all of the files underneath `/some/dir` will have mode 644, and all of the directories will have mode 755." validate do |value| if value.is_a?(String) and value !~ /^[0-7]+$/ raise Puppet::Error, "File modes can only be octal numbers, not #{should.inspect}" end end munge do |should| if should.is_a?(String) should.to_i(8).to_s(8) else should.to_s(8) end end # If we're a directory, we need to be executable for all cases # that are readable. This should probably be selectable, but eh. def dirmask(value) if FileTest.directory?(resource[:path]) value = value.to_i(8) value |= 0100 if value & 0400 != 0 value |= 010 if value & 040 != 0 value |= 01 if value & 04 != 0 value = value.to_s(8) end value end # If we're not following links and we're a link, then we just turn # off mode management entirely. def insync?(currentvalue) if stat = @resource.stat and stat.ftype == "link" and @resource[:links] != :follow self.debug "Not managing symlink mode" return true else return super(currentvalue) end end # Ideally, dirmask'ing could be done at munge time, but we don't know if 'ensure' # will eventually be a directory or something else. And unfortunately, that logic # depends on the ensure, source, and target properties. So rather than duplicate # that logic, and get it wrong, we do dirmask during retrieve, after 'ensure' has # been synced. def retrieve if @resource.stat @should &&= @should.collect { |s| self.dirmask(s) } end super end + + def should_to_s(should_value) + should_value.rjust(4,"0") + end + + def is_to_s(currentvalue) + currentvalue.rjust(4,"0") + end end end - diff --git a/lib/puppet/util/settings/file_setting.rb b/lib/puppet/util/settings/file_setting.rb index f02a0c547..9bc931108 100644 --- a/lib/puppet/util/settings/file_setting.rb +++ b/lib/puppet/util/settings/file_setting.rb @@ -1,125 +1,137 @@ require 'puppet/util/settings/setting' # A file. class Puppet::Util::Settings::FileSetting < Puppet::Util::Settings::Setting AllowedOwners = %w{root service} AllowedGroups = %w{root service} class SettingError < StandardError; end attr_accessor :mode, :create # Should we create files, rather than just directories? def create_files? create end def group=(value) unless AllowedGroups.include?(value) identifying_fields = [desc,name,default].compact.join(': ') raise SettingError, "Internal error: The :group setting for #{identifying_fields} must be 'service', not '#{value}'" end @group = value end def group return unless @group @settings[:group] end def owner=(value) unless AllowedOwners.include?(value) identifying_fields = [desc,name,default].compact.join(': ') raise SettingError, "Internal error: The :owner setting for #{identifying_fields} must be either 'root' or 'service', not '#{value}'" end @owner = value end def owner return unless @owner return "root" if @owner == "root" or ! use_service_user? @settings[:user] end def use_service_user? @settings[:mkusers] or @settings.service_user_available? end # Set the type appropriately. Yep, a hack. This supports either naming # the variable 'dir', or adding a slash at the end. def munge(value) # If it's not a fully qualified path... if value.is_a?(String) and value !~ /^\$/ and value != 'false' # Make it one value = File.expand_path(value) end if value.to_s =~ /\/$/ @type = :directory return value.sub(/\/$/, '') end value end # Return the appropriate type. def type value = @settings.value(self.name) if @name.to_s =~ /dir/ return :directory elsif value.to_s =~ /\/$/ return :directory elsif value.is_a? String return :file else return nil end end # Turn our setting thing into a Puppet::Resource instance. def to_resource return nil unless type = self.type path = self.value return nil unless path.is_a?(String) # Make sure the paths are fully qualified. path = File.expand_path(path) return nil unless type == :directory or create_files? or File.exist?(path) return nil if path =~ /^\/dev/ or path =~ /^[A-Z]:\/dev/i resource = Puppet::Resource.new(:file, path) if Puppet[:manage_internal_file_permissions] - resource[:mode] = self.mode if self.mode + if self.mode + # This ends up mimicking the munge method of the mode + # parameter to make sure that we're always passing the string + # version of the octal number. If we were setting the + # 'should' value for mode rather than the 'is', then the munge + # method would be called for us automatically. Normally, one + # wouldn't need to call the munge method manually, since + # 'should' gets set by the provider and it should be able to + # provide the data in the appropriate format. + mode = self.mode + mode = mode.to_i(8) if mode.is_a?(String) + mode = mode.to_s(8) + resource[:mode] = mode + end # REMIND fails on Windows because chown/chgrp functionality not supported yet if Puppet.features.root? and !Puppet.features.microsoft_windows? resource[:owner] = self.owner if self.owner resource[:group] = self.group if self.group end end resource[:ensure] = type resource[:loglevel] = :debug resource[:links] = :follow resource[:backup] = false resource.tag(self.section, self.name, "settings") resource end # Make sure any provided variables look up to something. def validate(value) return true unless value.is_a? String value.scan(/\$(\w+)/) { |name| name = $1 unless @settings.include?(name) raise ArgumentError, "Settings parameter '#{name}' is undefined" end } end end - diff --git a/spec/integration/provider/service/init_spec.rb b/spec/integration/provider/service/init_spec.rb index c209475bf..483507d5f 100755 --- a/spec/integration/provider/service/init_spec.rb +++ b/spec/integration/provider/service/init_spec.rb @@ -1,24 +1,30 @@ #!/usr/bin/env rspec require 'spec_helper' provider = Puppet::Type.type(:service).provider(:init) describe provider, :'fails_on_ruby_1.9.2' => true do describe "when running on FreeBSD", :if => (Facter.value(:operatingsystem) == "FreeBSD") do - it "should set its default path to include /etc/init.d and /usr/local/etc/init.d" do + it "should set its default path to include /etc/rc.d and /usr/local/etc/rc.d" do provider.defpath.should == ["/etc/rc.d", "/usr/local/etc/rc.d"] end end describe "when running on HP-UX", :if => (Facter.value(:operatingsystem) == "HP-UX") do it "should set its default path to include /sbin/init.d" do provider.defpath.should == "/sbin/init.d" end end - describe "when not running on FreeBSD or HP-UX", :if => (! %w{HP-UX FreeBSD}.include?(Facter.value(:operatingsystem))) do + describe "when running on Archlinux", :if => (Facter.value(:operatingsystem) == "Archlinux") do + it "should set its default path to include /etc/rc.d" do + provider.defpath.should == "/etc/rc.d" + end + end + + describe "when not running on FreeBSD, HP-UX or Archlinux", :if => (! %w{HP-UX FreeBSD Archlinux}.include?(Facter.value(:operatingsystem))) do it "should set its default path to include /etc/init.d" do provider.defpath.should == "/etc/init.d" end end end diff --git a/spec/integration/reference/providers_spec.rb b/spec/integration/reference/providers_spec.rb index 6d87ee02e..c2b83588d 100755 --- a/spec/integration/reference/providers_spec.rb +++ b/spec/integration/reference/providers_spec.rb @@ -1,16 +1,16 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/reference' reference = Puppet::Util::Reference.reference(:providers) describe reference do it "should exist" do reference.should_not be_nil end it "should be able to be rendered as markdown" do - lambda { reference.to_markdown }.should_not raise_error + reference.to_markdown end end diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index c1f85d501..b12c4fae9 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -1,398 +1,389 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application/apply' require 'puppet/file_bucket/dipper' require 'puppet/configurer' require 'fileutils' describe Puppet::Application::Apply do before :each do @apply = Puppet::Application[:apply] Puppet::Util::Log.stubs(:newdestination) end after :each do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.cache_class = nil Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end [:debug,:loadclasses,:verbose,:use_nodes,:detailed_exitcodes].each do |option| it "should declare handle_#{option} method" do @apply.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @apply.options.expects(:[]=).with(option, 'arg') @apply.send("handle_#{option}".to_sym, 'arg') end end it "should set the code to the provided code when :execute is used" do @apply.options.expects(:[]=).with(:code, 'arg') @apply.send("handle_execute".to_sym, 'arg') end it "should ask Puppet::Application to parse Puppet configuration file" do @apply.should_parse_config?.should be_true end describe "when applying options" do it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @apply.handle_logdest("console") end it "should put the logset options to true" do @apply.options.expects(:[]=).with(:logset,true) @apply.handle_logdest("console") end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:parse_config) Puppet::FileBucket::Dipper.stubs(:new) STDIN.stubs(:read) Puppet::Transaction::Report.indirection.stubs(:cache_class=) @apply.options.stubs(:[]).with(any_parameters) end - it "should set show_diff on --noop" do - Puppet[:noop] = true - Puppet[:show_diff] = false - - @apply.setup - - Puppet[:show_diff].should == true - end - it "should set console as the log destination if logdest option wasn't provided" do Puppet::Log.expects(:newdestination).with(:console) @apply.setup end it "should set INT trap" do Signal.expects(:trap).with(:INT) @apply.setup end it "should set log level to debug if --debug was passed" do @apply.options.stubs(:[]).with(:debug).returns(true) @apply.setup Puppet::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @apply.options.stubs(:[]).with(:verbose).returns(true) @apply.setup Puppet::Log.level.should == :info end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns true Puppet.settings.expects(:print_configs).returns true expect { @apply.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @apply.setup }.to exit_with 1 end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @apply.setup end end describe "when executing" do it "should dispatch to 'apply' if it was called with 'apply'" do @apply.options[:catalog] = "foo" @apply.expects(:apply) @apply.run_command end it "should dispatch to main otherwise" do @apply.stubs(:options).returns({}) @apply.expects(:main) @apply.run_command end describe "the main command" do include PuppetSpec::Files before :each do Puppet[:prerun_command] = '' Puppet[:postrun_command] = '' Puppet::Node::Facts.indirection.terminus_class = :memory Puppet::Node::Facts.indirection.cache_class = :memory Puppet::Node.indirection.terminus_class = :memory Puppet::Node.indirection.cache_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) @node = Puppet::Node.new(Puppet[:node_name_value]) Puppet::Node.indirection.save(@node) @catalog = Puppet::Resource::Catalog.new @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.indirection.stubs(:find).returns(@catalog) STDIN.stubs(:read) @transaction = Puppet::Transaction.new(@catalog) @catalog.stubs(:apply).returns(@transaction) Puppet::Util::Storage.stubs(:load) Puppet::Configurer.any_instance.stubs(:save_last_run_summary) # to prevent it from trying to write files end it "should set the code to run from --code" do @apply.options[:code] = "code to run" Puppet.expects(:[]=).with(:code,"code to run") expect { @apply.main }.to exit_with 0 end it "should set the code to run from STDIN if no arguments" do @apply.command_line.stubs(:args).returns([]) STDIN.stubs(:read).returns("code to run") Puppet.expects(:[]=).with(:code,"code to run") expect { @apply.main }.to exit_with 0 end it "should set the manifest if a file is passed on command line and the file exists" do manifest = tmpfile('site.pp') FileUtils.touch(manifest) @apply.command_line.stubs(:args).returns([manifest]) Puppet.expects(:[]=).with(:manifest,manifest) expect { @apply.main }.to exit_with 0 end it "should raise an error if a file is passed on command line and the file does not exist" do noexist = tmpfile('noexist.pp') @apply.command_line.stubs(:args).returns([noexist]) lambda { @apply.main }.should raise_error(RuntimeError, "Could not find file #{noexist}") end it "should set the manifest to the first file and warn other files will be skipped" do manifest = tmpfile('starwarsIV') FileUtils.touch(manifest) @apply.command_line.stubs(:args).returns([manifest, 'starwarsI', 'starwarsII']) Puppet.expects(:[]=).with(:manifest,manifest) Puppet.expects(:warning).with('Only one file can be applied per run. Skipping starwarsI, starwarsII') expect { @apply.main }.to exit_with 0 end it "should set the facts name based on the node_name_fact" do @facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(@facts) node = Puppet::Node.new('other_node_name') Puppet::Node.indirection.save(node) Puppet[:node_name_fact] = 'my_name_fact' expect { @apply.main }.to exit_with 0 @facts.name.should == 'other_node_name' end it "should set the node_name_value based on the node_name_fact" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(facts) node = Puppet::Node.new('other_node_name') Puppet::Node.indirection.save(node) Puppet[:node_name_fact] = 'my_name_fact' expect { @apply.main }.to exit_with 0 Puppet[:node_name_value].should == 'other_node_name' end it "should raise an error if we can't find the facts" do Puppet::Node::Facts.indirection.expects(:find).returns(nil) lambda { @apply.main }.should raise_error end it "should raise an error if we can't find the node" do Puppet::Node.indirection.expects(:find).returns(nil) lambda { @apply.main }.should raise_error end it "should merge in our node the loaded facts" do @facts.values = {'key' => 'value'} expect { @apply.main }.to exit_with 0 @node.parameters['key'].should == 'value' end it "should load custom classes if loadclasses" do @apply.options[:loadclasses] = true classfile = tmpfile('classfile') File.open(classfile, 'w') { |c| c.puts 'class' } Puppet[:classfile] = classfile @node.expects(:classes=).with(['class']) expect { @apply.main }.to exit_with 0 end it "should compile the catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns(@catalog) expect { @apply.main }.to exit_with 0 end it "should transform the catalog to ral" do @catalog.expects(:to_ral).returns(@catalog) expect { @apply.main }.to exit_with 0 end it "should finalize the catalog" do @catalog.expects(:finalize) expect { @apply.main }.to exit_with 0 end it "should call the prerun and postrun commands on a Configurer instance" do Puppet::Configurer.any_instance.expects(:execute_prerun_command).returns(true) Puppet::Configurer.any_instance.expects(:execute_postrun_command).returns(true) expect { @apply.main }.to exit_with 0 end it "should apply the catalog" do @catalog.expects(:apply).returns(stub_everything('transaction')) expect { @apply.main }.to exit_with 0 end it "should save the last run summary" do Puppet[:noop] = false report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.stubs(:new).returns(report) Puppet::Configurer.any_instance.expects(:save_last_run_summary).with(report) expect { @apply.main }.to exit_with 0 end describe "with detailed_exitcodes" do before :each do @apply.options[:detailed_exitcodes] = true end it "should exit with report's computed exit status" do Puppet[:noop] = false Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) expect { @apply.main }.to exit_with 666 end it "should exit with report's computed exit status, even if --noop is set" do Puppet[:noop] = true Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) expect { @apply.main }.to exit_with 666 end it "should always exit with 0 if option is disabled" do Puppet[:noop] = false report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) expect { @apply.main }.to exit_with 0 end it "should always exit with 0 if --noop" do Puppet[:noop] = true report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) expect { @apply.main }.to exit_with 0 end end end describe "the 'apply' command" do it "should read the catalog in from disk if a file name is provided" do @apply.options[:catalog] = "/my/catalog.pson" File.expects(:read).with("/my/catalog.pson").returns "something" Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns Puppet::Resource::Catalog.new @apply.apply end it "should read the catalog in from stdin if '-' is provided" do @apply.options[:catalog] = "-" $stdin.expects(:read).returns "something" Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns Puppet::Resource::Catalog.new @apply.apply end it "should deserialize the catalog from the default format" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something" Puppet::Resource::Catalog.stubs(:default_format).returns :rot13_piglatin Puppet::Resource::Catalog.stubs(:convert_from).with(:rot13_piglatin,'something').returns Puppet::Resource::Catalog.new @apply.apply end it "should fail helpfully if deserializing fails" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something syntacically invalid" lambda { @apply.apply }.should raise_error(Puppet::Error) end it "should convert plain data structures into a catalog if deserialization does not do so" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something" Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,"something").returns({:foo => "bar"}) Puppet::Resource::Catalog.expects(:pson_create).with({:foo => "bar"}).returns(Puppet::Resource::Catalog.new) @apply.apply end it "should convert the catalog to a RAL catalog and use a Configurer instance to apply it" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something" catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns catalog catalog.expects(:to_ral).returns "mycatalog" configurer = stub 'configurer' Puppet::Configurer.expects(:new).returns configurer configurer.expects(:run).with(:catalog => "mycatalog") @apply.apply end end end end diff --git a/spec/unit/indirector/facts/inventory_service_spec.rb b/spec/unit/indirector/facts/inventory_service_spec.rb new file mode 100644 index 000000000..f44934abc --- /dev/null +++ b/spec/unit/indirector/facts/inventory_service_spec.rb @@ -0,0 +1,22 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/indirector/facts/inventory_service' + +describe Puppet::Node::Facts::InventoryService do + it "should suppress failures and warn when saving facts" do + facts = Puppet::Node::Facts.new('foo') + request = Puppet::Indirector::Request.new(:facts, :save, facts) + + Net::HTTP.any_instance.stubs(:put).raises(Errno::ECONNREFUSED) + + Puppet.expects(:warning).with do |msg| + msg =~ /Could not upload facts for foo to inventory service/ + end + + expect { + subject.save(request) + }.should_not raise_error + end +end + diff --git a/spec/unit/parser/parser_spec.rb b/spec/unit/parser/parser_spec.rb index 78adc30ee..beb046845 100755 --- a/spec/unit/parser/parser_spec.rb +++ b/spec/unit/parser/parser_spec.rb @@ -1,424 +1,428 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Parser do Puppet::Parser::AST before :each do @known_resource_types = Puppet::Resource::TypeCollection.new("development") @parser = Puppet::Parser::Parser.new "development" @parser.stubs(:known_resource_types).returns @known_resource_types @true_ast = Puppet::Parser::AST::Boolean.new :value => true end it "should require an environment at initialization" do lambda { Puppet::Parser::Parser.new }.should raise_error(ArgumentError) end it "should set the environment" do env = Puppet::Node::Environment.new Puppet::Parser::Parser.new(env).environment.should == env end it "should convert the environment into an environment instance if a string is provided" do env = Puppet::Node::Environment.new("testing") Puppet::Parser::Parser.new("testing").environment.should == env end it "should be able to look up the environment-specific resource type collection" do rtc = Puppet::Node::Environment.new("development").known_resource_types parser = Puppet::Parser::Parser.new "development" parser.known_resource_types.should equal(rtc) end it "should delegate importing to the known resource type loader" do parser = Puppet::Parser::Parser.new "development" parser.known_resource_types.loader.expects(:import).with("newfile", "current_file") parser.lexer.expects(:file).returns "current_file" parser.import("newfile") end describe "when parsing files" do before do FileTest.stubs(:exist?).returns true File.stubs(:read).returns "" @parser.stubs(:watch_file) end it "should treat files ending in 'rb' as ruby files" do @parser.expects(:parse_ruby_file) @parser.file = "/my/file.rb" @parser.parse end end describe "when parsing append operator" do it "should not raise syntax errors" do lambda { @parser.parse("$var += something") }.should_not raise_error end it "shouldraise syntax error on incomplete syntax " do lambda { @parser.parse("$var += ") }.should raise_error end it "should create ast::VarDef with append=true" do vardef = @parser.parse("$var += 2").code[0] vardef.should be_a(Puppet::Parser::AST::VarDef) vardef.append.should == true end it "should work with arrays too" do vardef = @parser.parse("$var += ['test']").code[0] vardef.should be_a(Puppet::Parser::AST::VarDef) vardef.append.should == true end end describe "when parsing selector" do it "should support hash access on the left hand side" do lambda { @parser.parse("$h = { 'a' => 'b' } $a = $h['a'] ? { 'b' => 'd', default => undef }") }.should_not raise_error end end describe "when parsing 'if'" do it "not, it should create the correct ast objects" do Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } @parser.parse("if ! true { $var = 1 }") end it "boolean operation, it should create the correct ast objects" do Puppet::Parser::AST::BooleanOperator.expects(:new).with { |h| h[:rval].is_a?(Puppet::Parser::AST::Boolean) and h[:lval].is_a?(Puppet::Parser::AST::Boolean) and h[:operator]=="or" } @parser.parse("if true or true { $var = 1 }") end it "comparison operation, it should create the correct ast objects" do Puppet::Parser::AST::ComparisonOperator.expects(:new).with { |h| h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="<" } @parser.parse("if 1 < 2 { $var = 1 }") end end describe "when parsing if complex expressions" do it "should create a correct ast tree" do aststub = stub_everything 'ast' Puppet::Parser::AST::ComparisonOperator.expects(:new).with { |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]==">" }.returns(aststub) Puppet::Parser::AST::ComparisonOperator.expects(:new).with { |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="==" }.returns(aststub) Puppet::Parser::AST::BooleanOperator.expects(:new).with { |h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and" } @parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }") end it "should raise an error on incorrect expression" do lambda { @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }") }.should raise_error end end describe "when parsing resource references" do it "should not raise syntax errors" do lambda { @parser.parse('exec { test: param => File["a"] }') }.should_not raise_error end it "should not raise syntax errors with multiple references" do lambda { @parser.parse('exec { test: param => File["a","b"] }') }.should_not raise_error end it "should create an ast::ResourceReference" do Puppet::Parser::AST::ResourceReference.expects(:new).with { |arg| arg[:line]==1 and arg[:type]=="File" and arg[:title].is_a?(Puppet::Parser::AST::ASTArray) } @parser.parse('exec { test: command => File["a","b"] }') end end describe "when parsing resource overrides" do it "should not raise syntax errors" do lambda { @parser.parse('Resource["title"] { param => value }') }.should_not raise_error end it "should not raise syntax errors with multiple overrides" do lambda { @parser.parse('Resource["title1","title2"] { param => value }') }.should_not raise_error end it "should create an ast::ResourceOverride" do #Puppet::Parser::AST::ResourceOverride.expects(:new).with { |arg| # arg[:line]==1 and arg[:object].is_a?(Puppet::Parser::AST::ResourceReference) and arg[:parameters].is_a?(Puppet::Parser::AST::ResourceParam) #} ro = @parser.parse('Resource["title1","title2"] { param => value }').code[0] ro.should be_a(Puppet::Parser::AST::ResourceOverride) ro.line.should == 1 ro.object.should be_a(Puppet::Parser::AST::ResourceReference) ro.parameters[0].should be_a(Puppet::Parser::AST::ResourceParam) end end describe "when parsing if statements" do it "should not raise errors with empty if" do lambda { @parser.parse("if true { }") }.should_not raise_error end it "should not raise errors with empty else" do lambda { @parser.parse("if false { notice('if') } else { }") }.should_not raise_error end it "should not raise errors with empty if and else" do lambda { @parser.parse("if false { } else { }") }.should_not raise_error end it "should create a nop node for empty branch" do Puppet::Parser::AST::Nop.expects(:new) @parser.parse("if true { }") end it "should create a nop node for empty else branch" do Puppet::Parser::AST::Nop.expects(:new) @parser.parse("if true { notice('test') } else { }") end it "should build a chain of 'ifs' if there's an 'elsif'" do lambda { @parser.parse(<<-PP) }.should_not raise_error if true { notice('test') } elsif true {} else { } PP end end describe "when parsing function calls" do it "should not raise errors with no arguments" do lambda { @parser.parse("tag()") }.should_not raise_error end it "should not raise errors with rvalue function with no args" do lambda { @parser.parse("$a = template()") }.should_not raise_error end it "should not raise errors with arguments" do lambda { @parser.parse("notice(1)") }.should_not raise_error end it "should not raise errors with multiple arguments" do lambda { @parser.parse("notice(1,2)") }.should_not raise_error end it "should not raise errors with multiple arguments and a trailing comma" do lambda { @parser.parse("notice(1,2,)") }.should_not raise_error end end describe "when parsing arrays with trailing comma" do it "should not raise errors with a trailing comma" do lambda { @parser.parse("$a = [1,2,]") }.should_not raise_error end end describe "when providing AST context" do before do @lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev" @parser.stubs(:lexer).returns @lexer end it "should include the lexer's line" do @parser.ast_context[:line].should == 50 end it "should include the lexer's file" do @parser.ast_context[:file].should == "/foo/bar" end it "should include the docs if directed to do so" do @parser.ast_context(true)[:doc].should == "whev" end it "should not include the docs when told not to" do @parser.ast_context(false)[:doc].should be_nil end it "should not include the docs by default" do @parser.ast_context[:doc].should be_nil end end describe "when building ast nodes" do before do @lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev" @parser.stubs(:lexer).returns @lexer @class = Puppet::Resource::Type.new(:hostclass, "myclass", :use_docs => false) end it "should return a new instance of the provided class created with the provided options" do @class.expects(:new).with { |opts| opts[:foo] == "bar" } @parser.ast(@class, :foo => "bar") end it "should merge the ast context into the provided options" do @class.expects(:new).with { |opts| opts[:file] == "/foo" } @parser.expects(:ast_context).returns :file => "/foo" @parser.ast(@class, :foo => "bar") end it "should prefer provided options over AST context" do @class.expects(:new).with { |opts| opts[:file] == "/bar" } @lexer.expects(:file).returns "/foo" @parser.ast(@class, :file => "/bar") end it "should include docs when the AST class uses them" do @class.expects(:use_docs).returns true @class.stubs(:new) @parser.expects(:ast_context).with{ |docs, line| docs == true }.returns({}) @parser.ast(@class, :file => "/bar") end it "should get docs from lexer using the correct AST line number" do @class.expects(:use_docs).returns true @class.stubs(:new).with{ |a| a[:doc] == "doc" } @lexer.expects(:getcomment).with(12).returns "doc" @parser.ast(@class, :file => "/bar", :line => 12) end end describe "when retrieving a specific node" do it "should delegate to the known_resource_types node" do @known_resource_types.expects(:node).with("node") @parser.node("node") end end describe "when retrieving a specific class" do it "should delegate to the loaded code" do @known_resource_types.expects(:hostclass).with("class") @parser.hostclass("class") end end describe "when retrieving a specific definitions" do it "should delegate to the loaded code" do @known_resource_types.expects(:definition).with("define") @parser.definition("define") end end describe "when determining the configuration version" do it "should determine it from the resource type collection" do @parser.known_resource_types.expects(:version).returns "foo" @parser.version.should == "foo" end end describe "when looking up definitions" do it "should use the known resource types to check for them by name" do @parser.known_resource_types.stubs(:find_or_load).with("namespace","name",:definition).returns(:this_value) @parser.find_definition("namespace","name").should == :this_value end end describe "when looking up hostclasses" do it "should use the known resource types to check for them by name" do @parser.known_resource_types.stubs(:find_or_load).with("namespace","name",:hostclass).returns(:this_value) @parser.find_hostclass("namespace","name").should == :this_value end end describe "when parsing classes" do before :each do @krt = Puppet::Resource::TypeCollection.new("development") @parser = Puppet::Parser::Parser.new "development" @parser.stubs(:known_resource_types).returns @krt end it "should not create new classes" do @parser.parse("class foobar {}").code[0].should be_a(Puppet::Parser::AST::Hostclass) @krt.hostclass("foobar").should be_nil end it "should correctly set the parent class when one is provided" do @parser.parse("class foobar inherits yayness {}").code[0].instantiate('')[0].parent.should == "yayness" end it "should correctly set the parent class for multiple classes at a time" do statements = @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}").code statements[0].instantiate('')[0].parent.should == "yayness" statements[1].instantiate('')[0].parent.should == "bar" end it "should define the code when some is provided" do @parser.parse("class foobar { $var = val }").code[0].code.should_not be_nil end + it "should accept parametrized classes with trailing comma" do + @parser.parse("class foobar ($var1 = 0,) { $var = val }").code[0].code.should_not be_nil + end + it "should define parameters when provided" do foobar = @parser.parse("class foobar($biz,$baz) {}").code[0].instantiate('')[0] foobar.arguments.should == {"biz" => nil, "baz" => nil} end end describe "when parsing resources" do before :each do @krt = Puppet::Resource::TypeCollection.new("development") @parser = Puppet::Parser::Parser.new "development" @parser.stubs(:known_resource_types).returns @krt end it "should be able to parse class resources" do @krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil})) lambda { @parser.parse("class { foobar: biz => stuff }") }.should_not raise_error end it "should correctly mark exported resources as exported" do @parser.parse("@@file { '/file': }").code[0].exported.should be_true end it "should correctly mark virtual resources as virtual" do @parser.parse("@file { '/file': }").code[0].virtual.should be_true end end describe "when parsing nodes" do it "should be able to parse a node with a single name" do node = @parser.parse("node foo { }").code[0] node.should be_a Puppet::Parser::AST::Node node.names.length.should == 1 node.names[0].value.should == "foo" end it "should be able to parse a node with two names" do node = @parser.parse("node foo, bar { }").code[0] node.should be_a Puppet::Parser::AST::Node node.names.length.should == 2 node.names[0].value.should == "foo" node.names[1].value.should == "bar" end it "should be able to parse a node with three names" do node = @parser.parse("node foo, bar, baz { }").code[0] node.should be_a Puppet::Parser::AST::Node node.names.length.should == 3 node.names[0].value.should == "foo" node.names[1].value.should == "bar" node.names[2].value.should == "baz" end end end diff --git a/spec/unit/provider/exec/windows_spec.rb b/spec/unit/provider/exec/windows_spec.rb index 28438de56..748646ac3 100755 --- a/spec/unit/provider/exec/windows_spec.rb +++ b/spec/unit/provider/exec/windows_spec.rb @@ -1,112 +1,113 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:exec).provider(:windows) do include PuppetSpec::Files let(:resource) { Puppet::Type.type(:exec).new(:title => 'C:\foo', :provider => :windows) } let(:provider) { described_class.new(resource) } before :each do + Facter.stubs(:value).with(:operatingsystem).returns 'Windows' Puppet.features.stubs(:microsoft_windows?).returns(true) Puppet.features.stubs(:posix?).returns(false) end after :all do # This provider may not be suitable on some machines, so we want to reset # the default so it isn't used by mistake in future specs. Puppet::Type.type(:exec).defaultprovider = nil end describe "#extractexe" do describe "when the command has no arguments" do it "should return the command if it's quoted" do provider.extractexe('"foo"').should == 'foo' end it "should return the command if it's quoted and contains spaces" do provider.extractexe('"foo bar"').should == 'foo bar' end it "should return the command if it's not quoted" do provider.extractexe('foo').should == 'foo' end end describe "when the command has arguments" do it "should return the command if it's quoted" do provider.extractexe('"foo" bar baz').should == 'foo' end it "should return the command if it's quoted and contains spaces" do provider.extractexe('"foo bar" baz "quux quiz"').should == 'foo bar' end it "should return the command if it's not quoted" do provider.extractexe('foo bar baz').should == 'foo' end end end describe "#checkexe" do describe "when the command is absolute", :if => Puppet.features.microsoft_windows? do it "should return if the command exists and is a file" do command = tmpfile('command') FileUtils.touch(command) provider.checkexe(command).should == nil end it "should fail if the command doesn't exist" do command = tmpfile('command') expect { provider.checkexe(command) }.to raise_error(ArgumentError, "Could not find command '#{command}'") end it "should fail if the command isn't a file" do command = tmpfile('command') FileUtils.mkdir(command) expect { provider.checkexe(command) }.to raise_error(ArgumentError, "'#{command}' is a directory, not a file") end end describe "when the command is relative" do describe "and a path is specified" do before :each do provider.stubs(:which) end it "should search for executables with no extension" do provider.resource[:path] = [File.expand_path('/bogus/bin')] provider.expects(:which).with('foo').returns('foo') provider.checkexe('foo') end it "should fail if the command isn't in the path" do expect { provider.checkexe('foo') }.to raise_error(ArgumentError, "Could not find command 'foo'") end end it "should fail if no path is specified" do expect { provider.checkexe('foo') }.to raise_error(ArgumentError, "Could not find command 'foo'") end end end describe "#validatecmd" do it "should fail if the command isn't absolute and there is no path" do expect { provider.validatecmd('foo') }.to raise_error(Puppet::Error, /'foo' is not qualified and no path was specified/) end it "should not fail if the command is absolute and there is no path" do provider.validatecmd('C:\foo').should == nil end it "should not fail if the command is not absolute and there is a path" do resource[:path] = 'C:\path;C:\another_path' provider.validatecmd('foo').should == nil end end end diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index 95b605bf7..dd6434359 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -1,837 +1,886 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/host' describe Puppet::SSL::Host do include PuppetSpec::Files before do Puppet::SSL::Host.indirection.terminus_class = :file # Get a safe temporary file dir = tmpdir("ssl_host_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings.use :main, :ssl @host = Puppet::SSL::Host.new("myname") end after do # Cleaned out any cached localhost instance. Puppet::SSL::Host.reset Puppet::SSL::Host.ca_location = :none end it "should use any provided name as its name" do @host.name.should == "myname" end it "should retrieve its public key from its private key" do realkey = mock 'realkey' key = stub 'key', :content => realkey Puppet::SSL::Key.indirection.stubs(:find).returns(key) pubkey = mock 'public_key' realkey.expects(:public_key).returns pubkey @host.public_key.should equal(pubkey) end it "should default to being a non-ca host" do @host.ca?.should be_false end it "should be a ca host if its name matches the CA_NAME" do Puppet::SSL::Host.stubs(:ca_name).returns "yayca" Puppet::SSL::Host.new("yayca").should be_ca end it "should have a method for determining the CA location" do Puppet::SSL::Host.should respond_to(:ca_location) end it "should have a method for specifying the CA location" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for retrieving the default ssl host" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for producing an instance to manage the local host's keys" do Puppet::SSL::Host.should respond_to(:localhost) end it "should allow to reset localhost" do previous_host = Puppet::SSL::Host.localhost Puppet::SSL::Host.reset Puppet::SSL::Host.localhost.should_not == previous_host end it "should generate the certificate for the localhost instance if no certificate is available" do host = stub 'host', :key => nil Puppet::SSL::Host.expects(:new).returns host host.expects(:certificate).returns nil host.expects(:generate) Puppet::SSL::Host.localhost.should equal(host) end it "should create a localhost cert if no cert is available and it is a CA with autosign and it is using DNS alt names", :unless => Puppet.features.microsoft_windows? do Puppet[:autosign] = true Puppet[:confdir] = tmpdir('conf') Puppet[:dns_alt_names] = "foo,bar,baz" ca = Puppet::SSL::CertificateAuthority.new Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca localhost = Puppet::SSL::Host.localhost cert = localhost.certificate cert.should be_a(Puppet::SSL::Certificate) cert.subject_alt_names.should =~ %W[DNS:#{Puppet[:certname]} DNS:foo DNS:bar DNS:baz] end context "with dns_alt_names" do before :each do - Puppet[:dns_alt_names] = 'one, two' - @key = stub('key content') key = stub('key', :generate => true, :content => @key) Puppet::SSL::Key.stubs(:new).returns key Puppet::SSL::Key.indirection.stubs(:save).with(key) @cr = stub('certificate request') Puppet::SSL::CertificateRequest.stubs(:new).returns @cr Puppet::SSL::CertificateRequest.indirection.stubs(:save).with(@cr) end - it "should not include subjectAltName if not the local node" do - @cr.expects(:generate).with(@key, {}) + describe "explicitly specified" do + before :each do + Puppet[:dns_alt_names] = 'one, two' + end + + it "should not include subjectAltName if not the local node" do + @cr.expects(:generate).with(@key, {}) - Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate + Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate + end + + it "should include subjectAltName if I am a CA" do + @cr.expects(:generate). + with(@key, { :dns_alt_names => Puppet[:dns_alt_names] }) + + Puppet::SSL::Host.localhost + end end - it "should include subjectAltName if I am a CA" do - @cr.expects(:generate). - with(@key, { :dns_alt_names => Puppet[:dns_alt_names] }) + describe "implicitly defaulted" do + let(:ca) { stub('ca', :sign => nil) } - Puppet::SSL::Host.localhost + before :each do + Puppet[:dns_alt_names] = '' + + Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca + end + + it "should not include defaults if we're not the CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false + + @cr.expects(:generate).with(@key, {}) + + Puppet::SSL::Host.localhost + end + + it "should not include defaults if not the local node" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true + + @cr.expects(:generate).with(@key, {}) + + Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate + end + + it "should not include defaults if we can't resolve our fqdn" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true + Facter.stubs(:value).with(:fqdn).returns nil + + @cr.expects(:generate).with(@key, {}) + + Puppet::SSL::Host.localhost + end + + it "should provide defaults if we're bootstrapping the local master" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true + Facter.stubs(:value).with(:fqdn).returns 'web.foo.com' + Facter.stubs(:value).with(:domain).returns 'foo.com' + + @cr.expects(:generate).with(@key, {:dns_alt_names => "puppet, web.foo.com, puppet.foo.com"}) + + Puppet::SSL::Host.localhost + end end end it "should always read the key for the localhost instance in from disk" do host = stub 'host', :certificate => "eh" Puppet::SSL::Host.expects(:new).returns host host.expects(:key) Puppet::SSL::Host.localhost end it "should cache the localhost instance" do host = stub 'host', :certificate => "eh", :key => 'foo' Puppet::SSL::Host.expects(:new).once.returns host Puppet::SSL::Host.localhost.should == Puppet::SSL::Host.localhost end it "should be able to verify its certificate matches its key" do Puppet::SSL::Host.new("foo").should respond_to(:certificate_matches_key?) end it "should consider the certificate invalid if it cannot find a key" do host = Puppet::SSL::Host.new("foo") host.expects(:key).returns nil host.should_not be_certificate_matches_key end it "should consider the certificate invalid if it cannot find a certificate" do host = Puppet::SSL::Host.new("foo") host.expects(:key).returns mock("key") host.expects(:certificate).returns nil host.should_not be_certificate_matches_key end it "should consider the certificate invalid if the SSL certificate's key verification fails" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns false host.should_not be_certificate_matches_key end it "should consider the certificate valid if the SSL certificate's key verification succeeds" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns true host.should be_certificate_matches_key end describe "when specifying the CA location" do it "should support the location ':local'" do lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error end it "should support the location ':remote'" do lambda { Puppet::SSL::Host.ca_location = :remote }.should_not raise_error end it "should support the location ':none'" do lambda { Puppet::SSL::Host.ca_location = :none }.should_not raise_error end it "should support the location ':only'" do lambda { Puppet::SSL::Host.ca_location = :only }.should_not raise_error end it "should not support other modes" do lambda { Puppet::SSL::Host.ca_location = :whatever }.should raise_error(ArgumentError) end describe "as 'local'" do before do Puppet::SSL::Host.ca_location = :local end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key and Host as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Host.indirection.terminus_class.should == :file end it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end end describe "as 'remote'" do before do Puppet::SSL::Host.ca_location = :remote end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file end it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do Puppet::SSL::Host.indirection.terminus_class.should == :rest Puppet::SSL::Certificate.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :rest end end describe "as 'only'" do before do Puppet::SSL::Host.ca_location = :only end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Key.indirection.terminus_class.should == :ca Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do Puppet::SSL::Certificate.indirection.cache_class.should be_nil Puppet::SSL::CertificateRequest.indirection.cache_class.should be_nil Puppet::SSL::CertificateRevocationList.indirection.cache_class.should be_nil end it "should set the terminus class for Host to :file" do Puppet::SSL::Host.indirection.terminus_class.should == :file end end describe "as 'none'" do before do Puppet::SSL::Host.ca_location = :none end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Certificate.indirection.terminus_class.should == :file Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :file end it "should set the terminus class for Host to 'none'" do lambda { Puppet::SSL::Host.indirection.terminus_class }.should raise_error(Puppet::DevError) end end end it "should have a class method for destroying all files related to a given host" do Puppet::SSL::Host.should respond_to(:destroy) end describe "when destroying a host's SSL files" do before do Puppet::SSL::Key.indirection.stubs(:destroy).returns false Puppet::SSL::Certificate.indirection.stubs(:destroy).returns false Puppet::SSL::CertificateRequest.indirection.stubs(:destroy).returns false end it "should destroy its certificate, certificate request, and key" do Puppet::SSL::Key.indirection.expects(:destroy).with("myhost") Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost") Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with("myhost") Puppet::SSL::Host.destroy("myhost") end it "should return true if any of the classes returned true" do Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost").returns true Puppet::SSL::Host.destroy("myhost").should be_true end it "should report that nothing was deleted if none of the classes returned true" do Puppet::SSL::Host.destroy("myhost").should == "Nothing was deleted" end end describe "when initializing" do it "should default its name to the :certname setting" do Puppet.settings.expects(:value).with(:certname).returns "myname" Puppet::SSL::Host.new.name.should == "myname" end it "should downcase a passed in name" do Puppet::SSL::Host.new("Host.Domain.Com").name.should == "host.domain.com" end it "should downcase the certname if it's used" do Puppet.settings.expects(:value).with(:certname).returns "Host.Domain.Com" Puppet::SSL::Host.new.name.should == "host.domain.com" end it "should indicate that it is a CA host if its name matches the ca_name constant" do Puppet::SSL::Host.stubs(:ca_name).returns "myca" Puppet::SSL::Host.new("myca").should be_ca end end describe "when managing its private key" do before do @realkey = "mykey" @key = Puppet::SSL::Key.new("mykey") @key.content = @realkey end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(nil) @host.key.should be_nil end it "should find the key in the Key class and return the Puppet instance" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key) @host.key.should equal(@key) end it "should be able to generate and save a new key" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.expects(:generate) Puppet::SSL::Key.indirection.expects(:save) @host.generate_key.should be_true @host.key.should equal(@key) end it "should not retain keys that could not be saved" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.stubs(:generate) Puppet::SSL::Key.indirection.expects(:save).raises "eh" lambda { @host.generate_key }.should raise_error @host.key.should be_nil end it "should return any previously found key without requerying" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key).once @host.key.should equal(@key) @host.key.should equal(@key) end end describe "when managing its certificate request" do before do @realrequest = "real request" @request = Puppet::SSL::CertificateRequest.new("myname") @request.content = @realrequest end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(nil) @host.certificate_request.should be_nil end it "should find the request in the Key class and return it and return the Puppet SSL request" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns @request @host.certificate_request.should equal(@request) end it "should generate a new key when generating the cert request if no key exists" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.expects(:key).times(2).returns(nil).then.returns(key) @host.expects(:generate_key).returns(key) @request.stubs(:generate) Puppet::SSL::CertificateRequest.indirection.stubs(:save) @host.generate_certificate_request end it "should be able to generate and save a new request using the private key" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.expects(:generate).with("mycontent", {}) Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) @host.generate_certificate_request.should be_true @host.certificate_request.should equal(@request) end it "should return any previously found request without requerying" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(@request).once @host.certificate_request.should equal(@request) @host.certificate_request.should equal(@request) end it "should not keep its certificate request in memory if the request cannot be saved" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.stubs(:generate) @request.stubs(:name).returns("myname") terminus = stub 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh" lambda { @host.generate_certificate_request }.should raise_error @host.instance_eval { @certificate_request }.should be_nil end end describe "when managing its certificate" do before do @realcert = mock 'certificate' @cert = stub 'cert', :content => @realcert @host.stubs(:key).returns mock("key") @host.stubs(:certificate_matches_key?).returns true end it "should find the CA certificate if it does not have a certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.stubs(:find).with("myname").returns @cert @host.certificate end it "should not find the CA certificate if it is the CA host" do @host.expects(:ca?).returns true Puppet::SSL::Certificate.indirection.stubs(:find) Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).never @host.certificate end it "should return nil if it cannot find a CA certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns nil Puppet::SSL::Certificate.indirection.expects(:find).with("myname").never @host.certificate.should be_nil end it "should find the key if it does not have one" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns mock("key") @host.certificate end it "should generate the key if one cannot be found" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns nil @host.expects(:generate_key) @host.certificate end it "should find the certificate in the Certificate class and return the Puppet certificate instance" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns @cert @host.certificate.should equal(@cert) end it "should fail if the found certificate does not match the private key" do @host.expects(:certificate_matches_key?).returns false Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert lambda { @host.certificate }.should raise_error(Puppet::Error) end it "should return any previously found certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns(@cert).once @host.certificate.should equal(@cert) @host.certificate.should equal(@cert) end end it "should have a method for listing certificate hosts" do Puppet::SSL::Host.should respond_to(:search) end describe "when listing certificate hosts" do it "should default to listing all clients with any file types" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search end it "should be able to list only clients with a key" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Key end it "should be able to list only clients with a certificate" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Certificate end it "should be able to list only clients with a certificate request" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest end it "should return a Host instance created with the name of each found instance", :'fails_on_ruby_1.9.2' => true do key = stub 'key', :name => "key" cert = stub 'cert', :name => "cert" csr = stub 'csr', :name => "csr" Puppet::SSL::Key.indirection.expects(:search).returns [key] Puppet::SSL::Certificate.indirection.expects(:search).returns [cert] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [csr] returned = [] %w{key cert csr}.each do |name| result = mock(name) returned << result Puppet::SSL::Host.expects(:new).with(name).returns result end result = Puppet::SSL::Host.search returned.each do |r| result.should be_include(r) end end end it "should have a method for generating all necessary files" do Puppet::SSL::Host.new("me").should respond_to(:generate) end describe "when generating files" do before do @host = Puppet::SSL::Host.new("me") @host.stubs(:generate_key) @host.stubs(:generate_certificate_request) end it "should generate a key if one is not present" do @host.stubs(:key).returns nil @host.expects(:generate_key) @host.generate end it "should generate a certificate request if one is not present" do @host.expects(:certificate_request).returns nil @host.expects(:generate_certificate_request) @host.generate end describe "and it can create a certificate authority" do before do @ca = mock 'ca' Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca end it "should use the CA to sign its certificate request if it does not have a certificate" do @host.expects(:certificate).returns nil @ca.expects(:sign).with(@host.name, true) @host.generate end end describe "and it cannot create a certificate authority" do before do Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil end it "should seek its certificate" do @host.expects(:certificate) @host.generate end end end it "should have a method for creating an SSL store" do Puppet::SSL::Host.new("me").should respond_to(:ssl_store) end it "should always return the same store" do host = Puppet::SSL::Host.new("foo") store = mock 'store' store.stub_everything OpenSSL::X509::Store.expects(:new).returns store host.ssl_store.should equal(host.ssl_store) end describe "when creating an SSL store" do before do @host = Puppet::SSL::Host.new("me") @store = mock 'store' @store.stub_everything OpenSSL::X509::Store.stubs(:new).returns @store Puppet.settings.stubs(:value).with(:localcacert).returns "ssl_host_testing" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns(nil) end it "should accept a purpose" do @store.expects(:purpose=).with "my special purpose" @host.ssl_store("my special purpose") end it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY @host.ssl_store end it "should add the local CA cert file" do Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file" @store.expects(:add_file).with "/ca/cert/file" @host.ssl_store end describe "and a CRL is available" do before do @crl = stub 'crl', :content => "real_crl" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl Puppet.settings.stubs(:value).with(:certificate_revocation).returns true end it "should add the CRL" do @store.expects(:add_crl).with "real_crl" @host.ssl_store end it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK @host.ssl_store end end end describe "when waiting for a cert" do before do @host = Puppet::SSL::Host.new("me") end it "should generate its certificate request and attempt to read the certificate again if no certificate is found" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate) @host.wait_for_cert(1) end it "should catch and log errors during CSR saving" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.stubs(:sleep) @host.wait_for_cert(1) end it "should sleep and retry after failures saving the CSR if waitforcert is enabled" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should exit after failures saving the CSR of waitforcert is disabled" do @host.expects(:certificate).returns(nil) @host.expects(:generate).raises(RuntimeError) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do @host.stubs(:certificate).returns nil @host.expects(:generate) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do @host.expects(:certificate).times(3).returns(nil).then.returns(nil).then.returns "foo" @host.expects(:generate) @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should catch and log exceptions during certificate retrieval" do @host.expects(:certificate).times(3).returns(nil).then.raises(RuntimeError).then.returns("foo") @host.stubs(:generate) @host.stubs(:sleep) Puppet.expects(:err) @host.wait_for_cert(1) end end describe "when handling PSON", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do Puppet[:vardir] = tmpdir("ssl_test_vardir") Puppet[:ssldir] = tmpdir("ssl_test_ssldir") # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist Puppet[:cacert] = Puppet[:localcacert] @ca=Puppet::SSL::CertificateAuthority.new end describe "when converting to PSON" do it "should be able to identify a host with an unsigned certificate request" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request pson_hash = { "fingerprint" => host.certificate_request.fingerprint, "desired_state" => 'requested', "name" => host.name } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end it "should be able to identify a host with a signed certificate" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request @ca.sign(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'signed', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end it "should be able to identify a host with a revoked certificate" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request @ca.sign(host.name) @ca.revoke(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'revoked', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end end describe "when converting from PSON" do it "should return a Puppet::SSL::Host object with the specified desired state" do host = Puppet::SSL::Host.new("bazinga") host.desired_state="signed" pson_hash = { "name" => host.name, "desired_state" => host.desired_state, } generated_host = Puppet::SSL::Host.from_pson(pson_hash) generated_host.desired_state.should == host.desired_state generated_host.name.should == host.name end end end end diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index fb88553b4..9e85390eb 100755 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -1,719 +1,847 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/transaction' require 'fileutils' def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end describe Puppet::Transaction do include PuppetSpec::Files before do @basepath = make_absolute("/what/ever") @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) end it "should delegate its event list to the event manager" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.event_manager.expects(:events).returns %w{my events} @transaction.events.should == %w{my events} end it "should delegate adding times to its report" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.report.expects(:add_times).with(:foo, 10) @transaction.report.expects(:add_times).with(:bar, 20) @transaction.add_times :foo => 10, :bar => 20 end it "should be able to accept resource status instances" do resource = Puppet::Type.type(:notify).new :title => "foobar" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.resource_status(resource).should equal(status) end it "should be able to look resource status up by resource reference" do resource = Puppet::Type.type(:notify).new :title => "foobar" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.resource_status(resource.to_s).should equal(status) end # This will basically only ever be used during testing. it "should automatically create resource statuses if asked for a non-existent status" do resource = Puppet::Type.type(:notify).new :title => "foobar" @transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status) end it "should add provided resource statuses to its report" do resource = Puppet::Type.type(:notify).new :title => "foobar" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.report.resource_statuses[resource.to_s].should equal(status) end it "should consider a resource to be failed if a status instance exists for that resource and indicates it is failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) status.failed = "some message" @transaction.add_resource_status(status) @transaction.should be_failed(resource) end it "should not consider a resource to be failed if a status instance exists for that resource but indicates it is not failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.should_not be_failed(resource) end it "should consider there to be failed resources if any statuses are marked failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) status.failed = "some message" @transaction.add_resource_status(status) @transaction.should be_any_failed end it "should not consider there to be failed resources if no statuses are marked failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.should_not be_any_failed end it "should use the provided report object" do report = Puppet::Transaction::Report.new("apply") @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, report) @transaction.report.should == report end it "should create a report if none is provided" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.report.should be_kind_of Puppet::Transaction::Report end describe "when initializing" do it "should create an event manager" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager) @transaction.event_manager.transaction.should equal(@transaction) end it "should create a resource harness" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness) @transaction.resource_harness.transaction.should equal(@transaction) end end describe "when evaluating a resource" do before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.stubs(:skip?).returns false @resource = Puppet::Type.type(:file).new :path => @basepath end it "should check whether the resource should be skipped" do @transaction.expects(:skip?).with(@resource).returns false @transaction.eval_resource(@resource) end it "should process events" do @transaction.event_manager.expects(:process_events).with(@resource) @transaction.eval_resource(@resource) end describe "and the resource should be skipped" do before do @transaction.expects(:skip?).with(@resource).returns true end it "should mark the resource's status as skipped" do @transaction.eval_resource(@resource) @transaction.resource_status(@resource).should be_skipped end end end describe "when applying a resource" do before do @resource = Puppet::Type.type(:file).new :path => @basepath @status = Puppet::Resource::Status.new(@resource) @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.event_manager.stubs(:queue_events) @transaction.resource_harness.stubs(:evaluate).returns(@status) end it "should use its resource harness to apply the resource" do @transaction.resource_harness.expects(:evaluate).with(@resource) @transaction.apply(@resource) end it "should add the resulting resource status to its status list" do @transaction.apply(@resource) @transaction.resource_status(@resource).should be_instance_of(Puppet::Resource::Status) end it "should queue any events added to the resource status" do @status.expects(:events).returns %w{a b} @transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"]) @transaction.apply(@resource) end it "should log and skip any resources that cannot be applied" do @transaction.resource_harness.expects(:evaluate).raises ArgumentError @resource.expects(:err) @transaction.apply(@resource) @transaction.report.resource_statuses[@resource.to_s].should be_nil end end describe "#eval_generate" do let(:path) { tmpdir('eval_generate') } let(:resource) { Puppet::Type.type(:file).new(:path => path, :recurse => true) } let(:graph) { @transaction.relationship_graph } def find_vertex(type, title) graph.vertices.find {|v| v.type == type and v.title == title} end before :each do @filenames = [] 'a'.upto('c') do |x| @filenames << File.join(path,x) 'a'.upto('c') do |y| @filenames << File.join(path,x,y) FileUtils.mkdir_p(File.join(path,x,y)) 'a'.upto('c') do |z| @filenames << File.join(path,x,y,z) FileUtils.touch(File.join(path,x,y,z)) end end end @transaction.catalog.add_resource(resource) end it "should add the generated resources to the catalog" do @transaction.eval_generate(resource) @filenames.each do |file| @transaction.catalog.resource(:file, file).should be_a(Puppet::Type.type(:file)) end end it "should add a sentinel whit for the resource" do @transaction.eval_generate(resource) find_vertex(:whit, "completed_#{path}").should be_a(Puppet::Type.type(:whit)) end it "should replace dependencies on the resource with dependencies on the sentinel" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(dependent) res = find_vertex(resource.type, resource.title) generated = find_vertex(dependent.type, dependent.title) graph.should be_edge(res, generated) @transaction.eval_generate(resource) sentinel = find_vertex(:whit, "completed_#{path}") graph.should be_edge(sentinel, generated) graph.should_not be_edge(res, generated) end it "should add an edge from the nearest ancestor to the generated resource" do @transaction.eval_generate(resource) @filenames.each do |file| v = find_vertex(:file, file) p = find_vertex(:file, File.dirname(file)) graph.should be_edge(p, v) end end it "should add an edge from each generated resource to the sentinel" do @transaction.eval_generate(resource) sentinel = find_vertex(:whit, "completed_#{path}") @filenames.each do |file| v = find_vertex(:file, file) graph.should be_edge(v, sentinel) end end it "should add an edge from the resource to the sentinel" do @transaction.eval_generate(resource) res = find_vertex(:file, path) sentinel = find_vertex(:whit, "completed_#{path}") graph.should be_edge(res, sentinel) end it "should return false if an error occured when generating resources" do resource.stubs(:eval_generate).raises(Puppet::Error) @transaction.eval_generate(resource).should == false end it "should return true if resources were generated" do @transaction.eval_generate(resource).should == true end it "should not add a sentinel if no resources are generated" do path2 = tmpfile('empty') other_file = Puppet::Type.type(:file).new(:path => path2) @transaction.catalog.add_resource(other_file) @transaction.eval_generate(other_file).should == false find_vertex(:whit, "completed_#{path2}").should be_nil end end describe "#unblock" do let(:graph) { @transaction.relationship_graph } let(:resource) { Puppet::Type.type(:notify).new(:name => 'foo') } it "should calculate the number of blockers if it's not known" do graph.add_vertex(resource) 3.times do |i| other = Puppet::Type.type(:notify).new(:name => i.to_s) graph.add_vertex(other) graph.add_edge(other, resource) end graph.unblock(resource) graph.blockers[resource].should == 2 end it "should decrement the number of blockers if there are any" do graph.blockers[resource] = 40 graph.unblock(resource) graph.blockers[resource].should == 39 end it "should warn if there are no blockers" do vertex = stub('vertex') vertex.expects(:warning).with "appears to have a negative number of dependencies" graph.blockers[vertex] = 0 graph.unblock(vertex) end it "should return true if the resource is now unblocked" do graph.blockers[resource] = 1 graph.unblock(resource).should == true end it "should return false if the resource is still blocked" do graph.blockers[resource] = 2 graph.unblock(resource).should == false end end + describe "#finish" do + let(:graph) { @transaction.relationship_graph } + let(:path) { tmpdir('eval_generate') } + let(:resource) { Puppet::Type.type(:file).new(:path => path, :recurse => true) } + + before :each do + @transaction.catalog.add_resource(resource) + end + + it "should unblock the resource's dependents and queue them if ready" do + dependent = Puppet::Type.type(:file).new(:path => tmpfile('dependent'), :require => resource) + more_dependent = Puppet::Type.type(:file).new(:path => tmpfile('more_dependent'), :require => [resource, dependent]) + @transaction.catalog.add_resource(dependent, more_dependent) + + graph.finish(resource) + + graph.blockers[dependent].should == 0 + graph.blockers[more_dependent].should == 1 + + key = graph.unguessable_deterministic_key[dependent] + + graph.ready[key].must == dependent + + graph.ready.should_not be_has_key(graph.unguessable_deterministic_key[more_dependent]) + end + + it "should mark the resource as done" do + graph.finish(resource) + + graph.done[resource].should == true + end + end + describe "when traversing" do let(:graph) { @transaction.relationship_graph } let(:path) { tmpdir('eval_generate') } let(:resource) { Puppet::Type.type(:file).new(:path => path, :recurse => true) } before :each do @transaction.catalog.add_resource(resource) end it "should clear blockers if resources are added" do graph.blockers['foo'] = 3 graph.blockers['bar'] = 4 graph.ready[graph.unguessable_deterministic_key[resource]] = resource @transaction.expects(:eval_generate).with(resource).returns true graph.traverse {} graph.blockers.should be_empty end it "should yield the resource even if eval_generate is called" do graph.ready[graph.unguessable_deterministic_key[resource]] = resource @transaction.expects(:eval_generate).with(resource).returns true yielded = false graph.traverse do |res| yielded = true if res == resource end yielded.should == true end + it "should prefetch the provider if necessary" do + @transaction.expects(:prefetch_if_necessary).with(resource) + + graph.traverse {} + end + it "should not clear blockers if resources aren't added" do graph.blockers['foo'] = 3 graph.blockers['bar'] = 4 graph.ready[graph.unguessable_deterministic_key[resource]] = resource @transaction.expects(:eval_generate).with(resource).returns false graph.traverse {} graph.blockers.should == {'foo' => 3, 'bar' => 4, resource => 0} end it "should unblock all dependents of the resource" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) dependent2 = Puppet::Type.type(:notify).new(:name => "goodbye", :require => resource) @transaction.catalog.add_resource(dependent, dependent2) graph.blockers[dependent].should == 1 graph.blockers[dependent2].should == 1 graph.ready[graph.unguessable_deterministic_key[resource]] = resource graph.traverse {} graph.blockers[dependent].should == 0 graph.blockers[dependent2].should == 0 end it "should enqueue any unblocked dependents" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) dependent2 = Puppet::Type.type(:notify).new(:name => "goodbye", :require => resource) @transaction.catalog.add_resource(dependent, dependent2) graph.blockers[dependent].should == 1 graph.blockers[dependent2].should == 1 graph.ready[graph.unguessable_deterministic_key[resource]] = resource seen = [] graph.traverse do |res| seen << res end seen.should =~ [resource, dependent, dependent2] end it "should mark the resource done" do graph.ready[graph.unguessable_deterministic_key[resource]] = resource graph.traverse {} graph.done[resource].should == true end + + it "should not evaluate the resource if it's not suitable" do + resource.stubs(:suitable?).returns false + + graph.traverse do |resource| + raise "evaluated a resource" + end + end + + it "should defer an unsuitable resource unless it can't go on" do + other = Puppet::Type.type(:notify).new(:name => "hello") + @transaction.catalog.add_resource(other) + + # Show that we check once, then get the resource back and check again + resource.expects(:suitable?).twice.returns(false).then.returns(true) + + graph.traverse {} + end + + it "should fail unsuitable resources and go on if it gets blocked" do + dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) + @transaction.catalog.add_resource(dependent) + + resource.stubs(:suitable?).returns false + + evaluated = [] + graph.traverse do |res| + evaluated << res + end + + # We should have gone on to evaluate the children + evaluated.should == [dependent] + @transaction.resource_status(resource).should be_failed + end end describe "when generating resources" do it "should call 'generate' on all created resources" do first = Puppet::Type.type(:notify).new(:name => "first") second = Puppet::Type.type(:notify).new(:name => "second") third = Puppet::Type.type(:notify).new(:name => "third") @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) first.expects(:generate).returns [second] second.expects(:generate).returns [third] third.expects(:generate) @transaction.generate_additional_resources(first) end it "should finish all resources" do generator = stub 'generator', :depthfirst? => true, :tags => [], :ref => "Some[resource]" resource = stub 'resource', :tag => nil @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) generator.expects(:generate).returns [resource] @catalog.expects(:add_resource).yields(resource) resource.expects(:finish) @transaction.generate_additional_resources(generator) end it "should skip generated resources that conflict with existing resources" do generator = mock 'generator', :tags => [] resource = stub 'resource', :tag => nil @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) generator.expects(:generate).returns [resource] @catalog.expects(:add_resource).raises(Puppet::Resource::Catalog::DuplicateResourceError.new("foo")) resource.expects(:finish).never resource.expects(:info) # log that it's skipped @transaction.generate_additional_resources(generator) end it "should copy all tags to the newly generated resources" do child = stub 'child', :ref => "Some[child_resource]" generator = stub 'resource', :tags => ["one", "two"], :ref => "Some[resource]" @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) generator.stubs(:generate).returns [child] @catalog.stubs(:add_resource) child.expects(:tag).with("one", "two") child.expects(:finish) generator.expects(:depthfirst?) @transaction.generate_additional_resources(generator) end end describe "when skipping a resource" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog) end it "should skip resource with missing tags" do @transaction.stubs(:missing_tags?).returns(true) @transaction.should be_skip(@resource) end it "should skip unscheduled resources" do @transaction.stubs(:scheduled?).returns(false) @transaction.should be_skip(@resource) end it "should skip resources with failed dependencies" do @transaction.stubs(:failed_dependencies?).returns(true) @transaction.should be_skip(@resource) end it "should skip virtual resource" do @resource.stubs(:virtual?).returns true @transaction.should be_skip(@resource) end it "should skip device only resouce on normal host" do @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = false @transaction.should be_skip(@resource) end it "should not skip device only resouce on remote device" do @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = true @transaction.should_not be_skip(@resource) end it "should skip host resouce on device" do @resource.stubs(:appliable_to_device?).returns false @transaction.for_network_device = true @transaction.should be_skip(@resource) end end describe "when determining if tags are missing" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog) @transaction.stubs(:ignore_tags?).returns false end it "should not be missing tags if tags are being ignored" do @transaction.expects(:ignore_tags?).returns true @resource.expects(:tagged?).never @transaction.should_not be_missing_tags(@resource) end it "should not be missing tags if the transaction tags are empty" do @transaction.tags = [] @resource.expects(:tagged?).never @transaction.should_not be_missing_tags(@resource) end it "should otherwise let the resource determine if it is missing tags" do tags = ['one', 'two'] @transaction.tags = tags @resource.expects(:tagged?).with(*tags).returns(false) @transaction.should be_missing_tags(@resource) end end describe "when determining if a resource should be scheduled" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog) end it "should always schedule resources if 'ignoreschedules' is set" do @transaction.ignoreschedules = true @transaction.resource_harness.expects(:scheduled?).never @transaction.should be_scheduled(@resource) end it "should let the resource harness determine whether the resource should be scheduled" do @transaction.resource_harness.expects(:scheduled?).with(@transaction.resource_status(@resource), @resource).returns "feh" @transaction.scheduled?(@resource).should == "feh" end end describe "when prefetching" do - it "should match resources by name, not title" do - @catalog = Puppet::Resource::Catalog.new - @transaction = Puppet::Transaction.new(@catalog) + let(:catalog) { Puppet::Resource::Catalog.new } + let(:transaction) { Puppet::Transaction.new(catalog) } + let(:resource) { Puppet::Type.type(:sshkey).create :title => "foo", :name => "bar", :type => :dsa, :key => "eh", :provider => :parsed } + let(:resource2) { Puppet::Type.type(:package).create :title => "blah", :provider => "apt" } - # Have both a title and name - resource = Puppet::Type.type(:sshkey).create :title => "foo", :name => "bar", :type => :dsa, :key => "eh" - @catalog.add_resource resource + before :each do + catalog.add_resource resource + catalog.add_resource resource2 + end + + + describe "#resources_by_provider" do + it "should fetch resources by their type and provider" do + transaction.resources_by_provider(:sshkey, :parsed).should == { + resource.name => resource, + } + + transaction.resources_by_provider(:package, :apt).should == { + resource2.name => resource2, + } + end + + it "should omit resources whose types don't use providers" do + # faking the sshkey type not to have a provider + resource.class.stubs(:attrclass).returns nil + + transaction.resources_by_provider(:sshkey, :parsed).should == {} + end + it "should return empty hash for providers with no resources" do + transaction.resources_by_provider(:package, :yum).should == {} + end + end + + it "should match resources by name, not title" do resource.provider.class.expects(:prefetch).with("bar" => resource) - @transaction.prefetch + transaction.prefetch_if_necessary(resource) + end + + it "should not prefetch a provider which has already been prefetched" do + transaction.prefetched_providers[:sshkey][:parsed] = true + + resource.provider.class.expects(:prefetch).never + + transaction.prefetch_if_necessary(resource) + end + + it "should mark the provider prefetched" do + resource.provider.class.stubs(:prefetch) + + transaction.prefetch_if_necessary(resource) + + transaction.prefetched_providers[:sshkey][:parsed].should be_true + end + + it "should prefetch resources without a provider if prefetching the default provider" do + other = Puppet::Type.type(:sshkey).create :name => "other" + + other.instance_variable_set(:@provider, nil) + + catalog.add_resource other + + resource.provider.class.expects(:prefetch).with('bar' => resource, 'other' => other) + + transaction.prefetch_if_necessary(resource) end end it "should return all resources for which the resource status indicates the resource has changed when determinig changed resources" do @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) names = [] 2.times do |i| name = File.join(@basepath, "file#{i}") resource = Puppet::Type.type(:file).new :path => name names << resource.to_s @catalog.add_resource resource @transaction.add_resource_status Puppet::Resource::Status.new(resource) end @transaction.resource_status(names[0]).changed = true @transaction.changed?.should == [@catalog.resource(names[0])] end describe 'when checking application run state' do before do without_warnings { Puppet::Application = Class.new(Puppet::Application) } @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) end after do without_warnings { Puppet::Application = Puppet::Application.superclass } end it 'should return true for :stop_processing? if Puppet::Application.stop_requested? is true' do Puppet::Application.stubs(:stop_requested?).returns(true) @transaction.stop_processing?.should be_true end it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do Puppet::Application.stubs(:stop_requested?).returns(false) @transaction.stop_processing?.should be_false end describe 'within an evaluate call' do before do @resource = Puppet::Type.type(:notify).new :title => "foobar" @catalog.add_resource @resource - @transaction.stubs(:prepare) + @transaction.stubs(:add_dynamically_generated_resources) end it 'should stop processing if :stop_processing? is true' do @transaction.stubs(:stop_processing?).returns(true) @transaction.expects(:eval_resource).never @transaction.evaluate end it 'should continue processing if :stop_processing? is false' do @transaction.stubs(:stop_processing?).returns(false) @transaction.expects(:eval_resource).returns(nil) @transaction.evaluate end end end end describe Puppet::Transaction, " when determining tags" do before do @config = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@config) end it "should default to the tags specified in the :tags setting" do Puppet.expects(:[]).with(:tags).returns("one") @transaction.tags.should == %w{one} end it "should split tags based on ','" do Puppet.expects(:[]).with(:tags).returns("one,two") @transaction.tags.should == %w{one two} end it "should use any tags set after creation" do Puppet.expects(:[]).with(:tags).never @transaction.tags = %w{one two} @transaction.tags.should == %w{one two} end it "should always convert assigned tags to an array" do @transaction.tags = "one::two" @transaction.tags.should == %w{one::two} end it "should accept a comma-delimited string" do @transaction.tags = "one, two" @transaction.tags.should == %w{one two} end it "should accept an empty string" do @transaction.tags = "" @transaction.tags.should == [] end end diff --git a/spec/unit/type/file/mode_spec.rb b/spec/unit/type/file/mode_spec.rb index 8783a8464..2c6772aba 100755 --- a/spec/unit/type/file/mode_spec.rb +++ b/spec/unit/type/file/mode_spec.rb @@ -1,106 +1,142 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:mode) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => 0644 } let(:mode) { resource.property(:mode) } describe "#validate" do it "should accept values specified as integers" do expect { mode.value = 0755 }.not_to raise_error end it "should accept values specified as octal numbers in strings" do expect { mode.value = '0755' }.not_to raise_error end it "should not accept strings other than octal numbers" do expect do mode.value = 'readable please!' end.to raise_error(Puppet::Error, /File modes can only be octal numbers/) end end describe "#munge" do # This is sort of a redundant test, but its spec is important. it "should return the value as a string" do mode.munge('0644').should be_a(String) end it "should accept strings as arguments" do mode.munge('0644').should == '644' end it "should accept integers are arguments" do mode.munge(0644).should == '644' end end describe "#dirmask" do before :each do Dir.mkdir(path) end it "should add execute bits corresponding to read bits for directories" do mode.dirmask('0644').should == '755' end it "should not add an execute bit when there is no read bit" do mode.dirmask('0600').should == '700' end it "should not add execute bits for files that aren't directories" do resource[:path] = tmpfile('other_file') mode.dirmask('0644').should == '0644' end end describe "#insync?" do it "should return true if the mode is correct" do FileUtils.touch(path) mode.must be_insync('644') end it "should return false if the mode is incorrect" do FileUtils.touch(path) mode.must_not be_insync('755') end it "should return true if the file is a link and we are managing links", :unless => Puppet.features.microsoft_windows? do File.symlink('anything', path) mode.must be_insync('644') end end describe "#retrieve" do it "should return absent if the resource doesn't exist" do resource[:path] = File.expand_path("/does/not/exist") mode.retrieve.should == :absent end it "should retrieve the directory mode from the provider" do Dir.mkdir(path) mode.expects(:dirmask).with('644').returns '755' resource.provider.expects(:mode).returns '755' mode.retrieve.should == '755' end it "should retrieve the file mode from the provider" do FileUtils.touch(path) mode.expects(:dirmask).with('644').returns '644' resource.provider.expects(:mode).returns '644' mode.retrieve.should == '644' end end + + describe '#should_to_s' do + describe 'with a 3-digit mode' do + it 'returns a 4-digit mode with a leading zero' do + mode.should_to_s('755').should == '0755' + end + end + + describe 'with a 4-digit mode' do + it 'returns the 4-digit mode when the first digit is a zero' do + mode.should_to_s('0755').should == '0755' + end + + it 'returns the 4-digit mode when the first digit is not a zero' do + mode.should_to_s('1755').should == '1755' + end + end + end + + describe '#is_to_s' do + describe 'with a 3-digit mode' do + it 'returns a 4-digit mode with a leading zero' do + mode.is_to_s('755').should == '0755' + end + end + + describe 'with a 4-digit mode' do + it 'returns the 4-digit mode when the first digit is a zero' do + mode.is_to_s('0755').should == '0755' + end + + it 'returns the 4-digit mode when the first digit is not a zero' do + mode.is_to_s('1755').should == '1755' + end + end + end end diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 05299ca41..3b5f0cba6 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -1,671 +1,706 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type, :fails_on_windows => true do include PuppetSpec::Files it "should consider a parameter to be valid if it is a valid parameter" do Puppet::Type.type(:mount).should be_valid_parameter(:path) end it "should consider a parameter to be valid if it is a valid property" do Puppet::Type.type(:mount).should be_valid_parameter(:fstype) end it "should consider a parameter to be valid if it is a valid metaparam" do Puppet::Type.type(:mount).should be_valid_parameter(:noop) end it "should be able to retrieve a property by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve a parameter by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name)) end it "should be able to retrieve a property by name using the :parameter method" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve all set properties" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) props = resource.properties props.should_not be_include(nil) [:fstype, :ensure, :pass].each do |name| props.should be_include(resource.parameter(name)) end end it "should have a method for setting default values for resources" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:set_default) end it "should do nothing for attributes that have no defaults and no specified value" do Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil end it "should have a method for adding tags" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:tags) end it "should use the tagging module" do Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging) end it "should delegate to the tagging module when tags are added" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag).with(:mount) resource.expects(:tag).with(:tag1, :tag2) resource.tags = [:tag1,:tag2] end it "should add the current type as tag" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag) resource.expects(:tag).with(:mount) resource.tags = [:tag1,:tag2] end it "should have a method to know if the resource is exported" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:exported?) end it "should have a method to know if the resource is virtual" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:virtual?) end it "should consider its version to be its catalog version" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource.version.should == 50 end it "should consider its version to be zero if it has no catalog" do Puppet::Type.type(:mount).new(:name => "foo").version.should == 0 end it "should provide source_descriptors" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource.source_descriptors.should == {:tags=>["mount", "foo"], :path=>"/Mount[foo]"} end it "should consider its type to be the name of its class" do Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount end it "should use any provided noop value" do Puppet::Type.type(:mount).new(:name => "foo", :noop => true).must be_noop end it "should use the global noop value if none is provided" do Puppet[:noop] = true Puppet::Type.type(:mount).new(:name => "foo").must be_noop end it "should not be noop if in a non-host_config catalog" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource resource.should_not be_noop end describe "when creating an event" do before do @resource = Puppet::Type.type(:mount).new :name => "foo" end it "should have the resource's reference as the resource" do @resource.event.resource.should == "Mount[foo]" end it "should have the resource's log level as the default log level" do @resource[:loglevel] = :warning @resource.event.default_log_level.should == :warning end {:file => "/my/file", :line => 50, :tags => %{foo bar}}.each do |attr, value| it "should set the #{attr}" do @resource.stubs(attr).returns value @resource.event.send(attr).should == value end end it "should allow specification of event attributes" do @resource.event(:status => "noop").status.should == "noop" end end describe "when creating a provider" do before :each do @type = Puppet::Type.newtype(:provider_test_type) do newparam(:name) { isnamevar } newparam(:foo) newproperty(:bar) end end after :each do @type.provider_hash.clear end describe "when determining if instances of the type are managed" do it "should not consider audit only resources to be managed" do @type.new(:name => "foo", :audit => 'all').managed?.should be_false end it "should not consider resources with only parameters to be managed" do @type.new(:name => "foo", :foo => 'did someone say food?').managed?.should be_false end it "should consider resources with any properties set to be managed" do @type.new(:name => "foo", :bar => 'Let us all go there').managed?.should be_true end end it "should have documentation for the 'provider' parameter if there are providers" do @type.provide(:test_provider) @type.paramdoc(:provider).should =~ /`provider_test_type`[\s\r]+resource/ end it "should not have documentation for the 'provider' parameter if there are no providers" do expect { @type.paramdoc(:provider) }.to raise_error(NoMethodError) end it "should create a subclass of Puppet::Provider for the provider" do provider = @type.provide(:test_provider) provider.ancestors.should include(Puppet::Provider) end it "should use a parent class if specified" do parent_provider = @type.provide(:parent_provider) child_provider = @type.provide(:child_provider, :parent => parent_provider) child_provider.ancestors.should include(parent_provider) end it "should use a parent class if specified by name" do parent_provider = @type.provide(:parent_provider) child_provider = @type.provide(:child_provider, :parent => :parent_provider) child_provider.ancestors.should include(parent_provider) end it "should raise an error when the parent class can't be found" do expect { @type.provide(:child_provider, :parent => :parent_provider) }.to raise_error(Puppet::DevError, /Could not find parent provider.+parent_provider/) end it "should ensure its type has a 'provider' parameter" do @type.provide(:test_provider) @type.parameters.should include(:provider) end it "should remove a previously registered provider with the same name" do old_provider = @type.provide(:test_provider) new_provider = @type.provide(:test_provider) old_provider.should_not equal(new_provider) end it "should register itself as a provider for the type" do provider = @type.provide(:test_provider) provider.should == @type.provider(:test_provider) end it "should create a provider when a provider with the same name previously failed" do @type.provide(:test_provider) do raise "failed to create this provider" end rescue nil provider = @type.provide(:test_provider) provider.ancestors.should include(Puppet::Provider) provider.should == @type.provider(:test_provider) end end describe "when choosing a default provider" do it "should choose the provider with the highest specificity" do # Make a fake type type = Puppet::Type.newtype(:defaultprovidertest) do newparam(:name) do end end basic = type.provide(:basic) {} greater = type.provide(:greater) {} basic.stubs(:specificity).returns 1 greater.stubs(:specificity).returns 2 type.defaultprovider.should equal(greater) end end describe "when initializing" do describe "and passed a TransObject" do it "should fail" do trans = Puppet::TransObject.new("/foo", :mount) lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError) end end describe "and passed a Puppet::Resource instance" do it "should set its title to the title of the resource if the resource type is equal to the current type" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"}) Puppet::Type.type(:mount).new(resource).title.should == "/foo" end it "should set its title to the resource reference if the resource type is not equal to the current type" do resource = Puppet::Resource.new(:user, "foo") Puppet::Type.type(:mount).new(resource).title.should == "User[foo]" end [:line, :file, :catalog, :exported, :virtual].each do |param| it "should copy '#{param}' from the resource if present" do resource = Puppet::Resource.new(:mount, "/foo") resource.send(param.to_s + "=", "foo") resource.send(param.to_s + "=", "foo") Puppet::Type.type(:mount).new(resource).send(param).should == "foo" end end it "should copy any tags from the resource" do resource = Puppet::Resource.new(:mount, "/foo") resource.tag "one", "two" tags = Puppet::Type.type(:mount).new(resource).tags tags.should be_include("one") tags.should be_include("two") end it "should copy the resource's parameters as its own" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"}) params = Puppet::Type.type(:mount).new(resource).to_hash params[:fstype].should == "boo" params[:atboot].should == true end end describe "and passed a Hash" do it "should extract the title from the hash" do Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay" end it "should work when hash keys are provided as strings" do Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay" end it "should work when hash keys are provided as symbols" do Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay" end it "should use the name from the hash as the title if no explicit title is provided" do Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay" end it "should use the Resource Type's namevar to determine how to find the name in the hash" do yay = make_absolute('/yay') Puppet::Type.type(:file).new(:path => yay).title.should == yay end [:catalog].each do |param| it "should extract '#{param}' from the hash if present" do Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo" end end it "should use any remaining hash keys as its parameters" do resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo") resource[:fstype].must == "boo" resource[:atboot].must == true end end it "should fail if any invalid attributes have been provided" do lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error) end it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do resource = Puppet::Resource.new(:mount, "/foo") Puppet::Type.type(:mount).new(resource).name.should == "/foo" end it "should fail if no title, name, or namevar are provided" do lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error) end it "should set the attributes in the order returned by the class's :allattrs method" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) set[-1].should == :noop set[-2].should == :atboot end it "should always set the name and then default provider before anything else" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) set[0].should == :name set[1].should == :provider end # This one is really hard to test :/ it "should set each default immediately if no value is provided" do defaults = [] Puppet::Type.type(:service).any_instance.stubs(:set_default).with { |value| defaults << value; true } Puppet::Type.type(:service).new :name => "whatever" defaults[0].should == :provider end it "should retain a copy of the originally provided parameters" do Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false} end it "should delete the name via the namevar from the originally provided parameters" do Puppet::Type.type(:file).new(:name => make_absolute('/foo')).original_parameters[:path].should be_nil end end it "should have a class method for converting a hash into a Puppet::Resource instance" do Puppet::Type.type(:mount).must respond_to(:hash2resource) end describe "when converting a hash to a Puppet::Resource instance" do before do @type = Puppet::Type.type(:mount) end it "should treat a :title key as the title of the resource" do @type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo" end it "should use the name from the hash as the title if no explicit title is provided" do @type.hash2resource(:name => "foo").title.should == "foo" end it "should use the Resource Type's namevar to determine how to find the name in the hash" do @type.stubs(:key_attributes).returns([ :myname ]) @type.hash2resource(:myname => "foo").title.should == "foo" end [:catalog].each do |attr| it "should use any provided #{attr}" do @type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh" end end it "should set all provided parameters on the resource" do @type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"} end it "should not set the title as a parameter on the resource" do @type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil end it "should not set the catalog as a parameter on the resource" do @type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil end it "should treat hash keys equivalently whether provided as strings or symbols" do resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo") resource.title.should == "eh" resource[:name].should == "foo" resource[:fstype].should == "boo" end end describe "when retrieving current property values" do before do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.property(:ensure).stubs(:retrieve).returns :absent end it "should fail if its provider is unsuitable" do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.provider.class.expects(:suitable?).returns false lambda { @resource.retrieve_resource }.should raise_error(Puppet::Error) end it "should return a Puppet::Resource instance with its type and title set appropriately" do result = @resource.retrieve_resource result.should be_instance_of(Puppet::Resource) result.type.should == "Mount" result.title.should == "foo" end it "should set the name of the returned resource if its own name and title differ" do @resource[:name] = "my name" @resource.title = "other name" @resource.retrieve_resource[:name].should == "my name" end it "should provide a value for all set properties" do values = @resource.retrieve_resource [:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil } end it "should provide a value for 'ensure' even if no desired value is provided" do @resource = Puppet::Type.type(:file).new(:path => make_absolute("/my/file/that/can't/exist")) end it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do @resource.property(:ensure).expects(:retrieve).returns :absent @resource.property(:fstype).expects(:retrieve).never @resource.retrieve_resource[:fstype].should == :absent end it "should include the result of retrieving each property's current value if the resource is present" do @resource.property(:ensure).expects(:retrieve).returns :present @resource.property(:fstype).expects(:retrieve).returns 15 @resource.retrieve_resource[:fstype] == 15 end end describe ".title_patterns" do describe "when there's one namevar" do before do @type_class = Puppet::Type.type(:notify) @type_class.stubs(:key_attributes).returns([:one]) end it "should have a default pattern for when there's one namevar" do patterns = @type_class.title_patterns patterns.length.should == 1 patterns[0].length.should == 2 end it "should have a regexp that captures the entire string" do patterns = @type_class.title_patterns string = "abc\n\tdef" patterns[0][0] =~ string $1.should == "abc\n\tdef" end end end describe "when in a catalog" do before do @catalog = Puppet::Resource::Catalog.new @container = Puppet::Type.type(:component).new(:name => "container") @one = Puppet::Type.type(:file).new(:path => make_absolute("/file/one")) @two = Puppet::Type.type(:file).new(:path => make_absolute("/file/two")) @catalog.add_resource @container @catalog.add_resource @one @catalog.add_resource @two @catalog.add_edge @container, @one @catalog.add_edge @container, @two end it "should have no parent if there is no in edge" do @container.parent.should be_nil end it "should set its parent to its in edge" do @one.parent.ref.should == @container.ref end after do @catalog.clear(true) end end it "should have a 'stage' metaparam" do Puppet::Type.metaparamclass(:stage).should be_instance_of(Class) end + + describe "#suitable?" do + let(:type) { Puppet::Type.type(:file) } + let(:resource) { type.new :path => tmpfile('suitable') } + let(:provider) { resource.provider } + + it "should be suitable if its type doesn't use providers" do + type.stubs(:paramclass).with(:provider).returns nil + + resource.should be_suitable + end + + it "should be suitable if it has a provider which is suitable" do + resource.should be_suitable + end + + it "should not be suitable if it has a provider which is not suitable" do + provider.class.stubs(:suitable?).returns false + + resource.should_not be_suitable + end + + it "should be suitable if it does not have a provider and there is a default provider" do + resource.stubs(:provider).returns nil + + resource.should be_suitable + end + + it "should not be suitable if it doesn't have a provider and there is not default provider" do + resource.stubs(:provider).returns nil + type.stubs(:defaultprovider).returns nil + + resource.should_not be_suitable + end + end end describe Puppet::Type::RelationshipMetaparam do include PuppetSpec::Files it "should be a subclass of Puppet::Parameter" do Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter) end it "should be able to produce a list of subclasses" do Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses) end describe "when munging relationships", :fails_on_windows => true do before do @path = make_absolute('/foo') @resource = Puppet::Type.type(:mount).new :name => @path @metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource end it "should accept Puppet::Resource instances" do ref = Puppet::Resource.new(:file, @path) @metaparam.munge(ref)[0].should equal(ref) end it "should turn any string into a Puppet::Resource" do @metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource) end end it "should be able to validate relationships" do Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship) end it "should fail if any specified resource is not found in the catalog" do catalog = mock 'catalog' resource = stub 'resource', :catalog => catalog, :ref => "resource" param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]}) catalog.expects(:resource).with("Foo[bar]").returns "something" catalog.expects(:resource).with("Class[Test]").returns nil param.expects(:fail).with { |string| string.include?("Class[Test]") } param.validate_relationship end end describe Puppet::Type.metaparamclass(:check) do include PuppetSpec::Files it "should warn and create an instance of ':audit'" do file = Puppet::Type.type(:file).new :path => make_absolute('/foo') file.expects(:warning) file[:check] = :mode file[:audit].should == [:mode] end end describe Puppet::Type.metaparamclass(:audit) do include PuppetSpec::Files before do @resource = Puppet::Type.type(:file).new :path => make_absolute('/foo') end it "should default to being nil" do @resource[:audit].should be_nil end it "should specify all possible properties when asked to audit all properties" do @resource[:audit] = :all list = @resource.class.properties.collect { |p| p.name } @resource[:audit].should == list end it "should accept the string 'all' to specify auditing all possible properties" do @resource[:audit] = 'all' list = @resource.class.properties.collect { |p| p.name } @resource[:audit].should == list end it "should fail if asked to audit an invalid property" do lambda { @resource[:audit] = :foobar }.should raise_error(Puppet::Error) end it "should create an attribute instance for each auditable property" do @resource[:audit] = :mode @resource.parameter(:mode).should_not be_nil end it "should accept properties specified as a string" do @resource[:audit] = "mode" @resource.parameter(:mode).should_not be_nil end it "should not create attribute instances for parameters, only properties" do @resource[:audit] = :noop @resource.parameter(:noop).should be_nil end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:path, :mode, :owner] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) myfile = make_absolute('/my/file') res = Puppet::Type.type(:file).new( :title => myfile, :path => myfile, :owner => 'root', :content => 'hello' ) res.uniqueness_key.should == [ nil, 'root', myfile] end end end diff --git a/spec/unit/util/settings/file_setting_spec.rb b/spec/unit/util/settings/file_setting_spec.rb index 6344cf1b5..74d5090cb 100755 --- a/spec/unit/util/settings/file_setting_spec.rb +++ b/spec/unit/util/settings/file_setting_spec.rb @@ -1,284 +1,290 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/settings' require 'puppet/util/settings/file_setting' describe Puppet::Util::Settings::FileSetting do FileSetting = Puppet::Util::Settings::FileSetting include PuppetSpec::Files before do @basepath = make_absolute("/somepath") end describe "when determining whether the service user should be used" do before do @settings = mock 'settings' @settings.stubs(:[]).with(:mkusers).returns false @settings.stubs(:service_user_available?).returns true end it "should be true if the service user is available" do @settings.expects(:service_user_available?).returns true setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting") setting.should be_use_service_user end it "should be true if 'mkusers' is set" do @settings.expects(:[]).with(:mkusers).returns true setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting") setting.should be_use_service_user end it "should be false if the service user is not available and 'mkusers' is unset" do setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting") setting.should be_use_service_user end end describe "when setting the owner" do it "should allow the file to be owned by root" do root_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "root", :desc => "a setting") } root_owner.should_not raise_error end it "should allow the file to be owned by the service user" do service_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "service", :desc => "a setting") } service_owner.should_not raise_error end it "should allow the ownership of the file to be unspecified" do no_owner = lambda { FileSetting.new(:settings => mock("settings"), :desc => "a setting") } no_owner.should_not raise_error end it "should not allow other owners" do invalid_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "invalid", :desc => "a setting") } invalid_owner.should raise_error(FileSetting::SettingError) end end describe "when reading the owner" do it "should be root when the setting specifies root" do setting = FileSetting.new(:settings => mock("settings"), :owner => "root", :desc => "a setting") setting.owner.should == "root" end it "should be the owner of the service when the setting specifies service and the service user should be used" do settings = mock("settings") settings.stubs(:[]).returns "the_service" setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.expects(:use_service_user?).returns true setting.owner.should == "the_service" end it "should be the root when the setting specifies service and the service user should not be used" do settings = mock("settings") settings.stubs(:[]).returns "the_service" setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.expects(:use_service_user?).returns false setting.owner.should == "root" end it "should be nil when the owner is unspecified" do FileSetting.new(:settings => mock("settings"), :desc => "a setting").owner.should be_nil end end describe "when setting the group" do it "should allow the group to be service" do service_group = lambda { FileSetting.new(:settings => mock("settings"), :group => "service", :desc => "a setting") } service_group.should_not raise_error end it "should allow the group to be unspecified" do no_group = lambda { FileSetting.new(:settings => mock("settings"), :desc => "a setting") } no_group.should_not raise_error end it "should not allow invalid groups" do invalid_group = lambda { FileSetting.new(:settings => mock("settings"), :group => "invalid", :desc => "a setting") } invalid_group.should raise_error(FileSetting::SettingError) end end describe "when reading the group" do it "should be service when the setting specifies service" do setting = FileSetting.new(:settings => mock("settings", :[] => "the_service"), :group => "service", :desc => "a setting") setting.group.should == "the_service" end it "should be nil when the group is unspecified" do FileSetting.new(:settings => mock("settings"), :desc => "a setting").group.should be_nil end end it "should be able to be converted into a resource" do FileSetting.new(:settings => mock("settings"), :desc => "eh").should respond_to(:to_resource) end describe "when being converted to a resource" do before do @settings = mock 'settings' @file = Puppet::Util::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :mydir, :section => "mysect") @settings.stubs(:value).with(:mydir).returns @basepath end it "should skip files that cannot determine their types" do @file.expects(:type).returns nil @file.to_resource.should be_nil end it "should skip non-existent files if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file File.expects(:exist?).with(@basepath).returns false @file.to_resource.should be_nil end it "should manage existent files even if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file File.expects(:exist?).with(@basepath).returns true @file.to_resource.should be_instance_of(Puppet::Resource) end describe "on POSIX systems", :if => Puppet.features.posix? do it "should skip files in /dev" do @settings.stubs(:value).with(:mydir).returns "/dev/file" @file.to_resource.should be_nil end end it "should skip files whose paths are not strings" do @settings.stubs(:value).with(:mydir).returns :foo @file.to_resource.should be_nil end it "should return a file resource with the path set appropriately" do resource = @file.to_resource resource.type.should == "File" resource.title.should == @basepath end it "should fully qualified returned files if necessary (#795)" do @settings.stubs(:value).with(:mydir).returns "myfile" path = File.join(Dir.getwd, "myfile") # Dir.getwd can return windows paths with backslashes, so we normalize them using expand_path path = File.expand_path(path) if Puppet.features.microsoft_windows? @file.to_resource.title.should == path end - it "should set the mode on the file if a mode is provided" do + it "should set the mode on the file if a mode is provided as an octal number" do @file.mode = 0755 - @file.to_resource[:mode].should == 0755 + @file.to_resource[:mode].should == '755' + end + + it "should set the mode on the file if a mode is provided as a string" do + @file.mode = '0755' + + @file.to_resource[:mode].should == '755' end it "should not set the mode on a the file if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false @file.stubs(:mode).returns(0755) @file.to_resource[:mode].should == nil end it "should set the owner if running as root and the owner is provided" do Puppet.features.expects(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should == "foo" end it "should not set the owner if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should == nil end it "should set the group if running as root and the group is provided" do Puppet.features.expects(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:group).returns "foo" @file.to_resource[:group].should == "foo" end it "should not set the group if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:group).returns "foo" @file.to_resource[:group].should == nil end it "should not set owner if not running as root" do Puppet.features.expects(:root?).returns false Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should be_nil end it "should not set group if not running as root" do Puppet.features.expects(:root?).returns false Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:group).returns "foo" @file.to_resource[:group].should be_nil end describe "on Microsoft Windows systems" do before :each do Puppet.features.stubs(:microsoft_windows?).returns true end it "should not set owner" do @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should be_nil end it "should not set group" do @file.stubs(:group).returns "foo" @file.to_resource[:group].should be_nil end end it "should set :ensure to the file type" do @file.expects(:type).returns :directory @file.to_resource[:ensure].should == :directory end it "should set the loglevel to :debug" do @file.to_resource[:loglevel].should == :debug end it "should set the backup to false" do @file.to_resource[:backup].should be_false end it "should tag the resource with the settings section" do @file.expects(:section).returns "mysect" @file.to_resource.should be_tagged("mysect") end it "should tag the resource with the setting name" do @file.to_resource.should be_tagged("mydir") end it "should tag the resource with 'settings'" do @file.to_resource.should be_tagged("settings") end it "should set links to 'follow'" do @file.to_resource[:links].should == :follow end end end diff --git a/test/other/transactions.rb b/test/other/transactions.rb index 812e519ab..d77fd1538 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -1,401 +1,395 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'mocha' require 'puppet' require 'puppettest' require 'puppettest/support/resources' require 'puppettest/support/utils' class TestTransactions < Test::Unit::TestCase include PuppetTest::FileTesting include PuppetTest::Support::Resources include PuppetTest::Support::Utils class Fakeprop true) def finish $finished << self.name end end type.class_eval(&block) if block cleanup do Puppet::Type.rmtype(:generator) end type end # Create a new type that generates instances with shorter names. def mkreducer(&block) type = mkgenerator do def eval_generate ret = [] if title.length > 1 ret << self.class.new(:title => title[0..-2]) else return nil end ret end end type.class_eval(&block) if block type end def test_prefetch # Create a type just for testing prefetch name = :prefetchtesting $prefetched = false type = Puppet::Type.newtype(name) do newparam(:name) {} end cleanup do Puppet::Type.rmtype(name) end # Now create a provider type.provide(:prefetch) do def self.prefetch(resources) $prefetched = resources end end # Now create an instance inst = type.new :name => "yay" # Create a transaction trans = Puppet::Transaction.new(mk_catalog(inst)) - # Make sure prefetch works - assert_nothing_raised do - trans.prefetch - end - - assert_equal({inst.title => inst}, $prefetched, "type prefetch was not called") - - # Now make sure it gets called from within evaluate + # Make sure it gets called from within evaluate $prefetched = false assert_nothing_raised do trans.evaluate end assert_equal({inst.title => inst}, $prefetched, "evaluate did not call prefetch") end def test_ignore_tags? config = Puppet::Resource::Catalog.new config.host_config = true transaction = Puppet::Transaction.new(config) assert(! transaction.ignore_tags?, "Ignoring tags when applying a host catalog") config.host_config = false transaction = Puppet::Transaction.new(config) assert(transaction.ignore_tags?, "Not ignoring tags when applying a non-host catalog") end def test_missing_tags? resource = Puppet::Type.type(:notify).new :title => "foo" resource.stubs(:tagged?).returns true config = Puppet::Resource::Catalog.new # Mark it as a host config so we don't care which test is first config.host_config = true transaction = Puppet::Transaction.new(config) assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when none are set") # host catalogs pay attention to tags, no one else does. Puppet[:tags] = "three,four" config.host_config = false transaction = Puppet::Transaction.new(config) assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when not running a host catalog") # config.host_config = true transaction = Puppet::Transaction.new(config) assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when running a host catalog and all tags are present") transaction = Puppet::Transaction.new(config) resource.stubs :tagged? => false assert(transaction.missing_tags?(resource), "Considered a resource not to be missing tags when running a host catalog and tags are missing") end # Make sure changes in contained files still generate callback events. def test_generated_callbacks dir = tempfile maker = tempfile Dir.mkdir(dir) file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "" } File.chmod(0644, file) File.chmod(0755, dir) # So only the child file causes a change dirobj = Puppet::Type.type(:file).new :mode => "755", :recurse => true, :path => dir exec = Puppet::Type.type(:exec).new :title => "make", :command => "touch #{maker}", :path => ENV['PATH'], :refreshonly => true, :subscribe => dirobj assert_apply(dirobj, exec) assert(FileTest.exists?(maker), "Did not make callback file") end # Testing #401 -- transactions are calling refresh on classes that don't support it. def test_callback_availability $called = [] klass = Puppet::Type.newtype(:norefresh) do newparam(:name, :namevar => true) {} def method_missing(method, *args) $called << method end end cleanup do $called = nil Puppet::Type.rmtype(:norefresh) end file = Puppet::Type.type(:file).new :path => tempfile, :content => "yay" one = klass.new :name => "one", :subscribe => file assert_apply(file, one) assert(! $called.include?(:refresh), "Called refresh when it wasn't set as a method") end # Testing #437 - cyclic graphs should throw failures. def test_fail_on_cycle one = Puppet::Type.type(:exec).new(:name => "/bin/echo one") two = Puppet::Type.type(:exec).new(:name => "/bin/echo two") one[:require] = two two[:require] = one config = mk_catalog(one, two) trans = Puppet::Transaction.new(config) assert_raise(Puppet::Error) do trans.evaluate end end def test_errors_during_generation type = Puppet::Type.newtype(:failer) do newparam(:name) {} def eval_generate raise ArgumentError, "Invalid value" end def generate raise ArgumentError, "Invalid value" end end cleanup { Puppet::Type.rmtype(:failer) } obj = type.new(:name => "testing") assert_apply(obj) end def test_self_refresh_causes_triggering type = Puppet::Type.newtype(:refresher, :self_refresh => true) do attr_accessor :refreshed, :testing newparam(:name) {} newproperty(:testing) do def retrieve :eh end def sync # noop :ran_testing end end def refresh @refreshed = true end end cleanup { Puppet::Type.rmtype(:refresher)} obj = type.new(:name => "yay", :testing => "cool") assert(! obj.insync?(obj.retrieve), "fake object is already in sync") # Now make sure it gets refreshed when the change happens assert_apply(obj) assert(obj.refreshed, "object was not refreshed during transaction") end # Testing #433 def test_explicit_dependencies_beat_automatic # Create a couple of different resource sets that have automatic relationships and make sure the manual relationships win rels = {} # Now add the explicit relationship # Now files d = tempfile f = File.join(d, "file") file = Puppet::Type.type(:file).new(:path => f, :content => "yay") dir = Puppet::Type.type(:file).new(:path => d, :ensure => :directory, :require => file) rels[dir] = file rels.each do |after, before| config = mk_catalog(before, after) trans = Puppet::Transaction.new(config) str = "from #{before} to #{after}" - assert_nothing_raised("Failed to create graph #{str}") do - trans.prepare - end + assert_nothing_raised("Failed to create graph #{str}") do + trans.add_dynamically_generated_resources + end + graph = trans.relationship_graph assert(graph.edge?(before, after), "did not create manual relationship #{str}") assert(! graph.edge?(after, before), "created automatic relationship #{str}") end end # #542 - make sure resources in noop mode still notify their resources, # so that users know if a service will get restarted. def test_noop_with_notify path = tempfile epath = tempfile spath = tempfile file = Puppet::Type.type(:file).new( :path => path, :ensure => :file, :title => "file") exec = Puppet::Type.type(:exec).new( :command => "touch #{epath}", :path => ENV["PATH"], :subscribe => file, :refreshonly => true, :title => 'exec1') exec2 = Puppet::Type.type(:exec).new( :command => "touch #{spath}", :path => ENV["PATH"], :subscribe => exec, :refreshonly => true, :title => 'exec2') Puppet[:noop] = true assert(file.noop, "file not in noop") assert(exec.noop, "exec not in noop") @logs.clear assert_apply(file, exec, exec2) assert(! FileTest.exists?(path), "Created file in noop") assert(! FileTest.exists?(epath), "Executed exec in noop") assert(! FileTest.exists?(spath), "Executed second exec in noop") assert(@logs.detect { |l| l.message =~ /should be/ and l.source == file.property(:ensure).path}, "did not log file change") assert( @logs.detect { |l| l.message =~ /Would have/ and l.source == exec.path }, "did not log first exec trigger") assert( @logs.detect { |l| l.message =~ /Would have/ and l.source == exec2.path }, "did not log second exec trigger") end def test_only_stop_purging_with_relations files = [] paths = [] 3.times do |i| path = tempfile paths << path file = Puppet::Type.type(:file).new( :path => path, :ensure => :absent, :backup => false, :title => "file#{i}") File.open(path, "w") { |f| f.puts "" } files << file end files[0][:ensure] = :file files[0][:require] = files[1..2] # Mark the second as purging files[1].purging assert_apply(*files) assert(FileTest.exists?(paths[1]), "Deleted required purging file") assert(! FileTest.exists?(paths[2]), "Did not delete non-purged file") end def test_flush $state = :absent $flushed = 0 type = Puppet::Type.newtype(:flushtest) do newparam(:name) newproperty(:ensure) do newvalues :absent, :present, :other def retrieve $state end def set(value) $state = value :thing_changed end end def flush $flushed += 1 end end cleanup { Puppet::Type.rmtype(:flushtest) } obj = type.new(:name => "test", :ensure => :present) # first make sure it runs through and flushes assert_apply(obj) assert_equal(:present, $state, "Object did not make a change") assert_equal(1, $flushed, "object was not flushed") # Now run a noop and make sure we don't flush obj[:ensure] = "other" obj[:noop] = true assert_apply(obj) assert_equal(:present, $state, "Object made a change in noop") assert_equal(1, $flushed, "object was flushed in noop") end end