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/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/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/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/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