diff --git a/acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb b/acceptance/pending/pluginsync/7316_faces_should_be_available_via_pluginsync.rb similarity index 90% copy from acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb copy to acceptance/pending/pluginsync/7316_faces_should_be_available_via_pluginsync.rb index 616942f13..281c68fc4 100644 --- a/acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb +++ b/acceptance/pending/pluginsync/7316_faces_should_be_available_via_pluginsync.rb @@ -1,262 +1,242 @@ test_name "the pluginsync functionality should sync app definitions, and they should be runnable afterwards" # -# This test is intended to ensure that pluginsync syncs app and face definitions to the agents. -# Further, the apps and faces should be runnable on the agent after the sync has occurred. +# This test is intended to ensure that pluginsync syncs face definitions to the agents. +# Further, the face should be runnable on the agent after the sync has occurred. # # (NOTE: When this test is passing, it should resolve both #7316 re: verifying that apps/faces can # be run on the agent node after a plugin sync, and #6753 re: being able to run a face without # having a placeholder stub file in the "applications" directory.) # ############################################################################### # BEGIN UTILITY METHODS - ideally this stuff would live somewhere besides in # the actual test. ############################################################################### # Create a file on the host. # Parameters: # [host] the host to create the file on # [file_path] the path to the file to be created # [file_content] a string containing the contents to be written to the file # [options] a hash containing additional behavior options. Currently supported: # * :mkdirs (default false) if true, attempt to create the parent directories on the remote host before writing # the file # * :owner (default 'root') the username of the user that the file should be owned by # * :group (default 'puppet') the name of the group that the file should be owned by # * :mode (default '644') the mode (file permissions) that the file should be created with def create_test_file(host, file_rel_path, file_content, options) # set default options options[:mkdirs] ||= false options[:owner] ||= "root" options[:group] ||= "puppet" options[:mode] ||= "755" file_path = get_test_file_path(host, file_rel_path) mkdirs(host, File.dirname(file_path)) if (options[:mkdirs] == true) create_remote_file(host, file_path, file_content) # # NOTE: we need these chown/chmod calls because the acceptance framework connects to the nodes as "root", but # puppet 'master' runs as user 'puppet'. Therefore, in order for puppet master to be able to read any files # that we've created, we have to carefully set their permissions # chown(host, options[:owner], options[:group], file_path) chmod(host, options[:mode], file_path) end # Given a relative path, returns an absolute path for a test file. Basically, this just prepends the # a unique temp dir path (specific to the current test execution) to your relative path. def get_test_file_path(host, file_rel_path) File.join(@host_test_tmp_dirs[host.name], file_rel_path) end # Check for the existence of a temp file for the current test; basically, this just calls file_exists?(), # but prepends the path to the current test's temp dir onto the file_rel_path parameter. This allows # tests to be written using only a relative path to specify file locations, while still taking advantage # of automatic temp file cleanup at test completion. def test_file_exists?(host, file_rel_path) - # I don't think we can easily use "test -f" here, because our "execute" commands are all built around reading - # stdout as opposed to reading process exit codes - result = host.execute("ruby -e \"print File.exists?('#{get_test_file_path(host, file_rel_path)}')\"") - # get a boolean return value - result == "true" + host.execute("test -f \"#{get_test_file_path(host, file_rel_path)}\"", + :acceptable_exit_codes => [0, 1]) do |result| + return result.exit_code == 0 + end end def tmpdir(host, basename) host_tmpdir = host.tmpdir(basename) # we need to make sure that the puppet user can traverse this directory... chmod(host, "755", host_tmpdir) host_tmpdir end def mkdirs(host, dir_path) on(host, "mkdir -p #{dir_path}") end def chown(host, owner, group, path) on(host, "chown #{owner}:#{group} #{path}") end def chmod(host, mode, path) on(host, "chmod #{mode} #{path}") end # pluck this out of the test case environment; not sure if there is a better way cur_test_file = @path cur_test_file_shortname = File.basename(cur_test_file, File.extname(cur_test_file)) # we need one list of all of the hosts, to assist in managing temp dirs. It's possible # that the master is also an agent, so this will consolidate them into a unique set all_hosts = Set[master, *agents] # now we can create a hash of temp dirs--one per host, and unique to this test--without worrying about # doing it twice on any individual host @host_test_tmp_dirs = Hash[all_hosts.map do |host| [host.name, tmpdir(host, cur_test_file_shortname)] end ] # a silly variable for keeping track of whether or not all of the tests passed... all_tests_passed = false ############################################################################### # END UTILITY METHODS ############################################################################### ############################################################################### # BEGIN TEST LOGIC ############################################################################### # create some vars to point to the directories that we're going to point the master/agents at master_module_dir = "master_modules" agent_lib_dir = "agent_lib" app_name = "superbogus" app_desc = "a simple %1$s for testing %1$s delivery via plugin sync" app_output = "Hello from the #{app_name} %s" master_module_file_content = {} -master_module_file_content["application"] = <<-HERE -require 'puppet/application' - -class Puppet::Application::#{app_name.capitalize} < Puppet::Application - - def help - <<-HELP - -puppet-#{app_name}(8) -- #{app_desc % "application"} -======== - HELP - end - - def main() - puts("#{app_output % "application"}") - end -end -HERE - master_module_file_content["face"] = <<-HERE Puppet::Face.define(:#{app_name}, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "#{app_desc % "face"}" action(:foo) do summary "a test action defined in the test face in the main puppet lib dir" default when_invoked do |*args| puts "#{app_output % "face"}" end end end HERE # this begin block is here for handling temp file cleanup via an "ensure" block at the very end of the # test. begin - modes = ["application", "face"] + modes = ["face"] modes.each do |mode| # here we create a custom app, which basically doesn't do anything except for print a hello-world message agent_module_app_file = "#{agent_lib_dir}/puppet/#{mode}/#{app_name}.rb" master_module_app_file = "#{master_module_dir}/#{app_name}/lib/puppet/#{mode}/#{app_name}.rb" # copy all the files to the master step "write our simple module out to the master" do create_test_file(master, master_module_app_file, master_module_file_content[mode], :mkdirs => true) end step "verify that the app file exists on the master" do unless test_file_exists?(master, master_module_app_file) then fail_test("Failed to create app file '#{get_test_file_path(master, master_module_app_file)}' on master") end end step "start the master" do with_master_running_on(master, "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\"") do # the module files shouldn't exist on the agent yet because they haven't been synced step "verify that the module files don't exist on the agent path" do agents.each do |agent| if test_file_exists?(agent, agent_module_app_file) then fail_test("app file already exists on agent: '#{get_test_file_path(agent, agent_module_app_file)}'") end end end step "run the agent" do agents.each do |agent| run_agent_on(agent, "--trace --libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\" " + "--no-daemonize --verbose --onetime --test --server #{master}") end end end end step "verify that the module files were synced down to the agent" do agents.each do |agent| unless test_file_exists?(agent, agent_module_app_file) then fail_test("Expected app file not synced to agent: '#{get_test_file_path(agent, agent_module_app_file)}'") end end end step "verify that the application shows up in help" do agents.each do |agent| on(agent, PuppetCommand.new(:help, "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do assert_match(/^\s+#{app_name}\s+#{app_desc % mode}$/, result.stdout) end end end step "verify that we can run the application" do agents.each do |agent| on(agent, PuppetCommand.new(:"#{app_name}", "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do assert_match(/^#{app_output % mode}$/, result.stdout) end end end step "clear out the libdir on the agents in preparation for the next test" do agents.each do |agent| on(agent, "rm -rf #{get_test_file_path(agent, agent_module_app_file)}/*") end end end all_tests_passed = true ensure ########################################################################################## # Clean up all of the temp files created by this test. It would be nice if this logic # could be handled outside of the test itself; I envision a stanza like this one appearing # in a very large number of the tests going forward unless it is handled by the framework. ########################################################################################## if all_tests_passed then all_hosts.each do |host| on(host, "rm -rf #{@host_test_tmp_dirs[host.name]}") end end end \ No newline at end of file diff --git a/acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb b/acceptance/tests/pluginsync/7316_apps_should_be_available_via_pluginsync.rb similarity index 87% copy from acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb copy to acceptance/tests/pluginsync/7316_apps_should_be_available_via_pluginsync.rb index 616942f13..01768109c 100644 --- a/acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb +++ b/acceptance/tests/pluginsync/7316_apps_should_be_available_via_pluginsync.rb @@ -1,262 +1,237 @@ test_name "the pluginsync functionality should sync app definitions, and they should be runnable afterwards" # -# This test is intended to ensure that pluginsync syncs app and face definitions to the agents. -# Further, the apps and faces should be runnable on the agent after the sync has occurred. -# -# (NOTE: When this test is passing, it should resolve both #7316 re: verifying that apps/faces can -# be run on the agent node after a plugin sync, and #6753 re: being able to run a face without -# having a placeholder stub file in the "applications" directory.) +# This test is intended to ensure that pluginsync syncs app definitions to the agents. +# Further, the apps should be runnable on the agent after the sync has occurred. # ############################################################################### # BEGIN UTILITY METHODS - ideally this stuff would live somewhere besides in # the actual test. ############################################################################### # Create a file on the host. # Parameters: # [host] the host to create the file on # [file_path] the path to the file to be created # [file_content] a string containing the contents to be written to the file # [options] a hash containing additional behavior options. Currently supported: # * :mkdirs (default false) if true, attempt to create the parent directories on the remote host before writing # the file # * :owner (default 'root') the username of the user that the file should be owned by # * :group (default 'puppet') the name of the group that the file should be owned by # * :mode (default '644') the mode (file permissions) that the file should be created with def create_test_file(host, file_rel_path, file_content, options) # set default options options[:mkdirs] ||= false options[:owner] ||= "root" options[:group] ||= "puppet" options[:mode] ||= "755" file_path = get_test_file_path(host, file_rel_path) mkdirs(host, File.dirname(file_path)) if (options[:mkdirs] == true) create_remote_file(host, file_path, file_content) # # NOTE: we need these chown/chmod calls because the acceptance framework connects to the nodes as "root", but # puppet 'master' runs as user 'puppet'. Therefore, in order for puppet master to be able to read any files # that we've created, we have to carefully set their permissions # chown(host, options[:owner], options[:group], file_path) chmod(host, options[:mode], file_path) end # Given a relative path, returns an absolute path for a test file. Basically, this just prepends the # a unique temp dir path (specific to the current test execution) to your relative path. def get_test_file_path(host, file_rel_path) File.join(@host_test_tmp_dirs[host.name], file_rel_path) end # Check for the existence of a temp file for the current test; basically, this just calls file_exists?(), # but prepends the path to the current test's temp dir onto the file_rel_path parameter. This allows # tests to be written using only a relative path to specify file locations, while still taking advantage # of automatic temp file cleanup at test completion. def test_file_exists?(host, file_rel_path) - # I don't think we can easily use "test -f" here, because our "execute" commands are all built around reading - # stdout as opposed to reading process exit codes - result = host.execute("ruby -e \"print File.exists?('#{get_test_file_path(host, file_rel_path)}')\"") - # get a boolean return value - result == "true" + host.execute("test -f \"#{get_test_file_path(host, file_rel_path)}\"", + :acceptable_exit_codes => [0, 1]) do |result| + return result.exit_code == 0 + end end def tmpdir(host, basename) host_tmpdir = host.tmpdir(basename) # we need to make sure that the puppet user can traverse this directory... chmod(host, "755", host_tmpdir) host_tmpdir end def mkdirs(host, dir_path) on(host, "mkdir -p #{dir_path}") end def chown(host, owner, group, path) on(host, "chown #{owner}:#{group} #{path}") end def chmod(host, mode, path) on(host, "chmod #{mode} #{path}") end # pluck this out of the test case environment; not sure if there is a better way cur_test_file = @path cur_test_file_shortname = File.basename(cur_test_file, File.extname(cur_test_file)) # we need one list of all of the hosts, to assist in managing temp dirs. It's possible # that the master is also an agent, so this will consolidate them into a unique set all_hosts = Set[master, *agents] # now we can create a hash of temp dirs--one per host, and unique to this test--without worrying about # doing it twice on any individual host @host_test_tmp_dirs = Hash[all_hosts.map do |host| [host.name, tmpdir(host, cur_test_file_shortname)] end ] # a silly variable for keeping track of whether or not all of the tests passed... all_tests_passed = false ############################################################################### # END UTILITY METHODS ############################################################################### ############################################################################### # BEGIN TEST LOGIC ############################################################################### # create some vars to point to the directories that we're going to point the master/agents at master_module_dir = "master_modules" agent_lib_dir = "agent_lib" app_name = "superbogus" app_desc = "a simple %1$s for testing %1$s delivery via plugin sync" app_output = "Hello from the #{app_name} %s" master_module_file_content = {} master_module_file_content["application"] = <<-HERE require 'puppet/application' class Puppet::Application::#{app_name.capitalize} < Puppet::Application def help <<-HELP puppet-#{app_name}(8) -- #{app_desc % "application"} ======== HELP end def main() puts("#{app_output % "application"}") end end HERE -master_module_file_content["face"] = <<-HERE -Puppet::Face.define(:#{app_name}, '0.0.1') do - copyright "Puppet Labs", 2011 - license "Apache 2 license; see COPYING" - - summary "#{app_desc % "face"}" - - action(:foo) do - summary "a test action defined in the test face in the main puppet lib dir" - - default - when_invoked do |*args| - puts "#{app_output % "face"}" - end - end - -end -HERE - - - # this begin block is here for handling temp file cleanup via an "ensure" block at the very end of the # test. begin - modes = ["application", "face"] + modes = ["application"] modes.each do |mode| # here we create a custom app, which basically doesn't do anything except for print a hello-world message agent_module_app_file = "#{agent_lib_dir}/puppet/#{mode}/#{app_name}.rb" master_module_app_file = "#{master_module_dir}/#{app_name}/lib/puppet/#{mode}/#{app_name}.rb" # copy all the files to the master step "write our simple module out to the master" do create_test_file(master, master_module_app_file, master_module_file_content[mode], :mkdirs => true) end step "verify that the app file exists on the master" do unless test_file_exists?(master, master_module_app_file) then fail_test("Failed to create app file '#{get_test_file_path(master, master_module_app_file)}' on master") end end step "start the master" do with_master_running_on(master, - "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\"") do + "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\" " + + "--autosign true") do # the module files shouldn't exist on the agent yet because they haven't been synced step "verify that the module files don't exist on the agent path" do agents.each do |agent| if test_file_exists?(agent, agent_module_app_file) then fail_test("app file already exists on agent: '#{get_test_file_path(agent, agent_module_app_file)}'") end end end step "run the agent" do agents.each do |agent| run_agent_on(agent, "--trace --libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\" " + "--no-daemonize --verbose --onetime --test --server #{master}") end end end end step "verify that the module files were synced down to the agent" do agents.each do |agent| unless test_file_exists?(agent, agent_module_app_file) then fail_test("Expected app file not synced to agent: '#{get_test_file_path(agent, agent_module_app_file)}'") end end end step "verify that the application shows up in help" do agents.each do |agent| on(agent, PuppetCommand.new(:help, "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do assert_match(/^\s+#{app_name}\s+#{app_desc % mode}$/, result.stdout) end end end step "verify that we can run the application" do agents.each do |agent| on(agent, PuppetCommand.new(:"#{app_name}", "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do assert_match(/^#{app_output % mode}$/, result.stdout) end end end step "clear out the libdir on the agents in preparation for the next test" do agents.each do |agent| on(agent, "rm -rf #{get_test_file_path(agent, agent_module_app_file)}/*") end end end all_tests_passed = true ensure ########################################################################################## # Clean up all of the temp files created by this test. It would be nice if this logic # could be handled outside of the test itself; I envision a stanza like this one appearing # in a very large number of the tests going forward unless it is handled by the framework. ########################################################################################## if all_tests_passed then all_hosts.each do |host| on(host, "rm -rf #{@host_test_tmp_dirs[host.name]}") end end end \ No newline at end of file diff --git a/acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb b/acceptance/tests/pluginsync/7316_faces_with_app_stubs_should_be_available_via_pluginsync.rb similarity index 60% rename from acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb rename to acceptance/tests/pluginsync/7316_faces_with_app_stubs_should_be_available_via_pluginsync.rb index 616942f13..f0f7769de 100644 --- a/acceptance/pending/pluginsync/faces/7316_apps_and_faces_should_be_available_via_pluginsync.rb +++ b/acceptance/tests/pluginsync/7316_faces_with_app_stubs_should_be_available_via_pluginsync.rb @@ -1,262 +1,259 @@ test_name "the pluginsync functionality should sync app definitions, and they should be runnable afterwards" # -# This test is intended to ensure that pluginsync syncs app and face definitions to the agents. -# Further, the apps and faces should be runnable on the agent after the sync has occurred. +# This test is intended to ensure that pluginsync syncs face definitions to the agents. +# Further, the face should be runnable on the agent after the sync has occurred. # # (NOTE: When this test is passing, it should resolve both #7316 re: verifying that apps/faces can # be run on the agent node after a plugin sync, and #6753 re: being able to run a face without # having a placeholder stub file in the "applications" directory.) # ############################################################################### # BEGIN UTILITY METHODS - ideally this stuff would live somewhere besides in # the actual test. ############################################################################### # Create a file on the host. # Parameters: # [host] the host to create the file on # [file_path] the path to the file to be created # [file_content] a string containing the contents to be written to the file # [options] a hash containing additional behavior options. Currently supported: # * :mkdirs (default false) if true, attempt to create the parent directories on the remote host before writing # the file # * :owner (default 'root') the username of the user that the file should be owned by # * :group (default 'puppet') the name of the group that the file should be owned by # * :mode (default '644') the mode (file permissions) that the file should be created with def create_test_file(host, file_rel_path, file_content, options) # set default options options[:mkdirs] ||= false options[:owner] ||= "root" options[:group] ||= "puppet" options[:mode] ||= "755" file_path = get_test_file_path(host, file_rel_path) mkdirs(host, File.dirname(file_path)) if (options[:mkdirs] == true) create_remote_file(host, file_path, file_content) # # NOTE: we need these chown/chmod calls because the acceptance framework connects to the nodes as "root", but # puppet 'master' runs as user 'puppet'. Therefore, in order for puppet master to be able to read any files # that we've created, we have to carefully set their permissions # chown(host, options[:owner], options[:group], file_path) chmod(host, options[:mode], file_path) end # Given a relative path, returns an absolute path for a test file. Basically, this just prepends the # a unique temp dir path (specific to the current test execution) to your relative path. def get_test_file_path(host, file_rel_path) File.join(@host_test_tmp_dirs[host.name], file_rel_path) end # Check for the existence of a temp file for the current test; basically, this just calls file_exists?(), # but prepends the path to the current test's temp dir onto the file_rel_path parameter. This allows # tests to be written using only a relative path to specify file locations, while still taking advantage # of automatic temp file cleanup at test completion. def test_file_exists?(host, file_rel_path) - # I don't think we can easily use "test -f" here, because our "execute" commands are all built around reading - # stdout as opposed to reading process exit codes - result = host.execute("ruby -e \"print File.exists?('#{get_test_file_path(host, file_rel_path)}')\"") - # get a boolean return value - result == "true" + host.execute("test -f \"#{get_test_file_path(host, file_rel_path)}\"", + :acceptable_exit_codes => [0, 1]) do |result| + return result.exit_code == 0 + end end def tmpdir(host, basename) host_tmpdir = host.tmpdir(basename) # we need to make sure that the puppet user can traverse this directory... chmod(host, "755", host_tmpdir) host_tmpdir end def mkdirs(host, dir_path) on(host, "mkdir -p #{dir_path}") end def chown(host, owner, group, path) on(host, "chown #{owner}:#{group} #{path}") end def chmod(host, mode, path) on(host, "chmod #{mode} #{path}") end # pluck this out of the test case environment; not sure if there is a better way cur_test_file = @path cur_test_file_shortname = File.basename(cur_test_file, File.extname(cur_test_file)) # we need one list of all of the hosts, to assist in managing temp dirs. It's possible # that the master is also an agent, so this will consolidate them into a unique set all_hosts = Set[master, *agents] # now we can create a hash of temp dirs--one per host, and unique to this test--without worrying about # doing it twice on any individual host @host_test_tmp_dirs = Hash[all_hosts.map do |host| [host.name, tmpdir(host, cur_test_file_shortname)] end ] # a silly variable for keeping track of whether or not all of the tests passed... all_tests_passed = false ############################################################################### # END UTILITY METHODS ############################################################################### ############################################################################### # BEGIN TEST LOGIC ############################################################################### # create some vars to point to the directories that we're going to point the master/agents at master_module_dir = "master_modules" agent_lib_dir = "agent_lib" app_name = "superbogus" app_desc = "a simple %1$s for testing %1$s delivery via plugin sync" app_output = "Hello from the #{app_name} %s" master_module_file_content = {} -master_module_file_content["application"] = <<-HERE -require 'puppet/application' - -class Puppet::Application::#{app_name.capitalize} < Puppet::Application - - def help - <<-HELP - -puppet-#{app_name}(8) -- #{app_desc % "application"} -======== - HELP - end - - def main() - puts("#{app_output % "application"}") - end -end -HERE - -master_module_file_content["face"] = <<-HERE +master_module_face_content = <<-HERE Puppet::Face.define(:#{app_name}, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "#{app_desc % "face"}" action(:foo) do summary "a test action defined in the test face in the main puppet lib dir" default when_invoked do |*args| puts "#{app_output % "face"}" end end end HERE +master_module_app_content = <<-HERE +require 'puppet/application/face_base' + +class Puppet::Application::#{app_name.capitalize} < Puppet::Application::FaceBase +end + +HERE + # this begin block is here for handling temp file cleanup via an "ensure" block at the very end of the # test. begin - modes = ["application", "face"] + # here we create a custom app, which basically doesn't do anything except for print a hello-world message + agent_module_face_file = "#{agent_lib_dir}/puppet/face/#{app_name}.rb" + master_module_face_file = "#{master_module_dir}/#{app_name}/lib/puppet/face/#{app_name}.rb" - modes.each do |mode| + agent_module_app_file = "#{agent_lib_dir}/puppet/application/#{app_name}.rb" + master_module_app_file = "#{master_module_dir}/#{app_name}/lib/puppet/application/#{app_name}.rb" - # here we create a custom app, which basically doesn't do anything except for print a hello-world message - agent_module_app_file = "#{agent_lib_dir}/puppet/#{mode}/#{app_name}.rb" - master_module_app_file = "#{master_module_dir}/#{app_name}/lib/puppet/#{mode}/#{app_name}.rb" + # copy all the files to the master + step "write our simple module out to the master" do + create_test_file(master, master_module_app_file, master_module_app_content, :mkdirs => true) + create_test_file(master, master_module_face_file, master_module_face_content, :mkdirs => true) + end - # copy all the files to the master - step "write our simple module out to the master" do - create_test_file(master, master_module_app_file, master_module_file_content[mode], :mkdirs => true) + step "verify that the app file exists on the master" do + unless test_file_exists?(master, master_module_app_file) then + fail_test("Failed to create app file '#{get_test_file_path(master, master_module_app_file)}' on master") end - - step "verify that the app file exists on the master" do - unless test_file_exists?(master, master_module_app_file) then - fail_test("Failed to create app file '#{get_test_file_path(master, master_module_app_file)}' on master") - end + unless test_file_exists?(master, master_module_face_file) then + fail_test("Failed to create face file '#{get_test_file_path(master, master_module_face_file)}' on master") end + end - step "start the master" do - with_master_running_on(master, - "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\"") do - - # the module files shouldn't exist on the agent yet because they haven't been synced - step "verify that the module files don't exist on the agent path" do - agents.each do |agent| - if test_file_exists?(agent, agent_module_app_file) then - fail_test("app file already exists on agent: '#{get_test_file_path(agent, agent_module_app_file)}'") - end - end - end - - step "run the agent" do - agents.each do |agent| - run_agent_on(agent, "--trace --libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\" " + - "--no-daemonize --verbose --onetime --test --server #{master}") - end + step "start the master" do + with_master_running_on(master, + "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\" " + + "--autosign true") do + + # the module files shouldn't exist on the agent yet because they haven't been synced + step "verify that the module files don't exist on the agent path" do + agents.each do |agent| + if test_file_exists?(agent, agent_module_app_file) then + fail_test("app file already exists on agent: '#{get_test_file_path(agent, agent_module_app_file)}'") + end + if test_file_exists?(agent, agent_module_app_file) then + fail_test("face file already exists on agent: '#{get_test_file_path(agent, agent_module_face_file)}'") + end end - end - end - step "verify that the module files were synced down to the agent" do - agents.each do |agent| - unless test_file_exists?(agent, agent_module_app_file) then - fail_test("Expected app file not synced to agent: '#{get_test_file_path(agent, agent_module_app_file)}'") + step "run the agent" do + agents.each do |agent| + run_agent_on(agent, "--trace --libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\" " + + "--no-daemonize --verbose --onetime --test --server #{master}") end end + end + end - step "verify that the application shows up in help" do - agents.each do |agent| - on(agent, PuppetCommand.new(:help, "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do - assert_match(/^\s+#{app_name}\s+#{app_desc % mode}$/, result.stdout) - end + step "verify that the module files were synced down to the agent" do + agents.each do |agent| + unless test_file_exists?(agent, agent_module_app_file) then + fail_test("Expected app file not synced to agent: '#{get_test_file_path(agent, agent_module_app_file)}'") + end + unless test_file_exists?(agent, agent_module_face_file) then + fail_test("Expected face file not synced to agent: '#{get_test_file_path(agent, agent_module_face_file)}'") end end + end - step "verify that we can run the application" do - agents.each do |agent| - on(agent, PuppetCommand.new(:"#{app_name}", "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do - assert_match(/^#{app_output % mode}$/, result.stdout) - end + step "verify that the application shows up in help" do + agents.each do |agent| + on(agent, PuppetCommand.new(:help, "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do + assert_match(/^\s+#{app_name}\s+#{app_desc % "face"}$/, result.stdout) end end + end - step "clear out the libdir on the agents in preparation for the next test" do - agents.each do |agent| - on(agent, "rm -rf #{get_test_file_path(agent, agent_module_app_file)}/*") + step "verify that we can run the application" do + agents.each do |agent| + on(agent, PuppetCommand.new(:"#{app_name}", "--libdir=\"#{get_test_file_path(agent, agent_lib_dir)}\"")) do + assert_match(/^#{app_output % "face"}$/, result.stdout) end end + end + step "clear out the libdir on the agents in preparation for the next test" do + agents.each do |agent| + on(agent, "rm -rf #{get_test_file_path(agent, agent_module_app_file)}/*") + on(agent, "rm -rf #{get_test_file_path(agent, agent_module_face_file)}/*") + end end all_tests_passed = true ensure ########################################################################################## # Clean up all of the temp files created by this test. It would be nice if this logic # could be handled outside of the test itself; I envision a stanza like this one appearing # in a very large number of the tests going forward unless it is handled by the framework. ########################################################################################## if all_tests_passed then all_hosts.each do |host| on(host, "rm -rf #{@host_test_tmp_dirs[host.name]}") end end end \ No newline at end of file diff --git a/acceptance/tests/pluginsync/feature/pluginsync_should_sync_features.rb b/acceptance/tests/pluginsync/feature/pluginsync_should_sync_features.rb index e48039388..80d1c381a 100644 --- a/acceptance/tests/pluginsync/feature/pluginsync_should_sync_features.rb +++ b/acceptance/tests/pluginsync/feature/pluginsync_should_sync_features.rb @@ -1,287 +1,286 @@ test_name "the pluginsync functionality should sync feature definitions" # # This test is intended to ensure that pluginsync syncs feature definitions to the agents. It checks the feature # twice; once to make sure that it gets loaded successfully during the run in which it was synced, and once to # ensure that it still gets loaded successfully during the subsequent run (in which it should not be synced because # the files haven't changed.) # ############################################################################### # BEGIN UTILITY METHODS - ideally this stuff would live somewhere besides in # the actual test. ############################################################################### # Create a file on the host. # Parameters: # [host] the host to create the file on # [file_path] the path to the file to be created # [file_content] a string containing the contents to be written to the file # [options] a hash containing additional behavior options. Currently supported: # * :mkdirs (default false) if true, attempt to create the parent directories on the remote host before writing # the file # * :owner (default 'root') the username of the user that the file should be owned by # * :group (default 'puppet') the name of the group that the file should be owned by # * :mode (default '644') the mode (file permissions) that the file should be created with def create_test_file(host, file_rel_path, file_content, options) # set default options options[:mkdirs] ||= false options[:owner] ||= "root" options[:group] ||= "puppet" options[:mode] ||= "755" file_path = get_test_file_path(host, file_rel_path) mkdirs(host, File.dirname(file_path)) if (options[:mkdirs] == true) create_remote_file(host, file_path, file_content) # # NOTE: we need these chown/chmod calls because the acceptance framework connects to the nodes as "root", but # puppet 'master' runs as user 'puppet'. Therefore, in order for puppet master to be able to read any files # that we've created, we have to carefully set their permissions # chown(host, options[:owner], options[:group], file_path) chmod(host, options[:mode], file_path) end # Given a relative path, returns an absolute path for a test file. Basically, this just prepends the # a unique temp dir path (specific to the current test execution) to your relative path. def get_test_file_path(host, file_rel_path) File.join(@host_test_tmp_dirs[host.name], file_rel_path) end # Check for the existence of a temp file for the current test; basically, this just calls file_exists?(), # but prepends the path to the current test's temp dir onto the file_rel_path parameter. This allows # tests to be written using only a relative path to specify file locations, while still taking advantage # of automatic temp file cleanup at test completion. def test_file_exists?(host, file_rel_path) - # I don't think we can easily use "test -f" here, because our "execute" commands are all built around reading - # stdout as opposed to reading process exit codes - result = host.execute("ruby -e \"print File.exists?('#{get_test_file_path(host, file_rel_path)}')\"") - # get a boolean return value - result == "true" + host.execute("test -f \"#{get_test_file_path(host, file_rel_path)}\"", + :acceptable_exit_codes => [0, 1]) do |result| + return result.exit_code == 0 + end end def tmpdir(host, basename) host_tmpdir = host.tmpdir(basename) # we need to make sure that the puppet user can traverse this directory... chmod(host, "755", host_tmpdir) host_tmpdir end def mkdirs(host, dir_path) on(host, "mkdir -p #{dir_path}") end def chown(host, owner, group, path) on(host, "chown #{owner}:#{group} #{path}") end def chmod(host, mode, path) on(host, "chmod #{mode} #{path}") end # pluck this out of the test case environment; not sure if there is a better way cur_test_file = @path cur_test_file_shortname = File.basename(cur_test_file, File.extname(cur_test_file)) # we need one list of all of the hosts, to assist in managing temp dirs. It's possible # that the master is also an agent, so this will consolidate them into a unique set all_hosts = Set[master, *agents] # now we can create a hash of temp dirs--one per host, and unique to this test--without worrying about # doing it twice on any individual host @host_test_tmp_dirs = Hash[all_hosts.map do |host| [host.name, tmpdir(host, cur_test_file_shortname)] end ] # a silly variable for keeping track of whether or not all of the tests passed... all_tests_passed = false ############################################################################### # END UTILITY METHODS ############################################################################### ############################################################################### # BEGIN TEST LOGIC ############################################################################### # create some vars to point to the directories that we're going to point the master/agents at test_identifier = "pluginsync_should_sync_features" master_module_dir = "master_modules" agent_lib_dir = "agent_lib" module_name = "superbogus" # here we create a custom type, which basically doesn't do anything except for test the value of # our custom feature and write the result to a file agent_module_type_file = "#{agent_lib_dir}/puppet/type/#{module_name}.rb" master_module_type_file = "#{master_module_dir}/#{module_name}/lib/puppet/type/#{module_name}.rb" master_module_type_content = < "Hi. I'm setting the testfeature property of #{module_name} here in site.pp", } HERE # for convenience we build up a list of all of the files we are expecting to deploy on the master all_master_files = [ [master_module_feature_file, 'feature'], [master_module_type_file, 'type'], [master_manifest_file, 'manifest'] ] # for convenience we build up a list of all of the files we are expecting to deploy on the agents all_agent_files = [ [agent_module_feature_file, 'feature'], [agent_module_type_file, 'type'] ] # the command line args we'll pass to the agent each time we call it agent_args = "--trace --libdir=\"%s\" --pluginsync --no-daemonize --verbose " + "--onetime --test --server #{master}" # legal exit codes whenever we run the agent # we need to allow exit code 2, which means "changes were applied" on the agent agent_exit_codes = [0, 2] # this begin block is here for handling temp file cleanup via an "ensure" block at the very end of the # test. begin # copy all the files to the master step "write our simple module out to the master" do create_test_file(master, master_module_type_file, master_module_type_content, :mkdirs => true) create_test_file(master, master_module_feature_file, master_module_feature_content, :mkdirs => true) create_test_file(master, master_manifest_file, master_manifest_content, :mkdirs => true) end step "verify that the module and manifest files exist on the master" do all_master_files.each do |file_path, desc| unless test_file_exists?(master, file_path) then fail_test("Failed to create #{desc} file '#{get_test_file_path(master, file_path)}' on master") end end end step "start the master" do with_master_running_on(master, "--manifest=\"#{get_test_file_path(master, master_manifest_file)}\" " + "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\" " + "--autosign true --pluginsync") do # the module files shouldn't exist on the agent yet because they haven't been synced step "verify that the module files don't exist on the agent path" do agents.each do |agent| all_agent_files.each do |file_path, desc| if test_file_exists?(agent, file_path) then fail_test("#{desc} file already exists on agent: '#{get_test_file_path(agent, file_path)}'") end end end end step "run the agent and verify that it loaded the feature" do agents.each do |agent| run_agent_on(agent, agent_args % get_test_file_path(agent, agent_lib_dir), :acceptable_exit_codes => agent_exit_codes) do assert_match(/The value of the #{module_name} feature is: true/, result.stdout, "Expected agent stdout to include confirmation that the feature was 'true'") end end end step "verify that the module files were synced down to the agent" do agents.each do |agent| all_agent_files.each do |file_path, desc| unless test_file_exists?(agent, file_path) then fail_test("Expected #{desc} file not synced to agent: '#{get_test_file_path(agent, file_path)}'") end end end end step "run the agent again" do agents.each do |agent| run_agent_on(agent, agent_args % get_test_file_path(agent, agent_lib_dir), :acceptable_exit_codes => agent_exit_codes) do assert_match(/The value of the #{module_name} feature is: true/, result.stdout, "Expected agent stdout to include confirmation that the feature was 'true'") end end end #TODO: was thinking about putting in a check for the timestamps on the files (maybe add a method for that to # the framework?) to verify that they didn't get re-synced, but it seems like more trouble than it's worth # at the moment. #step "verify that the module files were not re-synced" do # fail_test("NOT YET IMPLEMENTED: verify that the module files were not re-synced") #end end all_tests_passed = true end ensure ########################################################################################## # Clean up all of the temp files created by this test. It would be nice if this logic # could be handled outside of the test itself; I envision a stanza like this one appearing # in a very large number of the tests going forward unless it is handled by the framework. ########################################################################################## if all_tests_passed then all_hosts.each do |host| on(host, "rm -rf #{@host_test_tmp_dirs[host.name]}") end end end diff --git a/lib/puppet.rb b/lib/puppet.rb index 4d6f8f112..978d63a55 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,129 +1,131 @@ # Try to load rubygems. Hey rubygems, I hate you. begin require 'rubygems' rescue LoadError end # see the bottom of the file for further inclusions require 'singleton' require 'facter' require 'puppet/error' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/util/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' require 'puppet/util/run_mode' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' module Puppet PUPPETVERSION = '2.7.12' def Puppet.version PUPPETVERSION end class << self include Puppet::Util attr_reader :features attr_writer :name end # the hash that determines how our system behaves @@settings = Puppet::Util::Settings.new # The services running in this process. @services ||= [] require 'puppet/util/logging' extend Puppet::Util::Logging # The feature collection @features = Puppet::Util::Feature.new('puppet/feature') # Load the base features. require 'puppet/feature/base' # Store a new default value. def self.setdefaults(section, hash) @@settings.setdefaults(section, hash) end # configuration parameter access and stuff def self.[](param) if param == :debug return Puppet::Util::Log.level == :debug else return @@settings[param] end end # configuration parameter access and stuff def self.[]=(param,value) @@settings[param] = value end def self.clear @@settings.clear end def self.debug=(value) if value Puppet::Util::Log.level=(:debug) else Puppet::Util::Log.level=(:notice) end end def self.settings @@settings end def self.run_mode $puppet_application_mode || Puppet::Util::RunMode[:user] end def self.application_name $puppet_application_name ||= "apply" end # Load all of the configuration parameters. require 'puppet/defaults' def self.genmanifest if Puppet[:genmanifest] puts Puppet.settings.to_manifest exit(0) end end # Parse the config file for this process. def self.parse_config Puppet.settings.parse end # Create a new type. Just proxy to the Type class. The mirroring query # code was deprecated in 2008, but this is still in heavy use. I suppose # this can count as a soft deprecation for the next dev. --daniel 2011-04-12 def self.newtype(name, options = {}, &block) Puppet::Type.newtype(name, options, &block) end end +# TODO cprice: hmmmm.... + require 'puppet/type' require 'puppet/parser' require 'puppet/resource' require 'puppet/network' require 'puppet/ssl' require 'puppet/module' require 'puppet/util/storage' require 'puppet/status' require 'puppet/file_bucket/file' diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index c3f5a3bef..f5ac84468 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -1,405 +1,431 @@ require 'optparse' require 'puppet/util/plugins' +require 'puppet/error' # This class handles all the aspects of a Puppet application/executable # * setting up options # * setting up logs # * choosing what to run # * representing execution status # # === Usage # An application is a subclass of Puppet::Application. # # For legacy compatibility, # Puppet::Application[:example].run # is equivalent to # Puppet::Application::Example.new.run # # # class Puppet::Application::Example << Puppet::Application # # def preinit # # perform some pre initialization # @all = false # end # # # run_command is called to actually run the specified command # def run_command # send Puppet::Util::CommandLine.new.args.shift # end # # # option uses metaprogramming to create a method # # and also tells the option parser how to invoke that method # option("--arg ARGUMENT") do |v| # @args << v # end # # option("--debug", "-d") do |v| # @debug = v # end # # option("--all", "-a:) do |v| # @all = v # end # # def handle_unknown(opt,arg) # # last chance to manage an option # ... # # let's say to the framework we finally handle this option # true # end # # def read # # read action # end # # def write # # writeaction # end # # end # # === Preinit # The preinit block is the first code to be called in your application, before option parsing, # setup or command execution. # # === Options # Puppet::Application uses +OptionParser+ to manage the application options. # Options are defined with the +option+ method to which are passed various # arguments, including the long option, the short option, a description... # Refer to +OptionParser+ documentation for the exact format. # * If the option method is given a block, this one will be called whenever # the option is encountered in the command-line argument. # * If the option method has no block, a default functionnality will be used, that # stores the argument (or true/false if the option doesn't require an argument) in # the global (to the application) options array. # * If a given option was not defined by a the +option+ method, but it exists as a Puppet settings: # * if +unknown+ was used with a block, it will be called with the option name and argument # * if +unknown+ wasn't used, then the option/argument is handed to Puppet.settings.handlearg for # a default behavior # # --help is managed directly by the Puppet::Application class, but can be overriden. # # === Setup # Applications can use the setup block to perform any initialization. # The defaul +setup+ behaviour is to: read Puppet configuration and manage log level and destination # # === What and how to run # If the +dispatch+ block is defined it is called. This block should return the name of the registered command # to be run. # If it doesn't exist, it defaults to execute the +main+ command if defined. # # === Execution state # The class attributes/methods of Puppet::Application serve as a global place to set and query the execution # status of the application: stopping, restarting, etc. The setting of the application status does not directly # aftect its running status; it's assumed that the various components within the application will consult these # settings appropriately and affect their own processing accordingly. Control operations (signal handlers and # the like) should set the status appropriately to indicate to the overall system that it's the process of # stopping or restarting (or just running as usual). # # So, if something in your application needs to stop the process, for some reason, you might consider: # # def stop_me! # # indicate that we're stopping # Puppet::Application.stop! # # ...do stuff... # end # # And, if you have some component that involves a long-running process, you might want to consider: # # def my_long_process(giant_list_to_munge) # giant_list_to_munge.collect do |member| # # bail if we're stopping # return if Puppet::Application.stop_requested? # process_member(member) # end # end module Puppet class Application require 'puppet/util' include Puppet::Util DOCPATTERN = ::File.expand_path(::File.dirname(__FILE__) + "/util/command_line/*" ) + class << self include Puppet::Util attr_accessor :run_status def clear! self.run_status = nil end def stop! self.run_status = :stop_requested end def restart! self.run_status = :restart_requested end # Indicates that Puppet::Application.restart! has been invoked and components should # do what is necessary to facilitate a restart. def restart_requested? :restart_requested == run_status end # Indicates that Puppet::Application.stop! has been invoked and components should do what is necessary # for a clean stop. def stop_requested? :stop_requested == run_status end # Indicates that one of stop! or start! was invoked on Puppet::Application, and some kind of process # shutdown/short-circuit may be necessary. def interrupted? [:restart_requested, :stop_requested].include? run_status end # Indicates that Puppet::Application believes that it's in usual running run_mode (no stop/restart request # currently active). def clear? run_status.nil? end # Only executes the given block if the run status of Puppet::Application is clear (no restarts, stops, # etc. requested). # Upon block execution, checks the run status again; if a restart has been requested during the block's # execution, then controlled_run will send a new HUP signal to the current process. # Thus, long-running background processes can potentially finish their work before a restart. def controlled_run(&block) return unless clear? result = block.call Process.kill(:HUP, $PID) if restart_requested? result end # used to declare code that handle an option def option(*options, &block) long = options.find { |opt| opt =~ /^--/ }.gsub(/^--(?:\[no-\])?([^ =]+).*$/, '\1' ).gsub('-','_') fname = symbolize("handle_#{long}") if (block_given?) define_method(fname, &block) else define_method(fname) do |value| self.options["#{long}".to_sym] = value end end self.option_parser_commands << [options, fname] end def banner(banner = nil) @banner ||= banner end def option_parser_commands @option_parser_commands ||= ( superclass.respond_to?(:option_parser_commands) ? superclass.option_parser_commands.dup : [] ) @option_parser_commands end def find(name) klass = name.to_s.capitalize begin require ::File.join('puppet', 'application', name.to_s.downcase) rescue LoadError => e puts "Unable to find application '#{name}'. #{e}" Kernel::exit(1) end self.const_get(klass) end def [](name) find(name).new end # # TODO cprice: look into changing this into two methods (getter/setter) # --cprice 2012-03-06 # # Sets or gets the run_mode name. Sets the run_mode name if a mode_name is # passed. Otherwise, gets the run_mode or a default run_mode # def run_mode( mode_name = nil) return @run_mode if @run_mode and not mode_name require 'puppet/util/run_mode' @run_mode = Puppet::Util::RunMode[ mode_name || :user ] end end attr_reader :options, :command_line # Every app responds to --version option("--version", "-V") do |arg| puts "#{Puppet.version}" exit end # Every app responds to --help option("--help", "-h") do |v| puts help exit end + def app_defaults() + { + :name => name, + :run_mode => @run_mode.name, + :confdir => @run_mode.conf_dir, + :vardir => @run_mode.var_dir, + :rundir => @run_mode.run_dir, + :logdir => @run_mode.log_dir, + } + end + + def initialize_app_defaults() + Puppet.settings.initialize_app_defaults(app_defaults) + end + # override to execute code before running anything else def preinit end def initialize(command_line = nil) require 'puppet/util/command_line' @command_line = command_line || Puppet::Util::CommandLine.new set_run_mode self.class.run_mode @options = {} - require 'puppet' - require 'puppet/util/instrumentation' - Puppet::Util::Instrumentation.init end # WARNING: This is a totally scary, frightening, and nasty internal API. We # strongly advise that you do not use this, and if you insist, we will # politely allow you to keep both pieces of your broken code. # # We plan to provide a supported, long-term API to deliver this in a way # that you can use. Please make sure that you let us know if you do require # this, and this message is still present in the code. --daniel 2011-02-03 def set_run_mode(mode) @run_mode = mode $puppet_application_mode = @run_mode $puppet_application_name = name - # XXXXXXXXXXX TODO cprice: this is crap. need to refactor this so that applications have a hook to initialize - # the settings that are specific to them; need to decide whether to formally specify the list of such - # settings, or just allow them to run willy-nilly. - if (mode.name == :master) - Puppet.settings.set_value(:facts_terminus, "yaml", :mutable_defaults) - end + ## XXXXXXXXXXX TODO cprice: this is crap. need to refactor this so that applications have a hook to initialize + ## the settings that are specific to them; need to decide whether to formally specify the list of such + ## settings, or just allow them to run willy-nilly. + #if (mode.name == :master) + # Puppet.settings.set_value(:facts_terminus, "yaml", :mutable_defaults) + #end + # + # + ### TODO cprice: get rid of this whole block, push it to some kind of application-specific hook or something + # + #if Puppet.respond_to? :settings + # # This is to reduce the amount of confusion in rspec + # # because it might have loaded defaults.rb before the globals were set + # # and thus have the wrong defaults for the current application + # Puppet.settings.set_value(:confdir, Puppet.run_mode.conf_dir, :mutable_defaults) + # Puppet.settings.set_value(:vardir, Puppet.run_mode.var_dir, :mutable_defaults) + # Puppet.settings.set_value(:name, Puppet.application_name.to_s, :mutable_defaults) + # #Puppet.settings.set_value(:logdir, Puppet.run_mode.logopts, :mutable_defaults) + # Puppet.settings.set_value(:rundir, Puppet.run_mode.run_dir, :mutable_defaults) + # Puppet.settings.set_value(:run_mode, Puppet.run_mode.name.to_s, :mutable_defaults) + #end + end + # This is the main application entry point + def run - ## TODO cprice: get rid of this whole block, push it to some kind of application-specific hook or something + # TODO cprice: the names of these lifecycle phases really suck... but I'm not sure we can do anything about them + # because existing apps/faces use them... maybe make some deprecated aliases? - if Puppet.respond_to? :settings - # This is to reduce the amount of confusion in rspec - # because it might have loaded defaults.rb before the globals were set - # and thus have the wrong defaults for the current application - Puppet.settings.set_value(:confdir, Puppet.run_mode.conf_dir, :mutable_defaults) - Puppet.settings.set_value(:vardir, Puppet.run_mode.var_dir, :mutable_defaults) - Puppet.settings.set_value(:name, Puppet.application_name.to_s, :mutable_defaults) - #Puppet.settings.set_value(:logdir, Puppet.run_mode.logopts, :mutable_defaults) - Puppet.settings.set_value(:rundir, Puppet.run_mode.run_dir, :mutable_defaults) - Puppet.settings.set_value(:run_mode, Puppet.run_mode.name.to_s, :mutable_defaults) + exit_on_fail("get application-specific default settings") do + plugin_hook('initialize_app_defaults') { initialize_app_defaults } end - end - # This is the main application entry point - def run + require 'puppet' + require 'puppet/util/instrumentation' + Puppet::Util::Instrumentation.init + exit_on_fail("initialize") { plugin_hook('preinit') { preinit } } exit_on_fail("parse application options") { plugin_hook('parse_options') { parse_options } } exit_on_fail("prepare for execution") { plugin_hook('setup') { setup } } exit_on_fail("configure routes from #{Puppet[:route_file]}") { configure_indirector_routes } exit_on_fail("run") { plugin_hook('run_command') { run_command } } end def main raise NotImplementedError, "No valid command or main" end def run_command main end def setup setup_logs end def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end Puppet::Util::Log.setup_default unless options[:setdest] end def configure_indirector_routes route_file = Puppet[:route_file] if ::File.exists?(route_file) routes = YAML.load_file(route_file) application_routes = routes[name.to_s] Puppet::Indirector.configure_routes(application_routes) if application_routes end end def parse_options # Create an option parser option_parser = OptionParser.new(self.class.banner) # TODO cprice: document this better. We need to include the globals so that the app # has an opportunity to override them. # Might be able to make this a little more efficient by sharing the Parser object - # betwween the command line class and this one. + # between the command line class and this one. # # Add all global options to it. Puppet.settings.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| handlearg(option[0], arg) end end # Add options that are local to this application, which were # created using the "option()" metaprogramming method. If there # are any conflicts, this application's options will be favored. self.class.option_parser_commands.each do |options, fname| option_parser.on(*options) do |value| # Call the method that "option()" created. self.send(fname, value) end end # Scan command line. We just hand any exceptions to our upper levels, # rather than printing help and exiting, so that we can meaningfully # respond with context-sensitive help if we want to. --daniel 2011-04-12 option_parser.parse!(self.command_line.args) end def handlearg(opt, val) opt, val = Puppet::Util::CommandLine.clean_opt(opt, val) send(:handle_unknown, opt, val) if respond_to?(:handle_unknown) end # this is used for testing def self.exit(code) exit(code) end def name self.class.to_s.sub(/.*::/,"").downcase.to_sym end def help "No help available for puppet #{name}" end def plugin_hook(step,&block) Puppet::Plugins.send("before_application_#{step}",:application_object => self) x = yield Puppet::Plugins.send("after_application_#{step}",:application_object => self, :return_value => x) x end private :plugin_hook end end diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index d94da7baa..22d691e2d 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -1,236 +1,244 @@ require 'puppet/application' class Puppet::Application::Master < Puppet::Application run_mode :master option("--debug", "-d") option("--verbose", "-v") # internal option, only to be used by ext/rack/config.ru option("--rack") option("--compile host", "-c host") do |arg| options[:node] = arg end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail Puppet.log_exception(detail) end end option("--parseonly") do puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def help <<-HELP puppet-master(8) -- The puppet master daemon ======== SYNOPSIS -------- The central puppet server. Functions as a certificate authority by default. USAGE ----- puppet master [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help] [-l|--logdest |console|syslog] [-v|--verbose] [-V|--version] [--compile ] DESCRIPTION ----------- This command starts an instance of puppet master, running as a daemon and using Ruby's built-in Webrick webserver. Puppet master can also be managed by other application servers; when this is the case, this executable is not used. OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'ssldir' is a valid configuration parameter, so you can specify '--ssldir ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet master with '--genconfig'. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --help: Print this help message. * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. * --compile: Compile a catalogue and output it in JSON from the puppet master. Uses facts contained in the $vardir/yaml/ directory to compile the catalog. EXAMPLE ------- puppet master DIAGNOSTICS ----------- When running as a standalone daemon, puppet master accepts the following signals: * SIGHUP: Restart the puppet master server. * SIGINT and SIGTERM: Shut down the puppet master server. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end + # TODO cprice: look for a cleaner pattern here + def app_defaults() + return @master_defaults if @master_defaults + @master_defaults = super + @master_defaults[:facts_terminus] = 'yaml' + @master_defaults + end + def preinit Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end # Create this first-off, so we have ARGV require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end def run_command if options[:node] compile else main end end def compile Puppet::Util::Log.newdestination :console raise ArgumentError, "Cannot render compiled catalogs without pson support" unless Puppet.features.pson? begin unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node]) raise "Could not compile catalog for #{options[:node]}" end jj catalog.to_resource rescue => detail $stderr.puts detail exit(30) end exit(0) end def main require 'etc' require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' # Make sure we've got a localhost ssl cert Puppet::SSL::Host.localhost # And now configure our server to *only* hit the CA for data, because that's # all it will have write access to. Puppet::SSL::Host.ca_location = :only if Puppet::SSL::CertificateAuthority.ca? if Puppet.features.root? begin Puppet::Util.chuser rescue => detail Puppet.log_exception(detail, "Could not change user to #{Puppet[:user]}: #{detail}") exit(39) end end unless options[:rack] require 'puppet/network/server' @daemon.server = Puppet::Network::Server.new() @daemon.daemonize if Puppet[:daemonize] else require 'puppet/network/http/rack' @app = Puppet::Network::HTTP::Rack.new() end Puppet.notice "Starting Puppet master version #{Puppet.version}" unless options[:rack] @daemon.start else return @app end end def setup raise Puppet::Error.new("Puppet master is not supported on Microsoft Windows") if Puppet.features.microsoft_windows? # Handle the logging settings. if options[:debug] or options[:verbose] if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end unless Puppet[:daemonize] or options[:rack] Puppet::Util::Log.newdestination(:console) options[:setdest] = true end end Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet.settings.use :main, :master, :ssl, :metrics # Cache our nodes in yaml. Currently not configurable. Puppet::Node.indirection.cache_class = :yaml # Configure all of the SSL stuff. if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local Puppet.settings.use :ca Puppet::SSL::CertificateAuthority.instance else Puppet::SSL::Host.ca_location = :none end end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index f1c5ca746..2e6daa2dd 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,961 +1,1474 @@ # !!!! TODO cprice: get rid of all run_mode/application_name calls in here... # The majority of Puppet's configuration settings are set in this file. module Puppet # TODO cprice: get rid of all Puppet.run_mode and Puppet.application_name from this file. setdefaults(:main, - :confdir => [Puppet.run_mode.conf_dir, "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process - is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, - it defaults to being in the user's home directory."], - :vardir => [Puppet.run_mode.var_dir, "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_."], - ### TODO cprice: introduce a constant for this... - :name => ['apply', "The name of the application, if we are running as one. The - default is essentially $0 without the path or `.rb`."], - :run_mode => [Puppet.run_mode.name.to_s, "The effective 'run mode' of the application: master, agent, or user."] + :confdir => { + :default => nil, + :type => :directory, + :desc => + "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process\n" + + "is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user,\n" + + "it defaults to being in the user's home directory.", + }, + :vardir => { + :default => nil, + :type => :directory, + :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", + }, + ### TODO cprice: this is usually getting set to a symbol value. We don't officially have a setting type for that yet... + :name => { + :default => nil, + :desc => "The name of the application, if we are running as one. The\n" + + "default is essentially $0 without the path or `.rb`.", + }, + :run_mode => { + :default => nil, + :desc => "The effective 'run mode' of the application: master, agent, or user.", + } ) - setdefaults(:main, :logdir => Puppet.run_mode.logopts) + setdefaults(:main, + :logdir => { + :default => nil, + :type => :directory, + :desc => "The directory in which to store log files", + } + ) setdefaults(:main, - :trace => [false, "Whether to print stack traces on some errors"], + :trace => { + :default => false, + :type => :boolean, + :desc => "Whether to print stack traces on some errors", + }, :autoflush => { :default => false, + :type => :boolean, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, - :syslogfacility => ["daemon", "What syslog facility to use when logging to - syslog. Syslog has a fixed list of valid facilities, and you must - choose one of those; you cannot just make one up."], - :statedir => { :default => "$vardir/state", - :mode => 01755, - :desc => "The directory where Puppet state is stored. Generally, - this directory can be removed without causing harm (although it - might result in spurious service restarts)." + :syslogfacility => { + :default => "daemon", + :desc => "What syslog facility to use when logging to\n" + + "syslog. Syslog has a fixed list of valid facilities, and you must\n" + + "choose one of those; you cannot just make one up." + }, + :statedir => { + :default => "$vardir/state", + :type => :directory, + :mode => 01755, + :desc => "The directory where Puppet state is stored. Generally, + this directory can be removed without causing harm (although it + might result in spurious service restarts)." }, :rundir => { - :default => Puppet.run_mode.run_dir, - :mode => 01777, - :desc => "Where Puppet PID files are kept." - }, - :genconfig => [false, - "Whether to just print a configuration to stdout and exit. Only makes - sense when used interactively. Takes into account arguments specified - on the CLI."], - :genmanifest => [false, - "Whether to just print a manifest to stdout and exit. Only makes - sense when used interactively. Takes into account arguments specified - on the CLI."], - :configprint => ["", - "Print the value of a specific configuration setting. If the name of a - setting is provided for this, then the value is printed and puppet - exits. Comma-separate multiple values. For a list of all values, - specify 'all'."], + :default => nil, + :type => :directory, + :mode => 01777, + :desc => "Where Puppet PID files are kept." + }, + :genconfig => { + :default => false, + :type => :boolean, + :desc => "Whether to just print a configuration to stdout and exit. Only makes\n" + + "sense when used interactively. Takes into account arguments specified\n" + + "on the CLI.", + }, + :genmanifest => { + :default => false, + :type => :boolean, + :desc => "Whether to just print a manifest to stdout and exit. Only makes\n" + + "sense when used interactively. Takes into account arguments specified\n" + + "on the CLI.", + }, + :configprint => { + :default => "", + :desc => "Print the value of a specific configuration setting. If the name of a\n" + + "setting is provided for this, then the value is printed and puppet\n" + + "exits. Comma-separate multiple values. For a list of all values,\n" + + "specify 'all'.", + }, :color => { :default => (Puppet.features.microsoft_windows? ? "false" : "ansi"), - :type => :setting, - :desc => "Whether to use colors when logging to the console. Valid values are - `ansi` (equivalent to `true`), `html`, and `false`, which produces no color. - Defaults to false on Windows, as its console does not support ansi colors.", - }, - :mkusers => [false, - "Whether to create the necessary user and group that puppet agent will - run as."], - :manage_internal_file_permissions => [true, - "Whether Puppet should manage the owner, group, and mode of files - it uses internally" - ], - :onetime => {:default => false, - :desc => "Run the configuration once, rather than as a long-running - daemon. This is useful for interactively running puppetd.", - :short => 'o' - }, - :path => {:default => "none", - :desc => "The shell search path. Defaults to whatever is inherited - from the parent process.", - :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. - :hook => proc do |value| - ENV["PATH"] = "" if ENV["PATH"].nil? - ENV["PATH"] = value unless value == "none" - paths = ENV["PATH"].split(File::PATH_SEPARATOR) - %w{/usr/sbin /sbin}.each do |path| - ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) + :type => :string, + :desc => "Whether to use colors when logging to the console. Valid values are\n" + + "`ansi` (equivalent to `true`), `html`, and `false`, which produces no color.\n" + + "Defaults to false on Windows, as its console does not support ansi colors.", + }, + :mkusers => { + :default => false, + :type => :boolean, + :desc => "Whether to create the necessary user and group that puppet agent will run as.", + }, + :manage_internal_file_permissions => { + :default => true, + :type => :boolean, + :desc => "Whether Puppet should manage the owner, group, and mode of files it uses internally", + }, + :onetime => { + :default => false, + :type => :boolean, + :desc => "Run the configuration once, rather than as a long-running\n" + + "daemon. This is useful for interactively running puppetd.", + :short => 'o', + }, + :path => { + :default => "none", + :desc => "The shell search path. Defaults to whatever is inherited\n" + + "from the parent process.", + :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. + :hook => proc do |value| + ENV["PATH"] = "" if ENV["PATH"].nil? + ENV["PATH"] = value unless value == "none" + paths = ENV["PATH"].split(File::PATH_SEPARATOR) + %w{/usr/sbin /sbin}.each do |path| + ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) + end + value end - value - end }, - :libdir => {:default => "$vardir/lib", - :desc => "An extra search path for Puppet. This is only useful - for those files that Puppet will load on demand, and is only - guaranteed to work for those cases. In fact, the autoload - mechanism is responsible for making sure this directory - is in Ruby's search path", - :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. - :hook => proc do |value| + :libdir => { + :type => :directory, + :default => "$vardir/lib", + :desc => "An extra search path for Puppet. This is only useful\n" + + "for those files that Puppet will load on demand, and is only\n" + + "guaranteed to work for those cases. In fact, the autoload\n" + + "mechanism is responsible for making sure this directory\n" + + "is in Ruby's search path\n", + #:call_on_define => true, # Call our hook with the default value, so we always get the libdir set. + :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, - :ignoreimport => [false, "If true, allows the parser to continue without requiring - all files referenced with `import` statements to exist. This setting was primarily - designed for use with commit hooks for parse-checking."], - :authconfig => [ "$confdir/namespaceauth.conf", - "The configuration file that defines the rights to the different - namespaces and methods. This can be used as a coarse-grained - authorization system for both `puppet agent` and `puppet master`." - ], - :environment => {:default => "production", :desc => "The environment Puppet is running in. For clients - (e.g., `puppet agent`) this determines the environment itself, which - is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes - we know nothing about." - }, - :diff_args => ["-u", "Which arguments to pass to the diff command when printing differences between - files. The command to use can be chosen with the `diff` setting."], + :ignoreimport => { + :default => false, + :type => :boolean, + :desc => "If true, allows the parser to continue without requiring\n" + + "all files referenced with `import` statements to exist. This setting was primarily\n" + + "designed for use with commit hooks for parse-checking.", + }, + :authconfig => { + :default => "$confdir/namespaceauth.conf", + :desc => "The configuration file that defines the rights to the different\n" + + "namespaces and methods. This can be used as a coarse-grained\n" + + "authorization system for both `puppet agent` and `puppet master`.", + }, + :environment => { + :default => "production", + :desc => "The environment Puppet is running in. For clients\n" + + "(e.g., `puppet agent`) this determines the environment itself, which\n" + + "is used to find modules and much more. For servers (i.e., `puppet master`)\n" + + "this provides the default environment for nodes we know nothing about." + }, + :diff_args => { + :default => "-u", + :desc => "Which arguments to pass to the diff command when printing differences between\n" + + "files. The command to use can be chosen with the `diff` setting.", + }, :diff => { :default => (Puppet.features.microsoft_windows? ? "" : "diff"), - :desc => "Which diff command to use when printing differences between files. This setting - has no default value on Windows, as standard `diff` is not available, but Puppet can use many - third-party diff tools.", - }, - :show_diff => [false, "Whether to log and report a contextual diff when files are being replaced. This causes - partial file contents to pass through Puppet's normal logging and reporting system, so this setting should be - used with caution if you are sending Puppet's reports to an insecure destination. - This feature currently requires the `diff/lcs` Ruby library."], + :desc => "Which diff command to use when printing differences between files. This setting\n" + + "has no default value on Windows, as standard `diff` is not available, but Puppet can use many\n" + + "third-party diff tools.", + }, + :show_diff => { + :type => :boolean, + :default => false, + :desc => "Whether to log and report a contextual diff when files are being replaced. This causes\n" + + "partial file contents to pass through Puppet's normal logging and reporting system, so this setting\n" + + "should be used with caution if you are sending Puppet's reports to an insecure destination.\n" + + "This feature currently requires the `diff/lcs` Ruby library.", + }, :daemonize => { - :default => (Puppet.features.microsoft_windows? ? false : true), - :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, - and to false on Windows (where Puppet currently cannot daemonize).", - :short => "D", - :hook => proc do |value| - if value and Puppet.features.microsoft_windows? - raise "Cannot daemonize on Windows" - end + :type => :boolean, + :default => (Puppet.features.microsoft_windows? ? false : true), + :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, + and to false on Windows (where Puppet currently cannot daemonize).", + :short => "D", + :hook => proc do |value| + if value and Puppet.features.microsoft_windows? + raise "Cannot daemonize on Windows" + end end }, - :maximum_uid => [4294967290, "The maximum allowed UID. Some platforms use negative UIDs - but then ship with tools that do not know how to handle signed ints, so the UIDs show up as - huge numbers that can then not be fed back into the system. This is a hackish way to fail in a - slightly more useful way when that happens."], - :route_file => ["$confdir/routes.yaml", "The YAML file containing indirector route configuration."], - :node_terminus => ["plain", "Where to find information about nodes."], - :catalog_terminus => ["compiler", "Where to get node catalogs. This is useful to change if, for instance, - you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."], + :maximum_uid => { + :default => 4294967290, + :desc => "The maximum allowed UID. Some platforms use negative UIDs\n" + + "but then ship with tools that do not know how to handle signed ints, so the UIDs show up as\n" + + "huge numbers that can then not be fed back into the system. This is a hackish way to fail in a\n" + + "slightly more useful way when that happens.", + }, + :route_file => { + :default => "$confdir/routes.yaml", + :desc => "The YAML file containing indirector route configuration.", + }, + :node_terminus => { + :default => "plain", + :desc => "Where to find information about nodes.", + }, + :catalog_terminus => { + :default => "compiler", + :desc => "Where to get node catalogs. This is useful to change if, for instance, + you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store.", + }, :facts_terminus => { ### TODO cprice: re-wire the master to override this to yaml - :default => Puppet.application_name.to_s == "master" ? 'yaml' : 'facter', - #:default => 'facter', + #:default => Puppet.application_name.to_s == "master" ? 'yaml' : 'facter', + :default => 'facter', :desc => "The node facts terminus.", :hook => proc do |value| require 'puppet/node/facts' # Cache to YAML if we're uploading facts away if %w[rest inventory_service].include? value.to_s Puppet::Node::Facts.indirection.cache_class = :yaml end end }, - :inventory_terminus => [ "$facts_terminus", "Should usually be the same as the facts terminus" ], - :httplog => { :default => "$logdir/http.log", - :owner => "root", - :mode => 0640, - :desc => "Where the puppet agent web server logs." - }, - :http_proxy_host => ["none", - "The HTTP proxy host to use for outgoing connections. Note: You - may need to use a FQDN for the server hostname when using a proxy."], - :http_proxy_port => [3128, "The HTTP proxy port to use for outgoing connections"], - :filetimeout => [ 15, - "The minimum time to wait (in seconds) between checking for updates in + :inventory_terminus => { + :default => "$facts_terminus", + :desc => "Should usually be the same as the facts terminus", + }, + :httplog => { + :default => "$logdir/http.log", + :type => :file, + :owner => "root", + :mode => 0640, + :desc => "Where the puppet agent web server logs.", + }, + :http_proxy_host => { + :default => "none", + :desc => "The HTTP proxy host to use for outgoing connections. Note: You + may need to use a FQDN for the server hostname when using a proxy.", + }, + :http_proxy_port => { + :default => 3128, + :desc => "The HTTP proxy port to use for outgoing connections", + }, + :filetimeout => { + :default => 15, + :desc => "The minimum time to wait (in seconds) between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether - a file (such as manifests or templates) has changed on disk." - ], - :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], - :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], - :queue_source => ["stomp://localhost:61613/", "Which type of queue to use for asynchronous processing. If your stomp server requires - authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1"], - :async_storeconfigs => {:default => false, :desc => "Whether to use a queueing system to provide asynchronous database integration. + a file (such as manifests or templates) has changed on disk.", + }, + :queue_type => { + :default => "stomp", + :desc => "Which type of queue to use for asynchronous processing.", + }, + :queue_type => { + :default => "stomp", + :desc => "Which type of queue to use for asynchronous processing.", + }, + :queue_source => { + :default => "stomp://localhost:61613/", + :desc => "Which type of queue to use for asynchronous processing. If your stomp server requires + authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1", + }, + :async_storeconfigs => { + :default => false, + :type => :boolean, + :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppetqd` be running and that 'PSON' support for ruby be installed.", - :hook => proc do |value| - if value - # This reconfigures the terminii for Node, Facts, and Catalog - Puppet.settings[:storeconfigs] = true + :hook => proc do |value| + if value + # This reconfigures the terminii for Node, Facts, and Catalog + Puppet.settings[:storeconfigs] = true - # But then we modify the configuration - Puppet::Resource::Catalog.indirection.cache_class = :queue - else - raise "Cannot disable asynchronous storeconfigs in a running process" + # But then we modify the configuration + Puppet::Resource::Catalog.indirection.cache_class = :queue + else + raise "Cannot disable asynchronous storeconfigs in a running process" + end end - end }, - :thin_storeconfigs => {:default => false, :desc => - "Boolean; whether storeconfigs store in the database only the facts and exported resources. - If true, then storeconfigs performance will be higher and still allow exported/collected - resources, but other usage external to Puppet might not work", + :thin_storeconfigs => { + :default => false, + :type => :boolean, + :desc => + "Boolean; whether storeconfigs store in the database only the facts and exported resources. + If true, then storeconfigs performance will be higher and still allow exported/collected + resources, but other usage external to Puppet might not work", :hook => proc do |value| Puppet.settings[:storeconfigs] = true if value end }, - :config_version => ["", "How to determine the configuration version. By default, it will be the + :config_version => { + :default => "", + :desc => "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the - reports, allowing you to correlate changes on your hosts to the source version on the server."], - :zlib => [true, - "Boolean; whether to use the zlib library", - ], - :prerun_command => ["", "A command to run before every agent run. If this command returns a non-zero - return code, the entire Puppet run will fail."], - :postrun_command => ["", "A command to run after every agent run. If this command returns a non-zero + reports, allowing you to correlate changes on your hosts to the source version on the server.", + }, + :zlib => { + :default => true, + :type => :boolean, + :desc => "Boolean; whether to use the zlib library", + }, + :prerun_command => { + :default => "", + :desc => "A command to run before every agent run. If this command returns a non-zero + return code, the entire Puppet run will fail.", + }, + :postrun_command => { + :default => "", + :desc => "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have - performed work during the normal run."], - :freeze_main => [false, "Freezes the 'main' class, disallowing any code to be added to it. This - essentially means that you can't have any code outside of a node, class, or definition other - than in the site manifest."] + performed work during the normal run.", + }, + :freeze_main => { + :default => false, + :type => :boolean, + :desc => "Freezes the 'main' class, disallowing any code to be added to it. This\n" + + "essentially means that you can't have any code outside of a node, class, or definition other\n" + + "than in the site manifest.", + } ) Puppet.setdefaults(:module_tool, - :module_repository => ['http://forge.puppetlabs.com', "The module repository"], - :module_working_dir => ['$vardir/puppet-module', "The directory into which module tool data is stored"] + :module_repository => { + :default => 'http://forge.puppetlabs.com', + :desc => "The module repository", + }, + :module_working_dir => { + :default => '$vardir/puppet-module', + :desc => "The directory into which module tool data is stored", + } ) hostname = Facter["hostname"].value domain = Facter["domain"].value if domain and domain != "" fqdn = [hostname, domain].join(".") else fqdn = hostname end Puppet.setdefaults( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for # manipulating naming. - :certname => {:default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults + :certname => { + :default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults to the fully qualified domain name.", :call_on_define => true, # Call our hook with the default value, so we're always downcased :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, :certdnsnames => { :default => '', :hook => proc do |value| unless value.nil? or value == '' then Puppet.warning < < { :default => '', :desc => < { :default => "$ssldir/certs", + :type => :directory, :owner => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", + :type => :directory, :mode => 0771, :owner => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", + :type => :directory, :owner => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", + :type => :directory, + :type => :directory, :owner => "service", :desc => "Where host certificate requests are stored." }, - :privatekeydir => { :default => "$ssldir/private_keys", + :privatekeydir => { + :default => "$ssldir/private_keys", + :type => :directory, :mode => 0750, :owner => "service", :desc => "The private key directory." }, - :privatedir => { :default => "$ssldir/private", + :privatedir => { + :default => "$ssldir/private", + :type => :directory, :mode => 0750, :owner => "service", :desc => "Where the client stores private certificate information." }, - :passfile => { :default => "$privatedir/password", + :passfile => { + :default => "$privatedir/password", + :type => :file, :mode => 0640, :owner => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, - :hostcsr => { :default => "$ssldir/csr_$certname.pem", + :hostcsr => { + :default => "$ssldir/csr_$certname.pem", + :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificate requests." }, - :hostcert => { :default => "$certdir/$certname.pem", + :hostcert => { + :default => "$certdir/$certname.pem", + :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificates." }, - :hostprivkey => { :default => "$privatekeydir/$certname.pem", + :hostprivkey => { + :default => "$privatekeydir/$certname.pem", + :type => :file, :mode => 0600, :owner => "service", :desc => "Where individual hosts store and look for their private key." }, - :hostpubkey => { :default => "$publickeydir/$certname.pem", + :hostpubkey => { + :default => "$publickeydir/$certname.pem", + :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their public key." }, - :localcacert => { :default => "$certdir/ca.pem", + :localcacert => { + :default => "$certdir/ca.pem", + :type => :file, :mode => 0644, :owner => "service", :desc => "Where each client stores the CA certificate." }, - :hostcrl => { :default => "$ssldir/crl.pem", + :hostcrl => { + :default => "$ssldir/crl.pem", + :type => :file, :mode => 0644, :owner => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, - :certificate_revocation => [true, "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) - to all clients. If enabled, CA chaining will almost definitely not work."] + :certificate_revocation => { + :default => true, + :type => :boolean, + :desc => "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) + to all clients. If enabled, CA chaining will almost definitely not work.", + } ) setdefaults( :ca, - :ca_name => ["Puppet CA: $certname", "The name to use the Certificate Authority certificate."], - :cadir => { :default => "$ssldir/ca", + :ca_name => { + :default => "Puppet CA: $certname", + :desc => "The name to use the Certificate Authority certificate.", + }, + :cadir => { + :default => "$ssldir/ca", + :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "The root directory for the certificate authority." }, - :cacert => { :default => "$cadir/ca_crt.pem", + :cacert => { + :default => "$cadir/ca_crt.pem", + :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "The CA certificate." }, - :cakey => { :default => "$cadir/ca_key.pem", + :cakey => { + :default => "$cadir/ca_key.pem", + :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "The CA private key." }, - :capub => { :default => "$cadir/ca_pub.pem", + :capub => { + :default => "$cadir/ca_pub.pem", + :type => :file, :owner => "service", :group => "service", :desc => "The CA public key." }, - :cacrl => { :default => "$cadir/ca_crl.pem", + :cacrl => { + :default => "$cadir/ca_crl.pem", + :type => :file, :owner => "service", :group => "service", :mode => 0664, :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", :hook => proc do |value| if value == 'false' Puppet.deprecation_warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing" end end }, - :caprivatedir => { :default => "$cadir/private", + :caprivatedir => { + :default => "$cadir/private", + :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores private certificate information." }, - :csrdir => { :default => "$cadir/requests", + :csrdir => { + :default => "$cadir/requests", + :type => :directory, :owner => "service", :group => "service", :desc => "Where the CA stores certificate requests" }, - :signeddir => { :default => "$cadir/signed", + :signeddir => { + :default => "$cadir/signed", + :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores signed certificates." }, - :capass => { :default => "$caprivatedir/ca.pass", + :capass => { + :default => "$caprivatedir/ca.pass", + :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "Where the CA stores the password for the private key" }, - :serial => { :default => "$cadir/serial", + :serial => { + :default => "$cadir/serial", + :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "Where the serial number for certificates is stored." }, - :autosign => { :default => "$confdir/autosign.conf", + :autosign => { + :default => "$confdir/autosign.conf", + :type => :file, :mode => 0644, :desc => "Whether to enable autosign. Valid values are true (which autosigns any key request, and is a very bad idea), false (which never autosigns any key request), and the path to a file, which uses that configuration file to determine which keys to sign."}, - :allow_duplicate_certs => [false, "Whether to allow a new certificate - request to overwrite an existing certificate."], - :ca_days => ["", "How long a certificate should be valid, in days. - This setting is deprecated; use `ca_ttl` instead"], - :ca_ttl => ["5y", "The default TTL for new certificates; valid values + :allow_duplicate_certs => { + :default => false, + :type => :boolean, + :desc => "Whether to allow a new certificate + request to overwrite an existing certificate.", + }, + :ca_days => { + :default => "", + :desc => "How long a certificate should be valid, in days. + This setting is deprecated; use `ca_ttl` instead", + }, + :ca_ttl => { + :default => "5y", + :desc => "The default TTL for new certificates; valid values must be an integer, optionally followed by one of the units 'y' (years of 365 days), 'd' (days), 'h' (hours), or 's' (seconds). The unit defaults to seconds. If this setting is set, ca_days is ignored. Examples are '3600' (one hour) - and '1825d', which is the same as '5y' (5 years) "], - :ca_md => ["md5", "The type of hash used in certificates."], - :req_bits => [4096, "The bit length of the certificates."], - :keylength => [4096, "The bit length of keys."], + and '1825d', which is the same as '5y' (5 years) ", + }, + :ca_md => { + :default => "md5", + :desc => "The type of hash used in certificates.", + }, + :req_bits => { + :default => 4096, + :desc => "The bit length of the certificates.", + }, + :keylength => { + :default => 4096, + :desc => "The bit length of keys.", + }, :cert_inventory => { :default => "$cadir/inventory.txt", + :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "A Complete listing of all certificates" } ) # Define the config default. - setdefaults( - Puppet.settings[:name], - :config => ["$confdir/puppet.conf", - "The configuration file for #{Puppet[:name]}."], - :pidfile => ["$rundir/$name.pid", "The pid file"], - :bindaddress => ["", "The address a listening server should bind to. Mongrel servers - default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."], - :servertype => {:default => "webrick", :desc => "The type of server to use. Currently supported + # TODO cprice: this may need some revisiting, especially the bit where we are using Puppet[:name] as if it were + # already set... + + setdefaults(:application, + #Puppet.settings[:name], + :config_file_name => { + :type => :string, + :default => Puppet::Util::Settings.default_config_file_name, + :desc => "The name of the puppet config file.", + }, + :config => { + :type => :file, + :default => "$confdir/${config_file_name}", + :desc => "The configuration file for #{Puppet[:name]}.", + }, + :pidfile => { + :type => :file, + :default => "$rundir/$name.pid", + :desc => "The pid file", + }, + :bindaddress => { + :default => "", + :desc => "The address a listening server should bind to. Mongrel servers + default to 127.0.0.1 and WEBrick defaults to 0.0.0.0.", + }, + :servertype => { + :default => "webrick", :desc => "The type of server to use. Currently supported options are webrick and mongrel. If you use mongrel, you will need a proxy in front of the process or processes, since Mongrel cannot speak SSL.", :call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set. :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" } } ) setdefaults(:master, - :user => ["puppet", "The user puppet master should run as."], - :group => ["puppet", "The group puppet master should run as."], - :manifestdir => ["$confdir/manifests", "Where puppet master looks for its manifests."], - :manifest => ["$manifestdir/site.pp", "The entry-point manifest for puppet master."], - :code => ["", "Code to parse directly. This is essentially only used + :user => { + :default => "puppet", + :desc => "The user puppet master should run as.", + }, + :group => { + :default => "puppet", + :desc => "The group puppet master should run as.", + }, + :manifestdir => { + :default => "$confdir/manifests", + :type => :directory, + :desc => "Where puppet master looks for its manifests.", + }, + :manifest => { + :default => "$manifestdir/site.pp", + :type => :file, + :desc => "The entry-point manifest for puppet master.", + }, + :code => { + :default => "", + :desc => "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet - executable"], - :masterlog => { :default => "$logdir/puppetmaster.log", + executable", + }, + :masterlog => { + :default => "$logdir/puppetmaster.log", + :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "Where puppet master logs. This is generally not used, since syslog is the default log destination." }, - :masterhttplog => { :default => "$logdir/masterhttp.log", + :masterhttplog => { + :default => "$logdir/masterhttp.log", + :type => :file, :owner => "service", :group => "service", :mode => 0660, :create => true, :desc => "Where the puppet master web server logs." }, - :masterport => [8140, "Which port puppet master listens on."], - :node_name => ["cert", "How the puppet master determines the client's identity + :masterport => { + :default => 8140, + :desc => "Which port puppet master listens on.", + }, + :node_name => { + :default => "cert", + :desc => "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client - reported in its facts)"], + reported in its facts)", + }, :bucketdir => { :default => "$vardir/bucket", + :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, - :rest_authconfig => [ "$confdir/auth.conf", - "The configuration file that defines the rights to the different + :rest_authconfig => { + :default => "$confdir/auth.conf", + :type => :file, + :desc => "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained - authorization system for `puppet master`." - ], - :ca => [true, "Wether the master should function as a certificate authority."], + authorization system for `puppet master`.", + }, + :ca => { + :default => true, + :type => :boolean, + :desc => "Whether the master should function as a certificate authority.", + }, :modulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", - :desc => "The search path for modules, as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", - :type => :setting # We don't want this to be considered a file, since it's multiple files. + :type => :path, + :desc => "The search path for modules, as a list of directories separated by the system path separator character. " + + "(The POSIX path separator is ':', and the Windows path separator is ';'.)", }, - :ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated + :ssl_client_header => { + :default => "HTTP_X_CLIENT_DN", + :desc => "The header containing an authenticated client's SSL DN. Only used with Mongrel. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). - See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], - :ssl_client_verify_header => ["HTTP_X_CLIENT_VERIFY", "The header containing the status + See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information.", + }, + :ssl_client_verify_header => { + :default => "HTTP_X_CLIENT_VERIFY", + :desc => "The header containing the status message of the client verification. Only used with Mongrel. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. - See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], + See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information.", + }, # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). - :yamldir => {:default => "$vardir/yaml", :owner => "service", :group => "service", :mode => "750", + :yamldir => { + :default => "$vardir/yaml", + :type => :directory, + :owner => "service", + :group => "service", + :mode => "750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, - :server_datadir => {:default => "$vardir/server_data", :owner => "service", :group => "service", :mode => "750", + :server_datadir => { + :default => "$vardir/server_data", + :type => :directory, + :owner => "service", + :group => "service", + :mode => "750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, - :reports => ["store", - "The list of reports to generate. All reports are looked for - in `puppet/reports/name.rb`, and multiple report names should be - comma-separated (whitespace is okay)." - ], - :reportdir => {:default => "$vardir/reports", + :reports => { + :default => "store", + :desc => "The list of reports to generate. All reports are looked for + in `puppet/reports/name.rb`, and multiple report names should be + comma-separated (whitespace is okay).", + }, + :reportdir => { + :default => "$vardir/reports", + :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, - :reporturl => ["http://localhost:3000/reports/upload", - "The URL used by the http reports processor to send reports"], - :fileserverconfig => ["$confdir/fileserver.conf", "Where the fileserver configuration is stored."], - :strict_hostname_checking => [false, "Whether to only search for the complete - hostname as it is in the certificate when searching for node information - in the catalogs."] + :reporturl => { + :default => "http://localhost:3000/reports/upload", + :desc => "The URL used by the http reports processor to send reports", + }, + :fileserverconfig => { + :default => "$confdir/fileserver.conf", + :type => :file, + :desc => "Where the fileserver configuration is stored.", + }, + :strict_hostname_checking => { + :default => false, + :desc => "Whether to only search for the complete + hostname as it is in the certificate when searching for node information + in the catalogs.", + } ) setdefaults(:metrics, - :rrddir => {:default => "$vardir/rrd", - :mode => 0750, - :owner => "service", - :group => "service", - :desc => "The directory where RRD database files are stored. + :rrddir => { + :type => :directory, + :default => "$vardir/rrd", + :mode => 0750, + :owner => "service", + :group => "service", + :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, - :rrdinterval => ["$runinterval", "How often RRD should expect data. - This should match how often the hosts report back to the server."] + :rrdinterval => { + :default => "$runinterval", + :desc => "How often RRD should expect data. + This should match how often the hosts report back to the server.", + } ) setdefaults(:device, - :devicedir => {:default => "$vardir/devices", :mode => "750", :desc => "The root directory of devices' $vardir"}, - :deviceconfig => ["$confdir/device.conf","Path to the device config file for puppet device"] + :devicedir => { + :default => "$vardir/devices", + :type => :directory, + :mode => "750", + :desc => "The root directory of devices' $vardir", + }, + :deviceconfig => { + :default => "$confdir/device.conf", + :desc => "Path to the device config file for puppet device", + } ) setdefaults(:agent, - :node_name_value => { :default => "$certname", + :node_name_value => { + :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, - :node_name_fact => { :default => "", + :node_name_fact => { + :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, - :localconfig => { :default => "$statedir/localconfig", + :localconfig => { + :default => "$statedir/localconfig", + :type => :file, :owner => "root", :mode => 0660, :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, - :statefile => { :default => "$statedir/state.yaml", + :statefile => { + :default => "$statedir/state.yaml", + :type => :file, :mode => 0660, :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, - :clientyamldir => {:default => "$vardir/client_yaml", :mode => "750", :desc => "The directory in which client-side YAML data is stored."}, - :client_datadir => {:default => "$vardir/client_data", :mode => "750", :desc => "The directory in which serialized data is stored on the client."}, - :classfile => { :default => "$statedir/classes.txt", + :clientyamldir => { + :default => "$vardir/client_yaml", + :type => :directory, + :mode => "750", + :desc => "The directory in which client-side YAML data is stored." + }, + :client_datadir => { + :default => "$vardir/client_data", + :type => :directory, + :mode => "750", + :desc => "The directory in which serialized data is stored on the client." + }, + :classfile => { + :default => "$statedir/classes.txt", + :type => :file, :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, - :resourcefile => { :default => "$statedir/resources.txt", + :resourcefile => { + :default => "$statedir/resources.txt", + :type => :file, :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, - :puppetdlog => { :default => "$logdir/puppetd.log", + :puppetdlog => { + :default => "$logdir/puppetd.log", + :type => :file, :owner => "root", :mode => 0640, :desc => "The log file for puppet agent. This is generally not used." }, :server => { :default => "puppet", :desc => "The server to which the puppet agent should connect" }, - :use_srv_records => [true, "Whether the server will search for SRV records in DNS for the current domain."], - :srv_domain => [ "#{domain}", "The domain which will be queried to find the SRV records of servers to use."], - :ignoreschedules => [false, - "Boolean; whether puppet agent should ignore schedules. This is useful - for initial puppet agent runs."], - :puppetport => [8139, "Which port puppet agent listens on."], - :noop => [false, "Whether puppet agent should be run in noop mode."], - :runinterval => [1800, # 30 minutes - "How often puppet agent applies the client configuration; in seconds. - Note that a runinterval of 0 means \"run continuously\" rather than - \"never run.\" If you want puppet agent to never run, you should start - it with the `--no-client` option."], - :listen => [false, "Whether puppet agent should listen for + :use_srv_records => { + :default => true, + :type => :boolean, + :desc => "Whether the server will search for SRV records in DNS for the current domain.", + }, + :srv_domain => { + :default => "#{domain}", + :desc => "The domain which will be queried to find the SRV records of servers to use.", + }, + :ignoreschedules => { + :default => false, + :type => :boolean, + :desc => "Boolean; whether puppet agent should ignore schedules. This is useful + for initial puppet agent runs.", + }, + :puppetport => { + :default => 8139, + :desc => "Which port puppet agent listens on.", + }, + :noop => { + :default => false, + :type => :boolean, + :desc => "Whether puppet agent should be run in noop mode.", + }, + :runinterval => { + :default => 1800, # 30 minutes + :desc => "How often puppet agent applies the client configuration; in seconds. + Note that a runinterval of 0 means \"run continuously\" rather than + \"never run.\" If you want puppet agent to never run, you should start + it with the `--no-client` option.", + }, + :listen => { + :default => false, + :type => :boolean, + :desc => "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to - requests on the `run`, `facts`, `certificate`, and `resource` endpoints."], - :ca_server => ["$server", "The server to use for certificate + requests on the `run`, `facts`, `certificate`, and `resource` endpoints.", + }, + :ca_server => { + :default => "$server", + :desc => "The server to use for certificate authority requests. It's a separate server because it cannot - and does not need to horizontally scale."], - :ca_port => ["$masterport", "The port to use for the certificate authority."], + and does not need to horizontally scale.", + }, + :ca_port => { + :default => "$masterport", + :desc => "The port to use for the certificate authority.", + }, :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.deprecation_warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings[:preferred_serialization_format] = value end } }, - :preferred_serialization_format => ["pson", "The preferred means of serializing + :preferred_serialization_format => { + :default => "pson", + :desc => "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all - classes that support it."], - :puppetdlockfile => [ "$statedir/puppetdlock", "A lock file to temporarily stop puppet agent from doing anything."], - :usecacheonfailure => [true, - "Whether to use the cached configuration when the remote + classes that support it.", + }, + :puppetdlockfile => { + :default => "$statedir/puppetdlock", + :type => :file, + :desc => "A lock file to temporarily stop puppet agent from doing anything.", + }, + :usecacheonfailure => { + :default => true, + :type => :boolean, + :desc => "Whether to use the cached configuration when the remote configuration will not compile. This option is useful for testing new configurations, where you want to fix the broken configuration - rather than reverting to a known-good one." - ], - :use_cached_catalog => [false, - "Whether to only use the cached catalog rather than compiling a new catalog + rather than reverting to a known-good one.", + }, + :use_cached_catalog => { + :default => false, + :type => :boolean, + :desc => "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively - disabled when a recompile is desired."], - :ignorecache => [false, - "Ignore cache and always recompile the configuration. This is + disabled when a recompile is desired.", + }, + :ignorecache => { + :default => false, + :type => :boolean, + :desc => "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts - change or if the server changes." - ], - :downcasefacts => [false, "Whether facts should be made all lowercase when sent to the server."], - :dynamicfacts => ["memorysize,memoryfree,swapsize,swapfree", - "Facts that are dynamic; these facts will be ignored when deciding whether + change or if the server changes.", + }, + :downcasefacts => { + :default => false, + :type => :boolean, + :desc => "Whether facts should be made all lowercase when sent to the server.", + }, + :dynamicfacts => { + :default => "memorysize,memoryfree,swapsize,swapfree", + :desc => "Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be - comma-separated."], - :splaylimit => ["$runinterval", - "The maximum time to delay before runs. Defaults to being the same as the - run interval."], - :splay => [false, - "Whether to sleep for a pseudo-random (but consistent) amount of time before - a run."], + comma-separated.", + }, + :splaylimit => { + :default => "$runinterval", + :desc => "The maximum time to delay before runs. Defaults to being the same as the + run interval.", + }, + :splay => { + :default => false, + :type => :boolean, + :desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before + a run.", + }, :clientbucketdir => { - :default => "$vardir/clientbucket", - :mode => 0750, - :desc => "Where FileBucket files are stored locally." + :default => "$vardir/clientbucket", + :type => :directory, + :mode => 0750, + :desc => "Where FileBucket files are stored locally." }, - :configtimeout => [120, - "How long the client should wait for the configuration to be retrieved + :configtimeout => { + :default => 120, + :desc => "How long the client should wait for the configuration to be retrieved before considering it a failure. This can help reduce flapping if too - many clients contact the server at one time." - ], + many clients contact the server at one time.", + }, :reportserver => { :default => "$server", :call_on_define => false, :desc => "(Deprecated for 'report_server') The server to which to send transaction reports.", :hook => proc do |value| Puppet.settings[:report_server] = value if value end }, - :report_server => ["$server", - "The server to send transaction reports to." - ], - :report_port => ["$masterport", - "The port to communicate with the report_server." - ], - :inventory_server => ["$server", - "The server to send facts to." - ], - :inventory_port => ["$masterport", - "The port to communicate with the inventory_server." - ], - :report => [true, - "Whether to send reports after every transaction." - ], - :lastrunfile => { :default => "$statedir/last_run_summary.yaml", - :mode => 0644, - :desc => "Where puppet agent stores the last run report summary in yaml format." + :report_server => { + :default => "$server", + :desc => "The server to send transaction reports to.", + }, + :report_port => { + :default => "$masterport", + :desc => "The port to communicate with the report_server.", + }, + :inventory_server => { + :default => "$server", + :desc => "The server to send facts to.", + }, + :inventory_port => { + :default => "$masterport", + :desc => "The port to communicate with the inventory_server.", + }, + :report => { + :default => true, + :type => :boolean, + :desc => "Whether to send reports after every transaction.", + }, + :lastrunfile => { + :default => "$statedir/last_run_summary.yaml", + :type => :file, + :mode => 0644, + :desc => "Where puppet agent stores the last run report summary in yaml format." }, - :lastrunreport => { :default => "$statedir/last_run_report.yaml", - :mode => 0644, - :desc => "Where puppet agent stores the last run report in yaml format." + :lastrunreport => { + :default => "$statedir/last_run_report.yaml", + :type => :file, + :mode => 0644, + :desc => "Where puppet agent stores the last run report in yaml format." }, - :graph => [false, "Whether to create dot graph files for the different + :graph => { + :default => false, + :type => :boolean, + :desc => "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools - like OmniGraffle or dot (which is part of ImageMagick)."], - :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."], - :http_compression => [false, "Allow http compression in REST communication with the master. + like OmniGraffle or dot (which is part of ImageMagick).", + }, + :graphdir => { + :default => "$statedir/graphs", + :type => :directory, + :desc => "Where to store dot-outputted graphs.", + }, + :http_compression => { + :default => false, + :type => :boolean, + :desc => "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support - compression, but if it supports it, this setting might reduce performance on high-speed LANs."], - :waitforcert => [120, # 2 minutes - "The time interval, specified in seconds, 'puppet agent' should connect to the server - and ask it to sign a certificate request. This is useful for the initial setup of a - puppet client. You can turn off waiting for certificates by specifying a time of 0." - ] + compression, but if it supports it, this setting might reduce performance on high-speed LANs.", + }, + :waitforcert => { + :default => 120, # 2 minutes + :desc => "The time interval, specified in seconds, 'puppet agent' should connect to the server + and ask it to sign a certificate request. This is useful for the initial setup of a + puppet client. You can turn off waiting for certificates by specifying a time of 0.", + } ) setdefaults(:inspect, - :archive_files => [false, "During an inspect run, whether to archive files whose contents are audited to a file bucket."], - :archive_file_server => ["$server", "During an inspect run, the file bucket server to archive files to if archive_files is set."] + :archive_files => { + :type => :boolean, + :default => false, + :desc => "During an inspect run, whether to archive files whose contents are audited to a file bucket.", + }, + :archive_file_server => { + :default => "$server", + :desc => "During an inspect run, the file bucket server to archive files to if archive_files is set.", + } ) # Plugin information. setdefaults( :main, - :plugindest => ["$libdir", - "Where Puppet should store plugins that it pulls down from the central - server."], - :pluginsource => ["puppet://$server/plugins", - "From where to retrieve plugins. The standard Puppet `file` type + :plugindest => { + :type => :directory, + :default => "$libdir", + :desc => "Where Puppet should store plugins that it pulls down from the central + server.", + }, + :pluginsource => { + :default => "puppet://$server/plugins", + :desc => "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can - be used here."], - :pluginsync => [true, "Whether plugins should be synced with the central server."], + be used here.", + }, + :pluginsync => { + :default => true, + :type => :boolean, + :desc => "Whether plugins should be synced with the central server.", + }, - :pluginsignore => [".svn CVS .git", "What files to ignore when pulling down plugins."] + :pluginsignore => { + :default => ".svn CVS .git", + :desc => "What files to ignore when pulling down plugins.", + } ) # Central fact information. setdefaults( :main, - :factpath => {:default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", - :desc => "Where Puppet should look for facts. Multiple directories should + :factpath => { + :type => :path, + :default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", + :desc => "Where Puppet should look for facts. Multiple directories should be separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", - :call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. - :type => :setting, # Don't consider it a file, because it could be multiple colon-separated files + #:call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }} ) setdefaults( :tagmail, - :tagmap => ["$confdir/tagmail.conf", "The mapping between reporting tags and email addresses."], - :sendmail => [which('sendmail') || '', "Where to find the sendmail binary with which to send email."], + :tagmap => { + :default => "$confdir/tagmail.conf", + :desc => "The mapping between reporting tags and email addresses.", + }, + :sendmail => { + :default => which('sendmail') || '', + :desc => "Where to find the sendmail binary with which to send email.", + }, + + :reportfrom => { + :default => "report@" + [Facter["hostname"].value,Facter["domain"].value].join("."), + :desc => "The 'from' email address for the reports.", + }, - :reportfrom => ["report@" + [Facter["hostname"].value, Facter["domain"].value].join("."), "The 'from' email address for the reports."], - :smtpserver => ["none", "The server through which to send email reports."] + :smtpserver => { + :default => "none", + :desc => "The server through which to send email reports.", + } ) setdefaults( :rails, - :dblocation => { :default => "$statedir/clientconfigs.sqlite3", - :mode => 0660, - :owner => "service", - :group => "service", - :desc => "The database cache for client configurations. Used for + :dblocation => { + :default => "$statedir/clientconfigs.sqlite3", + :type => :file, + :mode => 0660, + :owner => "service", + :group => "service", + :desc => "The database cache for client configurations. Used for querying within the language." }, - :dbadapter => [ "sqlite3", "The type of database to use." ], - :dbmigrate => [ false, "Whether to automatically migrate the database." ], - :dbname => [ "puppet", "The name of the database to use." ], - :dbserver => [ "localhost", "The database server for caching. Only - used when networked databases are used."], - :dbport => [ "", "The database password for caching. Only - used when networked databases are used."], - :dbuser => [ "puppet", "The database user for caching. Only - used when networked databases are used."], - :dbpassword => [ "puppet", "The database password for caching. Only - used when networked databases are used."], - :dbconnections => [ '', "The number of database connections for networked - databases. Will be ignored unless the value is a positive integer."], - :dbsocket => [ "", "The database socket location. Only used when networked - databases are used. Will be ignored if the value is an empty string."], - :railslog => {:default => "$logdir/rails.log", - :mode => 0600, - :owner => "service", - :group => "service", - :desc => "Where Rails-specific logs are sent" + :dbadapter => { + :default => "sqlite3", + :desc => "The type of database to use.", + }, + :dbmigrate => { + :default => false, + :type => :boolean, + :desc => "Whether to automatically migrate the database.", + }, + :dbname => { + :default => "puppet", + :desc => "The name of the database to use.", + }, + :dbserver => { + :default => "localhost", + :desc => "The database server for caching. Only + used when networked databases are used.", + }, + :dbport => { + :default => "", + :desc => "The database password for caching. Only + used when networked databases are used.", + }, + :dbuser => { + :default => "puppet", + :desc => "The database user for caching. Only + used when networked databases are used.", + }, + :dbpassword => { + :default => "puppet", + :desc => "The database password for caching. Only + used when networked databases are used.", + }, + :dbconnections => { + :default => '', + :desc => "The number of database connections for networked + databases. Will be ignored unless the value is a positive integer.", + }, + :dbsocket => { + :default => "", + :desc => "The database socket location. Only used when networked + databases are used. Will be ignored if the value is an empty string.", + }, + :railslog => { + :default => "$logdir/rails.log", + :type => :file, + :mode => 0600, + :owner => "service", + :group => "service", + :desc => "Where Rails-specific logs are sent" }, - :rails_loglevel => ["info", "The log level for Rails connections. The value must be - a valid log level within Rails. Production environments normally use `info` - and other environments normally use `debug`."] + :rails_loglevel => { + :default => "info", + :desc => "The log level for Rails connections. The value must be + a valid log level within Rails. Production environments normally use `info` + and other environments normally use `debug`.", + } ) setdefaults( :couchdb, - :couchdb_url => ["http://127.0.0.1:5984/puppet", "The url where the puppet couchdb database will be created"] + :couchdb_url => { + :default => "http://127.0.0.1:5984/puppet", + :desc => "The url where the puppet couchdb database will be created", + } ) setdefaults( :transaction, - :tags => ["", "Tags to use to find resources. If this is set, then + :tags => { + :default => "", + :desc => "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. - Values must be comma-separated."], - :evaltrace => [false, "Whether each resource should log when it is + Values must be comma-separated.", + }, + :evaltrace => { + :default => false, + :type => :boolean, + :desc => "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly - what is being done."], - :summarize => [false, - - "Whether to print a transaction summary." - ] + what is being done.", + }, + :summarize => { + :default => false, + :type => :boolean, + :desc => "Whether to print a transaction summary.", + } ) setdefaults( :main, - :external_nodes => ["none", - - "An external command that can produce node information. The command's output - must be a YAML dump of a hash, and that hash must have a `classes` key and/or - a `parameters` key, where `classes` is an array or hash and - `parameters` is a hash. For unknown nodes, the command should - exit with a non-zero exit code. + :external_nodes => { + :default => "none", + :desc => "An external command that can produce node information. The command's output + must be a YAML dump of a hash, and that hash must have a `classes` key and/or + a `parameters` key, where `classes` is an array or hash and + `parameters` is a hash. For unknown nodes, the command should + exit with a non-zero exit code. - This command makes it straightforward to store your node mapping - information in other data sources like databases."]) + This command makes it straightforward to store your node mapping + information in other data sources like databases.", + } + ) setdefaults( :ldap, - :ldapnodes => [false, - "Whether to search for node configurations in LDAP. See - http://projects.puppetlabs.com/projects/puppet/wiki/LDAP_Nodes for more information."], - :ldapssl => [false, - "Whether SSL should be used when searching for nodes. + :ldapnodes => { + :default => false, + :type => :boolean, + :desc => "Whether to search for node configurations in LDAP. See + http://projects.puppetlabs.com/projects/puppet/wiki/LDAP_Nodes for more information.", + }, + :ldapssl => { + :default => false, + :type => :boolean, + :desc => "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates - to be set up on the client side."], - :ldaptls => [false, - "Whether TLS should be used when searching for nodes. + to be set up on the client side.", + }, + :ldaptls => { + :default => false, + :type => :boolean, + :desc => "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates - to be set up on the client side."], - :ldapserver => ["ldap", - "The LDAP server. Only used if `ldapnodes` is enabled."], - :ldapport => [389, - "The LDAP port. Only used if `ldapnodes` is enabled."], - - :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", - "The search string used to find an LDAP node."], - :ldapclassattrs => ["puppetclass", - "The LDAP attributes to use to define Puppet classes. Values - should be comma-separated."], - :ldapstackedattrs => ["puppetvar", - "The LDAP attributes that should be stacked to arrays by adding + to be set up on the client side.", + }, + :ldapserver => { + :default => "ldap", + :desc => "The LDAP server. Only used if `ldapnodes` is enabled.", + }, + :ldapport => { + :default => 389, + :desc => "The LDAP port. Only used if `ldapnodes` is enabled.", + }, + + :ldapstring => { + :default => "(&(objectclass=puppetClient)(cn=%s))", + :desc => "The search string used to find an LDAP node.", + }, + :ldapclassattrs => { + :default => "puppetclass", + :desc => "The LDAP attributes to use to define Puppet classes. Values + should be comma-separated.", + }, + :ldapstackedattrs => { + :default => "puppetvar", + :desc => "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values - should be comma-separated."], - :ldapattrs => ["all", - "The LDAP attributes to include when querying LDAP for nodes. All + should be comma-separated.", + }, + :ldapattrs => { + :default => "all", + :desc => "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns - all attributes."], - :ldapparentattr => ["parentnode", - "The attribute to use to define the parent node."], - :ldapuser => ["", - "The user to use to connect to LDAP. Must be specified as a - full DN."], - :ldappassword => ["", "The password to use to connect to LDAP."], - :ldapbase => ["", - "The search base for LDAP searches. It's impossible to provide - a meaningful default here, although the LDAP libraries might - have one already set. Generally, it should be the 'ou=Hosts' - branch under your main directory."] + all attributes.", + }, + :ldapparentattr => { + :default => "parentnode", + :desc => "The attribute to use to define the parent node.", + }, + :ldapuser => { + :default => "", + :desc => "The user to use to connect to LDAP. Must be specified as a + full DN.", + }, + :ldappassword => { + :default => "", + :desc => "The password to use to connect to LDAP.", + }, + :ldapbase => { + :default => "", + :desc => "The search base for LDAP searches. It's impossible to provide + a meaningful default here, although the LDAP libraries might + have one already set. Generally, it should be the 'ou=Hosts' + branch under your main directory.", + } ) setdefaults(:master, :storeconfigs => { :default => false, + :type => :boolean, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. - :call_on_define => true, + #:call_on_define => true, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value Puppet.settings[:async_storeconfigs] or Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Node.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) # This doesn't actually work right now. setdefaults( :parser, - :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], - :templatedir => ["$vardir/templates", - "Where Puppet looks for template files. Can be a list of colon-separated - directories." - ] + :lexical => { + :default => false, + :type => :boolean, + :desc => "Whether to use lexical scoping (vs. dynamic).", + }, + :templatedir => { + :default => "$vardir/templates", + :type => :directory, + :desc => "Where Puppet looks for template files. Can be a list of colon-separated + directories.", + } ) setdefaults( :puppetdoc, - :document_all => [false, "Document all resources"] + :document_all => { + :default => false, + :type => :boolean, + :desc => "Document all resources", + } ) end diff --git a/lib/puppet/file_serving/indirection_hooks.rb b/lib/puppet/file_serving/indirection_hooks.rb index 4aafcb9fd..735c1dd9c 100644 --- a/lib/puppet/file_serving/indirection_hooks.rb +++ b/lib/puppet/file_serving/indirection_hooks.rb @@ -1,31 +1,38 @@ require 'uri' require 'puppet/file_serving' require 'puppet/util' # This module is used to pick the appropriate terminus # in file-serving indirections. This is necessary because # the terminus varies based on the URI asked for. module Puppet::FileServing::IndirectionHooks PROTOCOL_MAP = {"puppet" => :rest, "file" => :file} # Pick an appropriate terminus based on the protocol. def select_terminus(request) # We rely on the request's parsing of the URI. # Short-circuit to :file if it's a fully-qualified path or specifies a 'file' protocol. return PROTOCOL_MAP["file"] if Puppet::Util.absolute_path?(request.key) return PROTOCOL_MAP["file"] if request.protocol == "file" + # TODO: this seems like an incredibly fragile way to determine our protocol. (In fact, I broke it pretty nicely + # during my changes relating to settings/defaults.) Nick said he's going to fix it. :) + # --cprice 2012-03-14 + # TODO: we are special-casing both "puppet" and "apply" here... this shows up in tests as well + # (file_serving.rb in shared_behaviors). I don't think we need to special-case ":puppet" any longer, + # as I am not aware of any code path where the Puppet.settings[:name] could end up having that as a value. + # We're heading over the wire the protocol is 'puppet' and we've got a server name or we're not named 'apply' or 'puppet' - if request.protocol == "puppet" and (request.server or !["puppet","apply"].include?(Puppet.settings[:name])) + if request.protocol == "puppet" and (request.server or ![:puppet, :apply].include?(Puppet.settings[:name])) return PROTOCOL_MAP["puppet"] end if request.protocol and PROTOCOL_MAP[request.protocol].nil? raise(ArgumentError, "URI protocol '#{request.protocol}' is not currently supported for file serving") end # If we're still here, we're using the file_server or modules. :file_server end end diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index f51a8bfe6..b69813a25 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -1,109 +1,109 @@ require 'webrick' require 'webrick/https' require 'puppet/network/http/webrick/rest' require 'thread' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_revocation_list' class Puppet::Network::HTTP::WEBrick def initialize(args = {}) @listening = false @mutex = Mutex.new end def listen(args = {}) raise ArgumentError, ":address must be specified." unless args[:address] raise ArgumentError, ":port must be specified." unless args[:port] arguments = {:BindAddress => args[:address], :Port => args[:port]} arguments.merge!(setup_logger) arguments.merge!(setup_ssl) @server = WEBrick::HTTPServer.new(arguments) @server.listeners.each { |l| l.start_immediately = false } @server.mount('/', Puppet::Network::HTTP::WEBrickREST, :this_value_is_apparently_necessary_but_unused) @mutex.synchronize do raise "WEBrick server is already listening" if @listening @listening = true @thread = Thread.new { @server.start { |sock| raise "Client disconnected before connection could be established" unless IO.select([sock],nil,nil,6.2) sock.accept @server.run(sock) } } sleep 0.1 until @server.status == :Running end end def unlisten @mutex.synchronize do raise "WEBrick server is not listening" unless @listening @server.shutdown @thread.join @server = nil @listening = false end end def listening? @mutex.synchronize do @listening end end # Configure our http log file. def setup_logger # Make sure the settings are all ready for us. - Puppet.settings.use(:main, :ssl, Puppet[:name]) + Puppet.settings.use(:main, :ssl, :application) if Puppet.run_mode.master? file = Puppet[:masterhttplog] else file = Puppet[:httplog] end # open the log manually to prevent file descriptor leak file_io = ::File.open(file, "a+") file_io.sync = true if defined?(Fcntl::FD_CLOEXEC) file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) end args = [file_io] args << WEBrick::Log::DEBUG if Puppet::Util::Log.level == :debug logger = WEBrick::Log.new(*args) return :Logger => logger, :AccessLog => [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT ], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT ] ] end # Add all of the ssl cert information. def setup_ssl results = {} # Get the cached copy. We know it's been generated, too. host = Puppet::SSL::Host.localhost raise Puppet::Error, "Could not retrieve certificate for #{host.name} and not running on a valid certificate authority" unless host.certificate results[:SSLPrivateKey] = host.key.content results[:SSLCertificate] = host.certificate.content results[:SSLStartImmediately] = true results[:SSLEnable] = true raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME) results[:SSLCACertificateFile] = Puppet[:localcacert] results[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER results[:SSLCertificateStore] = host.ssl_store results end end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index 3e79f4ded..f92d600a5 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,138 +1,139 @@ require 'puppet/network/http' require 'puppet/util/pidlock' class Puppet::Network::Server attr_reader :server_type, :address, :port # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end # Get rid of console logging Puppet::Util::Log.close(:console) Process.setsid Dir.chdir("/") begin $stdin.reopen "/dev/null" $stdout.reopen "/dev/null", "a" $stderr.reopen $stdout Puppet::Util::Log.reopen rescue => detail Puppet::Util.replace_file("/tmp/daemonout", 0644) { |f| f.puts "Could not start #{Puppet[:name]}: #{detail}" } raise "Could not start #{Puppet[:name]}: #{detail}" end end # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # Remove the pid file for our daemon. def remove_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do Puppet::Util::Pidlock.new(pidfile).unlock end end # Provide the path to our pidfile. def pidfile Puppet[:pidfile] end def initialize(args = {}) valid_args = [:handlers, :port] bad_args = args.keys.find_all { |p| ! valid_args.include?(p) }.collect { |p| p.to_s }.join(",") raise ArgumentError, "Invalid argument(s) #{bad_args}" unless bad_args == "" @server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc. http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]") @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport") @address = determine_bind_address @listening = false @routes = {} self.register(args[:handlers]) if args[:handlers] # Make sure we have all of the directories we need to function. - Puppet.settings.use(:main, :ssl, Puppet[:name]) + #Puppet.settings.use(:main, :ssl, Puppet[:name]) + Puppet.settings.use(:main, :ssl, :application) end # Register handlers for REST networking, based on the Indirector. def register(*indirections) raise ArgumentError, "Indirection names are required." if indirections.empty? indirections.flatten.each do |name| Puppet::Indirector::Indirection.model(name) || raise(ArgumentError, "Cannot locate indirection '#{name}'.") @routes[name.to_sym] = true end end # Unregister Indirector handlers. def unregister(*indirections) raise "Cannot unregister indirections while server is listening." if listening? indirections = @routes.keys if indirections.empty? indirections.flatten.each do |i| raise(ArgumentError, "Indirection [#{i}] is unknown.") unless @routes[i.to_sym] end indirections.flatten.each do |i| @routes.delete(i.to_sym) end end def listening? @listening end def listen raise "Cannot listen -- already listening." if listening? @listening = true http_server.listen(:address => address, :port => port, :handlers => @routes.keys) end def unlisten raise "Cannot unlisten -- not currently listening." unless listening? http_server.unlisten @listening = false end def http_server_class http_server_class_by_type(@server_type) end def start create_pidfile listen end def stop unlisten remove_pidfile end private def http_server @http_server ||= http_server_class.new end def http_server_class_by_type(kind) Puppet::Network::HTTP.server_class_by_type(kind) end def determine_bind_address tmp = Puppet[:bindaddress] return tmp if tmp != "" server_type.to_s == "webrick" ? "0.0.0.0" : "127.0.0.1" end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index cbc933660..4fa4effb8 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,476 +1,480 @@ # The scope class, which handles storing and retrieving variables and types and # such. require 'puppet/parser/parser' require 'puppet/parser/templatewrapper' require 'strscan' require 'puppet/resource/type_collection_helper' class Puppet::Parser::Scope include Puppet::Resource::TypeCollectionHelper require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) include Enumerable include Puppet::Util::Errors attr_accessor :source, :resource attr_accessor :base, :keyword attr_accessor :top, :translated, :compiler attr_accessor :parent, :dynamic attr_reader :namespaces # thin wrapper around an ephemeral # symbol table. # when a symbol class Ephemeral def initialize(parent=nil) @symbols = {} @parent = parent end [:include?, :delete, :[]=].each do |m| define_method(m) do |*args| @symbols.send(m, *args) end end def [](name) unless @symbols.include?(name) or @parent.nil? @parent[name] else @symbols[name] end end end def [](name, options = {}) table = ephemeral?(name) ? @ephemeral.last : @symtable # If the variable is qualified, then find the specified scope and look the variable up there instead. if name =~ /^(.*)::(.+)$/ begin qualified_scope($1)[$2,options] rescue RuntimeError => e location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}" nil end elsif ephemeral_include?(name) or table.include?(name) # We can't use "if table[name]" here because the value might be false if options[:dynamic] and self != compiler.topscope location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' Puppet.deprecation_warning "Dynamic lookup of $#{name}#{location} is deprecated. Support will be removed in Puppet 2.8. Use a fully-qualified variable name (e.g., $classname::variable) or parameterized classes." end table[name] elsif parent parent[name,options.merge(:dynamic => (dynamic || options[:dynamic]))] else nil end end def []=(var, value) setvar(var, value) end # A demeterific shortcut to the catalog. def catalog compiler.catalog end def each to_hash.each { |name, value| yield(name, value) } end # Proxy accessors def host @compiler.node.name end def include?(name) ! self[name].nil? end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) (value != false and value != "" and value != :undef) end # Is the value a number?, return the correct object or nil if not a number def self.number?(value) return nil unless value.is_a?(Fixnum) or value.is_a?(Bignum) or value.is_a?(Float) or value.is_a?(String) if value.is_a?(String) if value =~ /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ return value.to_f elsif value =~ /^0x[0-9a-f]+$/i return value.to_i(16) elsif value =~ /^0[0-7]+$/ return value.to_i(8) elsif value =~ /^-?\d+$/ return value.to_i else return nil end end # it is one of Fixnum,Bignum or Float value end # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end # Remove this when rebasing def environment compiler ? compiler.environment : Puppet::Node::Environment.new end def find_hostclass(name) known_resource_types.find_hostclass(namespaces, name) end def find_definition(name) known_resource_types.find_definition(namespaces, name) end def findresource(string, name = nil) compiler.findresource(string, name) end # Initialize our new scope. Defaults to having no parent. def initialize(hash = {}) if hash.include?(:namespace) if n = hash[:namespace] @namespaces = [n] end hash.delete(:namespace) else @namespaces = [""] end hash.each { |name, val| method = name.to_s + "=" if self.respond_to? method self.send(method, val) else raise Puppet::DevError, "Invalid scope argument #{name}" end } extend_with_functions_module @tags = [] # The symbol table for this scope. This is where we store variables. @symtable = {} # the ephemeral symbol tables # those should not persist long, and are used for the moment only # for $0..$xy capture variables of regexes # this is actually implemented as a stack, with each ephemeral scope # shadowing the previous one @ephemeral = [ Ephemeral.new ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @defaults = Hash.new { |dhash,type| dhash[type] = {} } # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) return parent.class_set(name,scope) if parent @class_scopes[name] = scope end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name k = klass.respond_to?(:name) ? klass.name : klass @class_scopes[k] || (parent && parent.class_scope(k)) end # Collect all of the defaults set at any higher scopes. # This is a different type of lookup because it's additive -- # it collects all of the defaults, with defaults in closer scopes # overriding those in later scopes. def lookupdefaults(type) values = {} # first collect the values from the parents unless parent.nil? parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently if @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end #Puppet.debug "Got defaults for %s: %s" % # [type,values.inspect] values end # Look up a defined type. def lookuptype(name) find_definition(name) || find_hostclass(name) end def undef_as(x,v) if v.nil? or v == :undef x else v end end def qualified_scope(classname) raise "class #{classname} could not be found" unless klass = find_hostclass(classname) raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) kscope end private :qualified_scope # Look up a variable. The simplest value search we do. # This method is effectively deprecated - use self[] instead. def lookupvar(name, options = {}) self[name, options] end # Return a hash containing our variables and their values, optionally (and # by default) including the values defined in our parent. Local values # shadow parent values. def to_hash(recursive = true) target = parent.to_hash(recursive) if recursive and parent target ||= Hash.new @symtable.keys.each { |name| value = @symtable[name] if value == :undef target.delete(name) else target[name] = value end } target end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) compiler.newscope(self, options) end def parent_module_name return nil unless @parent return nil unless @parent.source @parent.source.module_name end # Return the list of scopes up to the top scope, ordered with our own first. # This is used for looking up variables and defaults. def scope_path if parent [self, parent.scope_path].flatten.compact else [self] end end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def setdefaults(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| #Puppet.debug "Default for %s is %s => %s" % # [type,ary[0].inspect,ary[1].inspect] if table.include?(param.name) raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", param.line, param.file) end table[param.name] = param } end # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope # to be reassigned. # It's preferred that you use self[]= instead of this; only use this # when you need to set options. def setvar(name,value, options = {}) table = options[:ephemeral] ? @ephemeral.last : @symtable if table.include?(name) unless options[:append] error = Puppet::ParseError.new("Cannot reassign variable #{name}") else error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end unless options[:append] table[name] = value else # append case # lookup the value in the scope if it exists and insert the var table[name] = undef_as('',self[name]) # concatenate if string, append if array, nothing for other types case value when Array table[name] += value when Hash raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" unless value.is_a?(Hash) table[name].merge!(value) else table[name] << value end end end # Return the tags associated with this scope. It's basically # just our parents' tags, plus our type. We don't cache this value # because our parent tags might change between calls. def tags resource.tags end # Used mainly for logging def to_s "Scope(#{@resource})" end # Undefine a variable; only used for testing. def unsetvar(var) table = ephemeral?(var) ? @ephemeral.last : @symtable table.delete(var) if table.include?(var) end # remove ephemeral scope up to level def unset_ephemeral_var(level=:all) if level == :all @ephemeral = [ Ephemeral.new ] else (@ephemeral.size - level).times do @ephemeral.pop end end end # check if name exists in one of the ephemeral scope. def ephemeral_include?(name) @ephemeral.reverse.each do |eph| return true if eph.include?(name) end false end # is name an ephemeral variable? def ephemeral?(name) name =~ /^\d+$/ end def ephemeral_level @ephemeral.size end def new_ephemeral @ephemeral.push(Ephemeral.new(@ephemeral.last)) end def ephemeral_from(match, file = nil, line = nil) raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) new_ephemeral setvar("0", match[0], :file => file, :line => line, :ephemeral => true) match.captures.each_with_index do |m,i| setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) end end def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end def find_defined_resource_type(type) environment.known_resource_types.find_definition(namespaces, type.to_s.downcase) end def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ super unless $1 super unless Puppet::Parser::Functions.function($1) # Calling .function(name) adds "function_#{name}" as a callable method on # self if it's found, so now we can just send it + # NOTE: I have watched this method end up in an infinite recursion / stack overflow. It seems + # to me that we ought to be checkign with "respond_to?" before calling send, and throwing an + # exception if we get a "false" back. However, I tried this, and it broke 1 test in scope_spec... + # and I don't have time to debug it further right now. --cprice 2012-03-15 send(method, *args) end def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] end private def extend_with_functions_module extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) extend Puppet::Parser::Functions.environment_module(environment) if environment != Puppet::Node::Environment.root end end diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb index 602fa0337..f0d62f044 100644 --- a/lib/puppet/property.rb +++ b/lib/puppet/property.rb @@ -1,372 +1,372 @@ # The virtual base class for properties, which are the self-contained building # blocks for actually doing work on the system. require 'puppet' require 'puppet/parameter' class Puppet::Property < Puppet::Parameter require 'puppet/property/ensure' # Because 'should' uses an array, we have a special method for handling # it. We also want to keep copies of the original values, so that # they can be retrieved and compared later when merging. attr_reader :shouldorig attr_writer :noop class << self attr_accessor :unmanaged attr_reader :name # Return array matching info, defaulting to just matching # the first value. def array_matching @array_matching ||= :first end # Set whether properties should match all values or just the first one. def array_matching=(value) value = value.intern if value.is_a?(String) raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" unless [:first, :all].include?(value) @array_matching = value end end # Look up a value's name, so we can find options and such. def self.value_name(name) if value = value_collection.match?(name) value.name end end # Retrieve an option set when a value was defined. def self.value_option(name, option) if value = value_collection.value(name) value.send(option) end end # Define a new valid value for a property. You must provide the value itself, # usually as a symbol, or a regex to match the value. # # The first argument to the method is either the value itself or a regex. # The second argument is an option hash; valid options are: # * :method: The name of the method to define. Defaults to 'set_'. # * :required_features: A list of features this value requires. # * :event: The event that should be returned when this value is set. # * :call: When to call any associated block. The default value # is `instead`, which means to call the value instead of calling the # provider. You can also specify `before` or `after`, which will # call both the block and the provider, according to the order you specify # (the `first` refers to when the block is called, not the provider). def self.newvalue(name, options = {}, &block) value = value_collection.newvalue(name, options, &block) define_method(value.method, &value.block) if value.method and value.block value end # Call the provider method. def call_provider(value) provider.send(self.class.name.to_s + "=", value) rescue NoMethodError self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}" end # Call the dynamically-created method associated with our value, if # there is one. def call_valuemethod(name, value) if method = self.class.value_option(name, :method) and self.respond_to?(method) begin event = self.send(method) rescue Puppet::Error raise rescue => detail - error = Puppet::Error.new("Could not set '#{value} on #{self.class.name}: #{detail}", @resource.line, @resource.file) + error = Puppet::Error.new("Could not set '#{value}' on #{self.class.name}: #{detail}", @resource.line, @resource.file) error.set_backtrace detail.backtrace Puppet.log_exception(detail, error.message) raise error end elsif block = self.class.value_option(name, :block) # FIXME It'd be better here to define a method, so that # the blocks could return values. self.instance_eval(&block) else devfail "Could not find method for value '#{name}'" end end # How should a property change be printed as a string? def change_to_s(current_value, newvalue) begin if current_value == :absent return "defined '#{name}' as #{self.class.format_value_for_display should_to_s(newvalue)}" elsif newvalue == :absent or newvalue == [:absent] return "undefined '#{name}' from #{self.class.format_value_for_display is_to_s(current_value)}" else return "#{name} changed #{self.class.format_value_for_display is_to_s(current_value)} to #{self.class.format_value_for_display should_to_s(newvalue)}" end rescue Puppet::Error, Puppet::DevError raise rescue => detail message = "Could not convert change '#{name}' to string: #{detail}" Puppet.log_exception(detail, message) raise Puppet::DevError, message end end # Figure out which event to return. def event_name value = self.should event_name = self.class.value_option(value, :event) and return event_name name == :ensure or return (name.to_s + "_changed").to_sym return (resource.type.to_s + case value when :present; "_created" when :absent; "_removed" else "_changed" end).to_sym end # Return a modified form of the resource event. def event resource.event :name => event_name, :desired_value => should, :property => self, :source_description => path end attr_reader :shadow # initialize our property def initialize(hash = {}) super if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name) setup_shadow(klass) end end # Determine whether the property is in-sync or not. If @should is # not defined or is set to a non-true value, then we do not have # a valid value for it and thus consider the property to be in-sync # since we cannot fix it. Otherwise, we expect our should value # to be an array, and if @is matches any of those values, then # we consider it to be in-sync. # # Don't override this method. def safe_insync?(is) # If there is no @should value, consider the property to be in sync. return true unless @should # Otherwise delegate to the (possibly derived) insync? method. insync?(is) end def self.method_added(sym) raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync? end # This method may be overridden by derived classes if necessary # to provide extra logic to determine whether the property is in # sync. In most cases, however, only `property_matches?` needs to be # overridden to give the correct outcome - without reproducing all the array # matching logic, etc, found here. def insync?(is) self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array) # an empty array is analogous to no should values return true if @should.empty? # Look for a matching value, either for all the @should values, or any of # them, depending on the configuration of this property. if match_all? then # Emulate Array#== using our own comparison function. # A non-array was not equal to an array, which @should always is. return false unless is.is_a? Array # If they were different lengths, they are not equal. return false unless is.length == @should.length # Finally, are all the elements equal? In order to preserve the # behaviour of previous 2.7.x releases, we need to impose some fun rules # on "equality" here. # # Specifically, we need to implement *this* comparison: the two arrays # are identical if the is values are == the should values, or if the is # values are == the should values, stringified. # # This does mean that property equality is not commutative, and will not # work unless the `is` value is carefully arranged to match the should. return (is == @should or is == @should.map(&:to_s)) # When we stop being idiots about this, and actually have meaningful # semantics, this version is the thing we actually want to do. # # return is.zip(@should).all? {|a, b| property_matches?(a, b) } else return @should.any? {|want| property_matches?(is, want) } end end # Compare the current and desired value of a property in a property-specific # way. Invoked by `insync?`; this should be overridden if your property # has a different comparison type but does not actually differentiate the # overall insync? logic. def property_matches?(current, desired) # This preserves the older Puppet behaviour of doing raw and string # equality comparisons for all equality. I am not clear this is globally # desirable, but at least it is not a breaking change. --daniel 2011-11-11 current == desired or current == desired.to_s end # because the @should and @is vars might be in weird formats, # we need to set up a mechanism for pretty printing of the values # default to just the values, but this way individual properties can # override these methods def is_to_s(currentvalue) currentvalue end # Send a log message. def log(msg) Puppet::Util::Log.create( :level => resource[:loglevel], :message => msg, :source => self ) end # Should we match all values, or just the first? def match_all? self.class.array_matching == :all end # Execute our shadow's munge code, too, if we have one. def munge(value) self.shadow.munge(value) if self.shadow super end # each property class must define the name method, and property instances # do not change that name # this implicitly means that a given object can only have one property # instance of a given property class def name self.class.name end # for testing whether we should actually do anything def noop # This is only here to make testing easier. if @resource.respond_to?(:noop?) @resource.noop? else if defined?(@noop) @noop else Puppet[:noop] end end end # By default, call the method associated with the property name on our # provider. In other words, if the property name is 'gid', we'll call # 'provider.gid' to retrieve the current value. def retrieve provider.send(self.class.name) end # Set our value, using the provider, an associated block, or both. def set(value) # Set a name for looking up associated options like the event. name = self.class.value_name(value) call = self.class.value_option(name, :call) || :none if call == :instead call_valuemethod(name, value) elsif call == :none # They haven't provided a block, and our parent does not have # a provider, so we have no idea how to handle this. self.fail "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider call_provider(value) else # LAK:NOTE 20081031 This is a change in behaviour -- you could # previously specify :call => [;before|:after], which would call # the setter *in addition to* the block. I'm convinced this # was never used, and it makes things unecessarily complicated. # If you want to specify a block and still call the setter, then # do so in the block. devfail "Cannot use obsolete :call value '#{call}' for property '#{self.class.name}'" end end # If there's a shadowing metaparam, instantiate it now. # This allows us to create a property or parameter with the # same name as a metaparameter, and the metaparam will only be # stored as a shadow. def setup_shadow(klass) @shadow = klass.new(:resource => self.resource) end # Only return the first value def should return nil unless defined?(@should) self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array) if match_all? return @should.collect { |val| self.unmunge(val) } else return self.unmunge(@should[0]) end end # Set the should value. def should=(values) values = [values] unless values.is_a?(Array) @shouldorig = values values.each { |val| validate(val) } @should = values.collect { |val| self.munge(val) } end def should_to_s(newvalue) [newvalue].flatten.join(" ") end def sync devfail "Got a nil value for should" unless should set(should) end # Verify that the passed value is valid. # If the developer uses a 'validate' hook, this method will get overridden. def unsafe_validate(value) super validate_features_per_value(value) end # Make sure that we've got all of the required features for a given value. def validate_features_per_value(value) if features = self.class.value_option(self.class.value_name(value), :required_features) features = Array(features) needed_features = features.collect { |f| f.to_s }.join(", ") raise ArgumentError, "Provider must have features '#{needed_features}' to set '#{self.class.name}' to '#{value}'" unless provider.satisfies?(features) end end # Just return any should value we might have. def value self.should end # Match the Parameter interface, but we really just use 'should' internally. # Note that the should= method does all of the validation and such. def value=(value) self.should = value end end diff --git a/lib/puppet/rails.rb b/lib/puppet/rails.rb index 36b4043ea..cd2b4c1df 100644 --- a/lib/puppet/rails.rb +++ b/lib/puppet/rails.rb @@ -1,134 +1,135 @@ # Load the appropriate libraries, or set a class indicating they aren't available require 'facter' require 'puppet' require 'logger' module Puppet::Rails TIME_DEBUG = true def self.connect # This global init does not work for testing, because we remove # the state dir on every test. return if ActiveRecord::Base.connected? Puppet.settings.use(:main, :rails, :master) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) begin loglevel = Logger.const_get(Puppet[:rails_loglevel].upcase) ActiveRecord::Base.logger.level = loglevel rescue => detail Puppet.warning "'#{Puppet[:rails_loglevel]}' is not a valid Rails log level; using debug" ActiveRecord::Base.logger.level = Logger::DEBUG end # As of ActiveRecord 2.2 allow_concurrency has been deprecated and no longer has any effect. ActiveRecord::Base.allow_concurrency = true if Puppet::Util.activerecord_version < 2.2 ActiveRecord::Base.verify_active_connections! begin args = database_arguments Puppet.info "Connecting to #{args[:adapter]} database: #{args[:database]}" ActiveRecord::Base.establish_connection(args) rescue => detail message = "Could not connect to database: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, message end end # The arguments for initializing the database connection. def self.database_arguments adapter = Puppet[:dbadapter] args = {:adapter => adapter, :log_level => Puppet[:rails_loglevel]} case adapter when "sqlite3" args[:database] = Puppet[:dblocation] when "mysql", "mysql2", "postgresql" args[:host] = Puppet[:dbserver] unless Puppet[:dbserver].to_s.empty? args[:port] = Puppet[:dbport] unless Puppet[:dbport].to_s.empty? args[:username] = Puppet[:dbuser] unless Puppet[:dbuser].to_s.empty? args[:password] = Puppet[:dbpassword] unless Puppet[:dbpassword].to_s.empty? args[:pool] = Puppet[:dbconnections].to_i unless Puppet[:dbconnections].to_i <= 0 args[:database] = Puppet[:dbname] args[:reconnect]= true socket = Puppet[:dbsocket] args[:socket] = socket unless socket.to_s.empty? when "oracle_enhanced" args[:database] = Puppet[:dbname] unless Puppet[:dbname].to_s.empty? args[:username] = Puppet[:dbuser] unless Puppet[:dbuser].to_s.empty? args[:password] = Puppet[:dbpassword] unless Puppet[:dbpassword].to_s.empty? args[:pool] = Puppet[:dbconnections].to_i unless Puppet[:dbconnections].to_i <= 0 else raise ArgumentError, "Invalid db adapter #{adapter}" end args end # Set up our database connection. It'd be nice to have a "use" system # that could make callbacks. def self.init raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" unless Puppet.features.rails? connect unless ActiveRecord::Base.connection.tables.include?("resources") require 'puppet/rails/database/schema' Puppet::Rails::Schema.init end migrate if Puppet[:dbmigrate] end # Migrate to the latest db schema. def self.migrate dbdir = nil $LOAD_PATH.each { |d| tmp = File.join(d, "puppet/rails/database") if FileTest.directory?(tmp) dbdir = tmp break end } raise Puppet::Error, "Could not find Puppet::Rails database dir" unless dbdir raise Puppet::Error, "Database has problems, can't migrate." unless ActiveRecord::Base.connection.tables.include?("resources") Puppet.notice "Migrating" begin ActiveRecord::Migrator.migrate(dbdir) rescue => detail message = "Could not migrate database: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, "Could not migrate database: #{detail}" end end # Tear down the database. Mostly only used during testing. def self.teardown raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" unless Puppet.features.rails? Puppet.settings.use(:master, :rails) begin ActiveRecord::Base.establish_connection(database_arguments) rescue => detail Puppet.log_exception(detail) raise Puppet::Error, "Could not connect to database: #{detail}" end ActiveRecord::Base.connection.tables.each do |t| ActiveRecord::Base.connection.drop_table t end end end +# TODO cprice: hmmm.. require 'puppet/rails/host' if Puppet.features.rails? diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index 517fa8f03..a127c4460 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -1,128 +1,128 @@ Puppet::Reports.register_report(:rrdgraph) do desc "Graph all available data about hosts using the RRD library. You must have the Ruby RRDtool library installed to use this report, which you can get from [the RubyRRDTool RubyForge page](http://rubyforge.org/projects/rubyrrdtool/). This package may also be available as `ruby-rrd` or `rrdtool-ruby` in your distribution's package management system. The library and/or package will both require the binary `rrdtool` package from your distribution to be installed. This report will create, manage, and graph RRD database files for each of the metrics generated during transactions, and it will create a few simple html files to display the reporting host's graphs. At this point, it will not create a common index file to display links to all hosts. All RRD files and graphs get created in the `rrddir` directory. If you want to serve these publicly, you should be able to just alias that directory in a web server. If you really know what you're doing, you can tune the `rrdinterval`, which defaults to the `runinterval`." def hostdir @hostdir ||= File.join(Puppet[:rrddir], self.host) end def htmlfile(type, graphs, field) file = File.join(hostdir, "#{type}.html") File.open(file, "w") do |of| of.puts "#{type.capitalize} graphs for #{host}" graphs.each do |graph| if field == :first name = graph.sub(/-\w+.png/, '').capitalize else name = graph.sub(/\w+-/, '').sub(".png", '').capitalize end of.puts "
" end of.puts "" end file end def mkhtml images = Dir.entries(hostdir).find_all { |d| d =~ /\.png/ } periodorder = %w{daily weekly monthly yearly} periods = {} types = {} images.each do |n| type, period = n.sub(".png", '').split("-") periods[period] ||= [] types[type] ||= [] periods[period] << n types[type] << n end files = [] # Make the period html files periodorder.each do |period| unless ary = periods[period] raise Puppet::Error, "Could not find graphs for #{period}" end files << htmlfile(period, ary, :first) end # make the type html files types.sort { |a,b| a[0] <=> b[0] }.each do |type, ary| newary = [] periodorder.each do |period| if graph = ary.find { |g| g.include?("-#{period}.png") } newary << graph else raise "Could not find #{type}-#{period} graph" end end files << htmlfile(type, newary, :second) end File.open(File.join(hostdir, "index.html"), "w") do |of| of.puts "Report graphs for #{host}" files.each do |file| of.puts "#{File.basename(file).sub(".html",'').capitalize}
" end of.puts "" end end def process(time = nil) time ||= Time.now.to_i unless File.directory?(hostdir) and FileTest.writable?(hostdir) # Some hackishness to create the dir with all of the right modes and ownership config = Puppet::Util::Settings.new - config.setdefaults(:reports, :hostdir => {:default => hostdir, :owner => 'service', :mode => 0755, :group => 'service', :desc => "eh"}) + config.setdefaults(:reports, :hostdir => {:type => :directory, :default => hostdir, :owner => 'service', :mode => 0755, :group => 'service', :desc => "eh"}) # This creates the dir. config.use(:reports) end self.metrics.each do |name, metric| metric.basedir = hostdir if name == "time" timeclean(metric) end metric.store(time) metric.graph end mkhtml unless FileTest.exists?(File.join(hostdir, "index.html")) end # Unfortunately, RRD does not deal well with changing lists of values, # so we have to pick a list of values and stick with it. In this case, # that means we record the total time, the config time, and that's about # it. We should probably send each type's time as a separate metric. def timeclean(metric) metric.values = metric.values.find_all { |name, label, value| ['total', 'config_retrieval'].include?(name.to_s) } end end diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb index e6f36bc97..3358b2018 100755 --- a/lib/puppet/type/file/content.rb +++ b/lib/puppet/type/file/content.rb @@ -1,238 +1,242 @@ require 'net/http' require 'uri' require 'tempfile' require 'puppet/util/checksums' require 'puppet/network/http/api/v1' require 'puppet/network/http/compression' module Puppet Puppet::Type.type(:file).newproperty(:content) do include Puppet::Util::Diff include Puppet::Util::Checksums include Puppet::Network::HTTP::API::V1 include Puppet::Network::HTTP::Compression.module attr_reader :actual_content desc <<-EOT The desired contents of a file, as a string. This attribute is mutually exclusive with `source` and `target`. Newlines and tabs can be specified in double-quoted strings using standard escaped syntax --- \n for a newline, and \t for a tab. With very small files, you can construct content strings directly in the manifest... define resolve(nameserver1, nameserver2, domain, search) { $str = "search $search domain $domain nameserver $nameserver1 nameserver $nameserver2 " file { "/etc/resolv.conf": content => "$str", } } ...but for larger files, this attribute is more useful when combined with the [template](http://docs.puppetlabs.com/references/latest/function.html#template) function. EOT # Store a checksum as the value, rather than the actual content. # Simplifies everything. munge do |value| if value == :absent value elsif checksum?(value) # XXX This is potentially dangerous because it means users can't write a file whose # entire contents are a plain checksum value else @actual_content = value resource.parameter(:checksum).sum(value) end end # Checksums need to invert how changes are printed. def change_to_s(currentvalue, newvalue) # Our "new" checksum value is provided by the source. if source = resource.parameter(:source) and tmp = source.checksum newvalue = tmp end if currentvalue == :absent return "defined content as '#{newvalue}'" elsif newvalue == :absent return "undefined content from '#{currentvalue}'" else return "content changed '#{currentvalue}' to '#{newvalue}'" end end def checksum_type if source = resource.parameter(:source) result = source.checksum else checksum = resource.parameter(:checksum) result = resource[:checksum] end if result =~ /^\{(\w+)\}.+/ return $1.to_sym else return result end end def length (actual_content and actual_content.length) || 0 end def content self.should end # Override this method to provide diffs if asked for. # Also, fix #872: when content is used, and replace is true, the file # should be insync when it exists def insync?(is) if resource.should_be_file? return false if is == :absent else return true end return true if ! @resource.replace? result = super if ! result and Puppet[:show_diff] write_temporarily do |path| notice "\n" + diff(@resource[:path], path) end end result end def retrieve return :absent unless stat = @resource.stat ftype = stat.ftype # Don't even try to manage the content on directories or links return nil if ["directory","link"].include?(ftype) begin resource.parameter(:checksum).sum_file(resource[:path]) rescue => detail raise Puppet::Error, "Could not read #{ftype} #{@resource.title}: #{detail}" end end # Make sure we're also managing the checksum property. def should=(value) @resource.newattr(:checksum) unless @resource.parameter(:checksum) super end # Just write our content out to disk. def sync return_event = @resource.stat ? :file_changed : :file_created # We're safe not testing for the 'source' if there's no 'should' # because we wouldn't have gotten this far if there weren't at least # one valid value somewhere. @resource.write(:content) return_event end def write_temporarily tempfile = Tempfile.new("puppet-file") tempfile.open write(tempfile) tempfile.close yield tempfile.path tempfile.delete end def write(file) resource.parameter(:checksum).sum_stream { |sum| each_chunk_from(actual_content || resource.parameter(:source)) { |chunk| sum << chunk file.print chunk } } end + # TODO: this is another terrible, fragile means of determining whether or not to + # make a web request... it makes me tempted to get rid of the ":name" setting + # entirely... --cprice 2012-03-14 + def self.standalone? - Puppet.settings[:name] == "apply" + Puppet.settings[:name] == :apply end # the content is munged so if it's a checksum source_or_content is nil # unless the checksum indirectly comes from source def each_chunk_from(source_or_content) if source_or_content.is_a?(String) yield source_or_content elsif content_is_really_a_checksum? && source_or_content.nil? yield read_file_from_filebucket elsif source_or_content.nil? yield '' elsif self.class.standalone? yield source_or_content.content elsif source_or_content.local? chunk_file_from_disk(source_or_content) { |chunk| yield chunk } else chunk_file_from_source(source_or_content) { |chunk| yield chunk } end end private def content_is_really_a_checksum? checksum?(should) end def chunk_file_from_disk(source_or_content) File.open(source_or_content.full_path, "rb") do |src| while chunk = src.read(8192) yield chunk end end end def get_from_source(source_or_content, &block) request = Puppet::Indirector::Request.new(:file_content, :find, source_or_content.full_path.sub(/^\//,'')) request.do_request(:fileserver) do |req| connection = Puppet::Network::HttpPool.http_instance(req.server, req.port) connection.request_get(indirection2uri(req), add_accept_encoding({"Accept" => "raw"}), &block) end end def chunk_file_from_source(source_or_content) get_from_source(source_or_content) do |response| case response.code when /^2/; uncompress(response) { |uncompressor| response.read_body { |chunk| yield uncompressor.uncompress(chunk) } } else # Raise the http error if we didn't get a 'success' of some kind. message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}" raise Net::HTTPError.new(message, response) end end end def read_file_from_filebucket raise "Could not get filebucket from file" unless dipper = resource.bucket sum = should.sub(/\{\w+\}/, '') dipper.getfile(sum) rescue => detail fail "Could not retrieve content for #{should} from filebucket: #{detail}" end end end diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index 20b137905..39dc19487 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -1,167 +1,180 @@ require 'pathname' require 'puppet/util/warnings' # Autoload paths, either based on names or all at once. class Puppet::Util::Autoload @autoloaders = {} @loaded = {} class << self attr_reader :autoloaders attr_accessor :loaded private :autoloaders, :loaded # List all loaded files. def list_loaded loaded.keys.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash| "#{path}: #{hash[:file]}" end end # Has a given path been loaded? This is used for testing whether a # changed file should be loaded or just ignored. This is only # used in network/client/master, when downloading plugins, to # see if a given plugin is currently loaded and thus should be # reloaded. def loaded?(path) path = cleanpath(path).chomp('.rb') loaded.include?(path) end # Save the fact that a given path has been loaded. This is so # we can load downloaded plugins if they've already been loaded # into memory. def mark_loaded(name, file) name = cleanpath(name) $LOADED_FEATURES << name + ".rb" unless $LOADED_FEATURES.include?(name) loaded[name] = [file, File.mtime(file)] end def changed?(name) name = cleanpath(name) return true unless loaded.include?(name) file, old_mtime = loaded[name] return true unless file == get_file(name) begin old_mtime != File.mtime(file) rescue Errno::ENOENT true end end # Load a single plugin by name. We use 'load' here so we can reload a # given plugin. def load_file(name, env=nil) file = get_file(name.to_s, env) return false unless file begin mark_loaded(name, file) Kernel.load file, @wrap return true rescue SystemExit,NoMemoryError raise rescue Exception => detail message = "Could not autoload #{name}: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, message end end def loadall(path) # Load every instance of everything we can find. files_to_load(path).each do |file| name = file.chomp(".rb") load_file(name) unless loaded?(name) end end def reload_changed loaded.keys.each { |file| load_file(file) if changed?(file) } end # Get the correct file to load for a given path # returns nil if no file is found def get_file(name, env=nil) name = name + '.rb' unless name =~ /\.rb$/ dirname, base = File.split(name) path = search_directories(env).find { |dir| File.exist?(File.join(dir, name)) } path and File.join(path, name) end def files_to_load(path) search_directories.map {|dir| files_in_dir(dir, path) }.flatten.uniq end def files_in_dir(dir, path) dir = Pathname.new(dir) Dir.glob(File.join(dir, path, "*.rb")).collect do |file| Pathname.new(file).relative_path_from(dir).to_s end end def module_directories(env=nil) # We have to require this late in the process because otherwise we might have # load order issues. require 'puppet/node/environment' real_env = Puppet::Node::Environment.new(env) # We're using a per-thread cache of module directories so that we don't # scan the filesystem each time we try to load something. This is reset # at the beginning of compilation and at the end of an agent run. Thread.current[:env_module_directories] ||= {} - Thread.current[:env_module_directories][real_env] ||= real_env.modulepath.collect do |dir| + # TODO cprice: document this... + if Puppet.settings.app_defaults_initialized? + Thread.current[:env_module_directories][real_env] ||= real_env.modulepath.collect do |dir| Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) } end.flatten.collect { |d| [File.join(d, "plugins"), File.join(d, "lib")] }.flatten.find_all do |d| FileTest.directory?(d) end + else + Thread.current[:env_module_directories][real_env] = [] + end + end + + def libdirs() + if (Puppet.settings.app_defaults_initialized?) + Puppet[:libdir].split(File::PATH_SEPARATOR) + else + [] + end end def search_directories(env=nil) - [module_directories(env), Puppet[:libdir].split(File::PATH_SEPARATOR), $LOAD_PATH].flatten + [module_directories(env), libdirs(), $LOAD_PATH].flatten end # Normalize a path. This converts ALT_SEPARATOR to SEPARATOR on Windows # and eliminates unnecessary parts of a path. def cleanpath(path) Pathname.new(path).cleanpath.to_s end end # Send [] and []= to the @autoloaders hash Puppet::Util.classproxy self, :autoloaders, "[]", "[]=" attr_accessor :object, :path, :objwarn, :wrap def initialize(obj, path, options = {}) @path = path.to_s raise ArgumentError, "Autoload paths cannot be fully qualified" if @path !~ /^\w/ @object = obj self.class[obj] = self options.each do |opt, value| begin self.send(opt.to_s + "=", value) rescue NoMethodError raise ArgumentError, "#{opt} is not a valid option" end end @wrap = true unless defined?(@wrap) end def load(name, env=nil) self.class.load_file(File.join(@path, name.to_s), env) end # Load all instances that we can. This uses require, rather than load, # so that already-loaded files don't get reloaded unnecessarily. def loadall self.class.loadall(@path) end def files_to_load self.class.files_to_load(@path) end end diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index 1c7cb61bb..e96dbf825 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -1,235 +1,237 @@ require 'puppet' require "puppet/util/plugins" require 'puppet/util/command_line_utils/puppet_option_parser' module Puppet module Util class CommandLine # Just aliasing in the class name for brevity PuppetOptionParser = Puppet::Util::CommandLineUtils::PuppetOptionParser def initialize(zero = $0, argv = ARGV, stdin = STDIN) @zero = zero @argv = argv.dup @stdin = stdin @subcommand_name, @args = subcommand_and_args(@zero, @argv, @stdin) Puppet::Plugins.on_commandline_initialization(:command_line_object => self) end attr :subcommand_name attr :args def appdir File.join('puppet', 'application') end # TODO cprice: document def parse_global_options # Create an option parser #option_parser = OptionParser.new option_parser = PuppetOptionParser.new option_parser.ignore_invalid_options = true # Add all global options to it. Puppet.settings.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| handlearg(option[0], arg) end end option_parser.parse(args) end private :parse_global_options # TODO cprice: document def handlearg(opt, val) #puts("HANDLE ARG: '#{opt}'") opt, val = self.class.clean_opt(opt, val) Puppet.settings.handlearg(opt, val) end private :handlearg # TODO cprice: document def self.clean_opt(opt, val) # rewrite --[no-]option to --no-option if that's what was given if opt =~ /\[no-\]/ and !val opt = opt.gsub(/\[no-\]/,'no-') end # otherwise remove the [no-] prefix to not confuse everybody opt = opt.gsub(/\[no-\]/, '') [opt, val] end def self.available_subcommands # TODO cprice: eventually we probably want to replace this with a call to the autoloader. however, at the moment # the autoloader considers the module path when loading, and we don't want to allow apps / faces to load # from there. Once that is resolved, this should be replaced. --cprice 2012-03-06 absolute_appdirs = $LOAD_PATH.collect do |x| File.join(x,'puppet','application') end.select{ |x| File.directory?(x) } absolute_appdirs.inject([]) do |commands, dir| commands + Dir[File.join(dir, '*.rb')].map{|fn| File.basename(fn, '.rb')} end.uniq end # available_subcommands was previously an instance method, not a class # method, and we have an unknown number of user-implemented applications # that depend on that behaviour. Forwarding allows us to preserve a # backward compatible API. --daniel 2011-04-11 def available_subcommands self.class.available_subcommands end def require_application(application) require File.join(appdir, application) end def execute # TODO cprice: document, possibly refactor into some kind of setup/init method Puppet::Util.exit_on_fail("parse global options") { parse_global_options } + + # NOTE: this is a change in behavior where we are now parsing the config file on every run; before, there # were several apps that did not do this. Puppet::Util.exit_on_fail("parse configuration file") { Puppet.settings.parse } if subcommand_name and available_subcommands.include?(subcommand_name) then require_application subcommand_name # TODO cprice: replace this. For the short term, replace it with something that is not so application-specific # (i.e.. so that we can load faces). Longer-term, use the autoloader. See comments in # #available_subcommands method above. --cprice 2012-03-06 app = Puppet::Application.find(subcommand_name).new(self) Puppet::Plugins.on_application_initialization(:appliation_object => self) # See the note in 'warn_later' down below. --daniel 2011-06-01 if $delayed_deprecation_warning_for_p_u_cl.is_a? String then Puppet.deprecation_warning($delayed_deprecation_warning_for_p_u_cl) $delayed_deprecation_warning_for_p_u_cl = true end app.run elsif ! execute_external_subcommand then unless subcommand_name.nil? then puts "Error: Unknown Puppet subcommand '#{subcommand_name}'" end puts "See 'puppet help' for help on available puppet subcommands" end end def execute_external_subcommand external_command = "puppet-#{subcommand_name}" require 'puppet/util' path_to_subcommand = Puppet::Util.which(external_command) return false unless path_to_subcommand exec(path_to_subcommand, *args) end def legacy_executable_name name = CommandLineUtils::LegacyCommandLine::LEGACY_NAMES[ subcommand_name.intern ] return name unless name.nil? return subcommand_name.intern end private def subcommand_and_args(zero, argv, stdin) zero = File.basename(zero, '.rb') if zero == 'puppet' case argv.first when nil then if stdin.tty? then [nil, argv] # ttys get usage info else # Killed for 2.7.0 --daniel 2011-06-01 warn_later < level, :source => log_source, :message => message}.merge(log_metadata)) end # Create a method for each log level. Puppet::Util::Log.eachlevel do |level| define_method(level) do |args| args = args.join(" ") if args.is_a?(Array) send_log(level, args) end end # Log an exception via Puppet.err. Will also log the backtrace if Puppet[:trace] is set. # Parameters: # [exception] an Exception to log # [message] an optional String overriding the message to be logged; by default, we log Exception.message. # If you pass a String here, your string will be logged instead. You may also pass nil if you don't # wish to log a message at all; in this case it is likely that you are only calling this method in order # to take advantage of the backtrace logging. - def log_exception(exception, message = :default) - - # TODO cprice: document this insanity - log_proc = Proc.new do |msg| - if (Puppet::Util::Log.destinations.length == 0) - STDERR.puts(msg) - err(msg) - else - err(msg) - end - end + # [options] supported options: + # :force_console => if true, will ensure that the error is written to the console, even if the console is not + # on the configured list of logging destinations + def log_exception(exception, message = :default, options = {}) + ## TODO cprice: document this insanity + #log_proc = Proc.new do |msg| + # if ((Puppet::Util::Log.destinations.length == 0) or + # (options[:force_console]) and + # !(Puppet::Util::Log.destinations.has_key?(:console)) and + # ) + # STDERR.puts("err: " + msg) + # err(msg) + # else + # err(msg) + # end + #end case message when :default - log_proc.call(exception.message) + err(exception.message) when nil # don't log anything if they passed a nil; they are just calling for the optional backtrace logging else - log_proc.call(message) + err(message) end - log_proc.call(Puppet::Util.pretty_backtrace(exception.backtrace)) if Puppet[:trace] && exception.backtrace + err(Puppet::Util.pretty_backtrace(exception.backtrace)) if Puppet[:trace] && exception.backtrace + end + + + def log_and_raise(exception, message) + log_exception(exception, message) + raise Puppet::Error.new(message + "\n" + exception) end #TODO cprice: document or get rid of these def superdebug(msg) err("\n\n\n********************* #{msg}***********************\n\n\n") end def supertrace(msg) err("\n\n\n********************* #{msg}***********************\n#{Puppet::Util.pretty_backtrace}\n\n\n") end class DeprecationWarning < Exception; end # Log a warning indicating that the code path is deprecated. Note that this method keeps track of the # offending lines of code that triggered the deprecation warning, and will only log a warning once per # offending line of code. It will also stop logging deprecation warnings altogether after 100 unique # deprecation warnings have been logged. # Parameters: # [message] The message to log (logs via ) def deprecation_warning(message) $deprecation_warnings ||= {} if $deprecation_warnings.length < 100 then offender = get_deprecation_offender() if (! $deprecation_warnings.has_key?(offender)) then $deprecation_warnings[offender] = message warning("#{message}\n (at #{offender})") end end end def get_deprecation_offender() # we have to put this in its own method to simplify testing; we need to be able to mock the offender results in # order to test this class, and our framework does not appear to enjoy it if you try to mock Kernel.caller # # let's find the offending line; we need to jump back up the stack a few steps to find the method that called # the deprecated method caller()[2] end def clear_deprecation_warnings $deprecation_warnings.clear if $deprecation_warnings end # TODO: determine whether there might be a potential use for adding a puppet configuration option that would # enable this deprecation logging. # utility method that can be called, e.g., from spec_helper config.after, when tracking down calls to deprecated # code. # Parameters: # [deprecations_file] relative or absolute path of a file to log the deprecations to # [pattern] (default nil) if specified, will only log deprecations whose message matches the provided pattern def log_deprecations_to_file(deprecations_file, pattern = nil) # this method may get called lots and lots of times (e.g., from spec_helper config.after) without the global # list of deprecation warnings being cleared out. We don't want to keep logging the same offenders over and over, # so, we need to keep track of what we've logged. # # It'd be nice if we could just clear out the list of deprecation warnings, but then the very next spec might # find the same offender, and we'd end up logging it again. $logged_deprecation_warnings ||= {} File.open(deprecations_file, "a") do |f| if ($deprecation_warnings) then $deprecation_warnings.each do |offender, message| if (! $logged_deprecation_warnings.has_key?(offender)) then $logged_deprecation_warnings[offender] = true if ((pattern.nil?) || (message =~ pattern)) then f.puts(message) f.puts(offender) f.puts() end end end end end end private def is_resource? defined?(Puppet::Type) && is_a?(Puppet::Type) end def is_resource_parameter? defined?(Puppet::Parameter) && is_a?(Puppet::Parameter) end def log_metadata [:file, :line, :tags].inject({}) do |result, attr| result[attr] = send(attr) if respond_to?(attr) result end end def log_source # We need to guard the existence of the constants, since this module is used by the base Puppet module. (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s to_s end end diff --git a/lib/puppet/util/run_mode.rb b/lib/puppet/util/run_mode.rb index a3160d551..802eb22a1 100644 --- a/lib/puppet/util/run_mode.rb +++ b/lib/puppet/util/run_mode.rb @@ -1,83 +1,92 @@ module Puppet module Util class RunMode def initialize(name) @name = name.to_sym end @@run_modes = Hash.new {|h, k| h[k] = RunMode.new(k)} attr :name def self.[](name) @@run_modes[name] end def master? name == :master end def agent? name == :agent end def user? name == :user end - # TODO cprice: None of the following three methods seems to do anything that relates to run mode in any way; - # I wonder if this is the right place for them to be defined? --cprice 2012-03-06 + def conf_dir which_dir( - (Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") : "/etc/puppet"), - "~/.puppet" + Puppet::Util::Settings.default_global_config_dir, + Puppet::Util::Settings.default_user_config_dir ) end def var_dir which_dir( - (Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var") : "/var/lib/puppet"), - "~/.puppet/var" + Puppet::Util::Settings.default_global_var_dir, + Puppet::Util::Settings.default_user_var_dir ) end def run_dir "$vardir/run" end - def logopts - if master? - { - :default => "$vardir/log", - :mode => 0750, - :owner => "service", - :group => "service", - :desc => "The Puppet log directory." - } - else - ["$vardir/log", "The Puppet log directory."] - end + #def logopts + # TODO cprice: need to look into how to get this into the catalog during a "use", because + # these are not getting set as "defaults" any more. Best options are probably: + # 1. special-case this during "initialize_application_defaults" in settings.rb, + # 2. Allow :application_defaults settings category to carry these hashes with them somehow, + # 3. delay the original call to setdefaults/definesettings for the application settings, + # 4. make a second (override) call to "setdefaults/definesettings" during initialize_application_defaults + #if master? + # { + # :default => "$vardir/log", + # :mode => 0750, + # :owner => "service", + # :group => "service", + # :desc => "The Puppet log directory." + # } + #else + # ["$vardir/log", "The Puppet log directory."] + #end + #end + + def log_dir + "$vardir/log" end private def which_dir( global, user ) #FIXME: we should test if we're user "puppet" # there's a comment that suggests that we do that # and we currently don't. expand_path case when name == :master; global when Puppet.features.root?; global else user end end def expand_path( dir ) require 'etc' ENV["HOME"] ||= Etc.getpwuid(Process.uid).dir File.expand_path(dir) end end end end diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index 6716d5836..cc2f11391 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -1,964 +1,1077 @@ require 'puppet' require 'sync' require 'getoptlong' require 'puppet/external/event-loop' require 'puppet/util/loadedfile' +class Puppet::SettingsError < Puppet::Error +end + # The class for handling configuration files. class Puppet::Util::Settings include Enumerable - require 'puppet/util/settings/setting' + require 'puppet/util/settings/string_setting' require 'puppet/util/settings/file_setting' + require 'puppet/util/settings/dir_setting' require 'puppet/util/settings/boolean_setting' - attr_accessor :file + attr_accessor :files attr_reader :timer ReadOnly = [:run_mode, :name] + # TODO cprice: determine which of these are actually useful / meaningful + REQUIRED_APP_SETTINGS = [:name, :run_mode, :logdir, :confdir, :vardir] + + def self.default_global_config_dir() + Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") : "/etc/puppet" + end + + def self.default_user_config_dir() + "~/.puppet" + end + + def self.default_global_var_dir() + Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var") : "/var/lib/puppet" + end + + def self.default_user_var_dir() + "~/.puppet/var" + end + + def self.default_config_file_name() + "puppet.conf" + end + + # Retrieve a config value def [](param) value(param) end # Set a config value. This doesn't set the defaults, it sets the value itself. def []=(param, value) set_value(param, value, :memory) end # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. def addargs(options) # Add all of the config parameters as valid options. self.each { |name, setting| setting.getopt_args.each { |args| options << args } } options end # Generate the list of valid arguments, in a format that OptionParser can # understand, and add them to the passed option list. def optparse_addargs(options) # Add all of the config parameters as valid options. self.each { |name, setting| options << setting.optparse_args } options end # Is our parameter a boolean parameter? def boolean?(param) param = param.to_sym !!(@config.include?(param) and @config[param].kind_of? BooleanSetting) end # Remove all set values, potentially skipping cli values. def clear @sync.synchronize do unsafe_clear end end # Remove all set values, potentially skipping cli values. - def unsafe_clear(clear_cli = true, clear_mutable_defaults = false) + def unsafe_clear(clear_cli = true, clear_application_defaults = false) @values.each do |name, values| - next if ((name == :mutable_defaults) and !clear_mutable_defaults) + next if ((name == :application_defaults) and !clear_application_defaults) next if ((name == :cli) and !clear_cli) @values.delete(name) end # TODO cprice: if this condition is really based on the fact that we are reparsing # the config file, then our parameters should be named more clearly. # --cprice 2012-03-06 # Don't clear the 'used' in this case, since it's a config file reparse, # and we want to retain this info. @used = [] if clear_cli @cache.clear end private :unsafe_clear # Private method for internal test use only; allows to do a comprehensive clear of all settings between tests. # # @return nil def clear_for_tests() @sync.synchronize do unsafe_clear(true, true) + @app_defaults_initialized = false end end private :clear_for_tests # This is mostly just used for testing. def clearused @cache.clear @used = [] end + + def app_defaults_initialized?() + @app_defaults_initialized + end + + def initialize_app_defaults(app_defaults) + REQUIRED_APP_SETTINGS.each do |key| + raise Puppet::SettingsError, "missing required app default setting '#{key}'" unless app_defaults.has_key?(key) + end + app_defaults.each do |key, value| + set_value(key, value, :application_defaults) + end + + + @app_defaults_initialized = true + end + # Do variable interpolation on the value. def convert(value, environment = nil) return nil if value.nil? return value unless value.is_a? String newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value| varname = $2 || $1 if varname == "environment" and environment environment elsif pval = self.value(varname, environment) pval else - raise Puppet::DevError, "Could not find value for #{value}" + raise Puppet::SettingsError, "Could not find value for #{value}" end end newval end # Return a value's description. def description(name) if obj = @config[name.to_sym] obj.desc else nil end end def each @config.each { |name, object| yield name, object } end # Iterate over each section name. def eachsection yielded = [] @config.each do |name, object| section = object.section unless yielded.include? section yield section yielded << section end end end # Return an object by name. def setting(param) param = param.to_sym @config[param] end # Handle a command-line argument. def handlearg(opt, value = nil) @cache.clear if value.is_a?(FalseClass) value = "false" elsif value.is_a?(TrueClass) value = "true" end value &&= munge_value(value) str = opt.sub(/^--/,'') bool = true newstr = str.sub(/^no-/, '') if newstr != str str = newstr bool = false end str = str.intern if @config[str].is_a?(Puppet::Util::Settings::BooleanSetting) if value == "" or value.nil? value = bool end end set_value(str, value, :cli) end def include?(name) name = name.intern if name.is_a? String @config.include?(name) end # check to see if a short name is already defined def shortinclude?(short) short = short.intern if name.is_a? String @shortnames.include?(short) end # Create a new collection of config settings. def initialize @config = {} @shortnames = {} @created = [] @searchpath = nil # Mutex-like thing to protect @values @sync = Sync.new # Keep track of set values. @values = Hash.new { |hash, key| hash[key] = {} } # And keep a per-environment cache @cache = Hash.new { |hash, key| hash[key] = {} } # The list of sections we've used. @used = [] end # NOTE: ACS ahh the util classes. . .sigh # as part of a fix for 1183, I pulled the logic for the following 5 methods out of the executables and puppet.rb # They probably deserve their own class, but I don't want to do that until I can refactor environments # its a little better than where they were # Prints the contents of a config file with the available config settings, or it # prints a single value of a config setting. def print_config_options env = value(:environment) val = value(:configprint) if val == "all" hash = {} each do |name, obj| val = value(name,env) val = val.inspect if val == "" hash[name] = val end hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val| puts "#{name} = #{val}" end else val.split(/\s*,\s*/).sort.each do |v| if include?(v) #if there is only one value, just print it for back compatibility if v == val puts value(val,env) break end puts "#{v} = #{value(v,env)}" else puts "invalid parameter: #{v}" return false end end end true end def generate_config puts to_config true end def generate_manifest puts to_manifest true end def print_configs return print_config_options if value(:configprint) != "" return generate_config if value(:genconfig) generate_manifest if value(:genmanifest) end def print_configs? (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true end # Return a given object's file metadata. def metadata(param) if obj = @config[param.to_sym] and obj.is_a?(FileSetting) return [:owner, :group, :mode].inject({}) do |meta, p| if v = obj.send(p) meta[p] = v end meta end else nil end end # Make a directory with the appropriate user, group, and mode def mkdir(default) obj = get_config_file_default(default) Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do mode = obj.mode || 0750 Dir.mkdir(obj.value, mode) end end # Figure out the section name for the run_mode. def run_mode Puppet.run_mode.name end # Return all of the parameters associated with a given section. def params(section = nil) if section section = section.intern if section.is_a? String @config.find_all { |name, obj| obj.section == section }.collect { |name, obj| name } else @config.keys end end # Parse the configuration file. Just provides # thread safety. def parse - raise "No :config setting defined; cannot parse unknown config file" unless self[:config] - + #config_file_paths = + # begin + # self[:config] + # rescue SettingsError => err + # "/etc/puppet/" + # end + #raise "No :config setting defined; cannot parse unknown config file" unless self[:config] + + # TODO cprice: document... precedence et al @sync.synchronize do - unsafe_parse(self[:config]) + unsafe_parse(main_config_file) + unsafe_parse(user_config_file) unless Puppet.features.root? end # Create a timer so that this file will get checked automatically # and reparsed if necessary. set_filetimeout_timer end + def main_config_file + begin + return self[:config] if self[:config] + rescue Puppet::SettingsError => err + # TODO cprice: doc + end + return File.join(self.class.default_global_config_dir, config_file_name) + end + private :main_config_file + + def user_config_file + return File.join(self.class.default_user_config_dir, config_file_name) + end + private :user_config_file + + def config_file_name + begin + return self[:config_file_name] if self[:config_file_name] + rescue Puppet::SettingsError => err + # TODO cprice: doc + end + return self.class.default_config_file_name + end + private :config_file_name + # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly. def unsafe_parse(file) + Puppet.debug("Looking for config file '#{file}'") return unless FileTest.exist?(file) begin data = parse_file(file) + Puppet.debug("Parsed config file '#{file}'") rescue => detail Puppet.log_exception(detail, "Could not parse #{file}: #{detail}") return end unsafe_clear(false) metas = {} data.each do |area, values| metas[area] = values.delete(:_meta) values.each do |key,value| set_value(key, value, area, :dont_trigger_handles => true, :ignore_bad_settings => true ) end end # Determine our environment, if we have one. if @config[:environment] env = self.value(:environment).to_sym else env = "none" end # Call any hooks we should be calling. settings_with_hooks.each do |setting| each_source(env) do |source| if value = @values[source][setting.name] # We still have to use value to retrieve the value, since # we want the fully interpolated value, not $vardir/lib or whatever. # This results in extra work, but so few of the settings # will have associated hooks that it ends up being less work this # way overall. setting.handle(self.value(setting.name, env)) break end end end # We have to do it in the reverse of the search path, # because multiple sections could set the same value # and I'm too lazy to only set the metadata once. searchpath.reverse.each do |source| source = run_mode if source == :run_mode source = @name if (@name && source == :name) if meta = metas[source] set_metadata(meta) end end end # The following method strikes me as a good example of how dynamic languages # can be abused; I'd really like to see some stronger typing on this stuff. # And I will totally try to do that if I get a chance. :) # --cprice 2012-03-06 # Create a new setting. The value is passed in because it's used to determine # what kind of setting we're creating, but the value itself might be either # a default or a value, so we can't actually assign it. def newsetting(hash) klass = nil hash[:section] = hash[:section].to_sym if hash[:section] + + # TODO cprice: document these setting types and mention that some are placeholders for the future + if type = hash[:type] - unless klass = {:setting => Setting, :file => FileSetting, :boolean => BooleanSetting}[type] + unless klass = { + :string => StringSetting, + :file => FileSetting, + :directory => DirSetting, + :path => StringSetting, + :boolean => BooleanSetting, + } [type] raise ArgumentError, "Invalid setting type '#{type}'" end hash.delete(:type) else - case hash[:default] - when true, false, "true", "false" - klass = BooleanSetting - when /^\$\w+\//, /^\//, /^\w:\// - klass = FileSetting - when String, Integer, Float # nothing - klass = Setting - else - raise ArgumentError, "Invalid value '#{hash[:default].inspect}' for #{hash[:name]}" - end + #case hash[:default] + #when true, false, "true", "false" + # klass = BooleanSetting + #when /^\$\w+\//, /^\//, /^\w:\// + # klass = FileSetting + #when String, Integer, Float, nil # nothing + # klass = Setting + #else + # raise ArgumentError, "Invalid value '#{hash[:default].inspect}' for #{hash[:name]}" + #end + klass = StringSetting end hash[:settings] = self setting = klass.new(hash) setting end # This has to be private, because it doesn't add the settings to @config private :newsetting # Iterate across all of the objects in a given section. def persection(section) section = section.to_sym self.each { |name, obj| if obj.section == section yield obj end } end - def file - return @file if @file - if path = self[:config] and FileTest.exist?(path) - @file = Puppet::Util::LoadedFile.new(path) + def files + return @files if @files + @files = [] + [main_config_file, user_config_file].each do |path| + if FileTest.exist?(path) + @files << Puppet::Util::LoadedFile.new(path) + end end + @files end # Reparse our config file, if necessary. def reparse - if file and file.changed? - Puppet.notice "Reparsing #{file.file}" - parse - reuse + if files + files.each do |file| + if file.changed? + Puppet.notice "Reparsing #{file.file}" + parse + reuse + end + end end end def reuse return unless defined?(@used) @sync.synchronize do # yay, thread-safe new = @used @used = [] self.use(*new) end end # The order in which to search for values. def searchpath(environment = nil) if environment - [:cli, :memory, environment, :run_mode, :main, :mutable_defaults] + [:cli, :memory, environment, :run_mode, :main, :application_defaults] else - [:cli, :memory, :run_mode, :main, :mutable_defaults] + [:cli, :memory, :run_mode, :main, :application_defaults] end end # Get a list of objects per section def sectionlist sectionlist = [] self.each { |name, obj| section = obj.section || "puppet" sections[section] ||= [] sectionlist << section unless sectionlist.include?(section) sections[section] << obj } return sectionlist, sections end def service_user_available? return @service_user_available if defined?(@service_user_available) return @service_user_available = false unless user_name = self[:user] user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure @service_user_available = user.exists? end def legacy_to_mode(type, param) require 'puppet/util/command_line_utils/legacy_command_line' if Puppet::Util::CommandLineUtils::LegacyCommandLine::LEGACY_APPS.has_key?(type) new_type = Puppet::Util::CommandLineUtils::LegacyCommandLine::LEGACY_APPS[type].run_mode Puppet.deprecation_warning "You have configuration parameter $#{param} specified in [#{type}], which is a deprecated section. I'm assuming you meant [#{new_type}]" return new_type end type end def set_value(param, value, type, options = {}) param = param.to_sym unless setting = @config[param] if options[:ignore_bad_settings] return else raise ArgumentError, "Attempt to assign a value to unknown configuration parameter #{param.inspect}" end end value = setting.munge(value) if setting.respond_to?(:munge) setting.handle(value) if setting.respond_to?(:handle) and not options[:dont_trigger_handles] - if ReadOnly.include? param and type != :mutable_defaults + if ReadOnly.include? param and type != :application_defaults raise ArgumentError, "You're attempting to set configuration parameter $#{param}, which is read-only." end type = legacy_to_mode(type, param) @sync.synchronize do # yay, thread-safe # Allow later inspection to determine if the setting was set on the # command line, or through some other code path. Used for the # `dns_alt_names` option during cert generate. --daniel 2011-10-18 setting.setbycli = true if type == :cli @values[type][param] = value @cache.clear clearused # Clear the list of environments, because they cache, at least, the module path. # We *could* preferentially just clear them if the modulepath is changed, # but we don't really know if, say, the vardir is changed and the modulepath # is defined relative to it. We need the defined?(stuff) because of loading # order issues. Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment) end value end + # TODO cprice: rename this. + # Set a bunch of defaults in a given section. The sections are actually pretty # pointless, but they help break things up a bit, anyway. def setdefaults(section, defs) section = section.to_sym call = [] defs.each { |name, hash| - if hash.is_a? Array - unless hash.length == 2 - raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription" - end - tmp = hash - hash = {} - [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } - end + raise ArgumentError, "setting definition for '#{name}' is not a hash!" unless hash.is_a? Hash + #if hash.is_a? Array + # unless hash.length == 2 + # raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription" + # end + # tmp = hash + # hash = {} + # [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } + #end name = name.to_sym hash[:name] = name hash[:section] = section raise ArgumentError, "Parameter #{name} is already defined" if @config.include?(name) tryconfig = newsetting(hash) if short = tryconfig.short if other = @shortnames[short] raise ArgumentError, "Parameter #{other.name} is already using short name '#{short}'" end @shortnames[short] = tryconfig end @config[name] = tryconfig # Collect the settings that need to have their hooks called immediately. # We have to collect them so that we can be sure we're fully initialized before # the hook is called. call << tryconfig if tryconfig.call_on_define } call.each { |setting| setting.handle(self.value(setting.name)) } end # Create a timer to check whether the file should be reparsed. def set_filetimeout_timer return unless timeout = self[:filetimeout] and timeout = Integer(timeout) and timeout > 0 timer = EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse } end # Convert the settings we manage into a catalog full of resources that model those settings. def to_catalog(*sections) sections = nil if sections.empty? catalog = Puppet::Resource::Catalog.new("Settings") - @config.values.find_all { |value| value.is_a?(FileSetting) }.each do |file| + #@config.each.find_all { |key, value| value.is_a?(FileSetting) }.each do |key, file| + @config.keys.find_all { |key| @config[key].is_a?(FileSetting) }.each do |key| + file = @config[key] next unless (sections.nil? or sections.include?(file.section)) next unless resource = file.to_resource next if catalog.resource(resource.ref) + # This can be really useful for debugging... + #puts("Using settings: adding file resource '#{key}': '#{resource.inspect}'") + catalog.add_resource(resource) end add_user_resources(catalog, sections) catalog end # Convert our list of config settings into a configuration file. def to_config str = %{The configuration file for #{Puppet[:name]}. Note that this file is likely to have unused configuration parameters in it; any parameter that's valid anywhere in Puppet can be in any config file, even if it's not used. Every section can specify three special parameters: owner, group, and mode. These parameters affect the required permissions of any files specified after their specification. Puppet will sometimes use these parameters to check its own configured state, so they can be used to make Puppet a bit more self-managing. Generated on #{Time.now}. }.gsub(/^/, "# ") # Add a section heading that matches our name. if @config.include?(:run_mode) str += "[#{self[:run_mode]}]\n" end eachsection do |section| persection(section) do |obj| str += obj.to_config + "\n" unless ReadOnly.include? obj.name or obj.name == :genconfig end end return str end # Convert to a parseable manifest def to_manifest catalog = to_catalog catalog.resource_refs.collect do |ref| catalog.resource(ref).to_manifest end.join("\n\n") end # Create the necessary objects to use a section. This is idempotent; # you can 'use' a section as many times as you want. def use(*sections) sections = sections.collect { |s| s.to_sym } @sync.synchronize do # yay, thread-safe sections = sections.reject { |s| @used.include?(s) } return if sections.empty? begin catalog = to_catalog(*sections).to_ral rescue => detail - Puppet.log_exception(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") + Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") - # We need some way to get rid of any resources created during the catalog creation - # but not cleaned up. - return + # TODO cprice: kill + ## We need some way to get rid of any resources created during the catalog creation + ## but not cleaned up. + #return end catalog.host_config = false catalog.apply do |transaction| if transaction.any_failed? report = transaction.report failures = report.logs.find_all { |log| log.level == :err } raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}" end end sections.each { |s| @used << s } @used.uniq! end end def valid?(param) param = param.to_sym @config.has_key?(param) end def uninterpolated_value(param, environment = nil) param = param.to_sym environment &&= environment.to_sym ## do we really need to be using throw here? isn't that a fairly slow operation? ## I guess if we are caching the values then this shouldn't get called all that ## often. --cprice 2012-03-06 # See if we can find it within our searchable list of values val = catch :foundval do each_source(environment) do |source| # Look for the value. We have to test the hash for whether # it exists, because the value might be false. @sync.synchronize do throw :foundval, @values[source][param] if @values[source].include?(param) end end throw :foundval, nil end # If we didn't get a value, use the default val = @config[param].default if val.nil? val end # Find the correct value using our search path. Optionally accept an environment # in which to search before the other configuration sections. def value(param, environment = nil) param = param.to_sym environment &&= environment.to_sym # Short circuit to nil for undefined parameters. return nil unless @config.include?(param) # Yay, recursion. #self.reparse unless [:config, :filetimeout].include?(param) # Check the cache first. It needs to be a per-environment # cache so that we don't spread values from one env # to another. if cached = @cache[environment||"none"][param] return cached end val = uninterpolated_value(param, environment) if param == :code # if we interpolate code, all hell breaks loose. return val end # Convert it if necessary - val = convert(val, environment) + begin + val = convert(val, environment) + rescue Puppet::SettingsError => err + raise Puppet::SettingsError.new("Error converting value for param '#{param}': #{err}") + end + # And cache it @cache[environment||"none"][param] = val val end # Open a file with the appropriate user, group, and mode def write(default, *args, &bloc) obj = get_config_file_default(default) writesub(default, value(obj.name), *args, &bloc) end # Open a non-default file under a default dir with the appropriate user, # group, and mode def writesub(default, file, *args, &bloc) obj = get_config_file_default(default) chown = nil if Puppet.features.root? chown = [obj.owner, obj.group] else chown = [nil, nil] end Puppet::Util::SUIDManager.asuser(*chown) do mode = obj.mode ? obj.mode.to_i : 0640 args << "w" if args.empty? args << mode # Update the umask to make non-executable files Puppet::Util.withumask(File.umask ^ 0111) do File.open(file, *args) do |file| yield file end end end end def readwritelock(default, *args, &bloc) file = value(get_config_file_default(default).name) tmpfile = file + ".tmp" sync = Sync.new raise Puppet::DevError, "Cannot create #{file}; directory #{File.dirname(file)} does not exist" unless FileTest.directory?(File.dirname(tmpfile)) sync.synchronize(Sync::EX) do File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf| rf.lock_exclusive do if File.exist?(tmpfile) raise Puppet::Error, ".tmp file already exists for #{file}; Aborting locked write. Check the .tmp file and delete if appropriate" end # If there's a failure, remove our tmpfile begin writesub(default, tmpfile, *args, &bloc) rescue File.unlink(tmpfile) if FileTest.exist?(tmpfile) raise end begin File.rename(tmpfile, file) rescue => detail Puppet.err "Could not rename #{file} to #{tmpfile}: #{detail}" File.unlink(tmpfile) if FileTest.exist?(tmpfile) end end end end end private def get_config_file_default(default) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default #{default}" end raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting obj end def add_user_resources(catalog, sections) return unless Puppet.features.root? return if Puppet.features.microsoft_windows? return unless self[:mkusers] @config.each do |name, setting| next unless setting.respond_to?(:owner) next unless sections.nil? or sections.include?(setting.section) if user = setting.owner and user != "root" and catalog.resource(:user, user).nil? resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present}) resource[:gid] = self[:group] if self[:group] catalog.add_resource resource end if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil? catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present}) end end end # Yield each search source in turn. def each_source(environment) searchpath(environment).each do |source| # Modify the source as necessary. source = self.run_mode if source == :run_mode yield source end end # Return all settings that have associated hooks; this is so # we can call them after parsing the configuration file. def settings_with_hooks @config.values.find_all { |setting| setting.respond_to?(:handle) } end # Extract extra setting information for files. def extract_fileinfo(string) result = {} value = string.sub(/\{\s*([^}]+)\s*\}/) do params = $1 params.split(/\s*,\s*/).each do |str| if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/ param, value = $1.intern, $2 result[param] = value raise ArgumentError, "Invalid file option '#{param}'" unless [:owner, :mode, :group].include?(param) if param == :mode and value !~ /^\d+$/ raise ArgumentError, "File modes must be numbers" end else raise ArgumentError, "Could not parse '#{string}'" end end '' end result[:value] = value.sub(/\s*$/, '') result end # Convert arguments into booleans, integers, or whatever. def munge_value(value) # Handle different data types correctly return case value when /^false$/i; false when /^true$/i; true when /^\d+$/i; Integer(value) when true; true when false; false else value.gsub(/^["']|["']$/,'').sub(/\s+$/, '') end end # This method just turns a file in to a hash of hashes. def parse_file(file) text = read_file(file) result = Hash.new { |names, name| names[name] = {} } count = 0 # Default to 'main' for the section. section = :main result[section][:_meta] = {} text.split(/\n/).each do |line| count += 1 case line when /^\s*\[(\w+)\]\s*$/ section = $1.intern # Section names - #disallow mutable_defaults in config file - if section == :mutable_defaults - raise Puppet::Error.new("Illegal section 'mutable_defaults' in config file", file, line) + #disallow application_defaults in config file + if section == :application_defaults + raise Puppet::Error.new("Illegal section 'application_defaults' in config file", file, line) end # Add a meta section result[section][:_meta] ||= {} when /^\s*#/; next # Skip comments when /^\s*$/; next # Skip blanks when /^\s*(\w+)\s*=\s*(.*?)\s*$/ # settings var = $1.intern # We don't want to munge modes, because they're specified in octal, so we'll # just leave them as a String, since Puppet handles that case correctly. if var == :mode value = $2 else value = munge_value($2) end # Check to see if this is a file argument and it has extra options begin if value.is_a?(String) and options = extract_fileinfo(value) value = options[:value] options.delete(:value) result[section][:_meta][var] = options end result[section][var] = value rescue Puppet::Error => detail detail.file = file detail.line = line raise end else error = Puppet::Error.new("Could not match line #{line}") error.file = file error.line = line raise error end end result end # Read the file in. def read_file(file) begin return File.read(file) rescue Errno::ENOENT raise ArgumentError, "No such file #{file}" rescue Errno::EACCES raise ArgumentError, "Permission denied to file #{file}" end end # Set file metadata. def set_metadata(meta) meta.each do |var, values| values.each do |param, value| @config[var].send(param.to_s + "=", value) end end end end diff --git a/lib/puppet/util/settings/boolean_setting.rb b/lib/puppet/util/settings/boolean_setting.rb index e4678c9b4..d11e4c837 100644 --- a/lib/puppet/util/settings/boolean_setting.rb +++ b/lib/puppet/util/settings/boolean_setting.rb @@ -1,30 +1,30 @@ -require 'puppet/util/settings/setting' +require 'puppet/util/settings/string_setting' # A simple boolean. -class Puppet::Util::Settings::BooleanSetting < Puppet::Util::Settings::Setting +class Puppet::Util::Settings::BooleanSetting < Puppet::Util::Settings::StringSetting # get the arguments in getopt format def getopt_args if short [["--#{name}", "-#{short}", GetoptLong::NO_ARGUMENT], ["--no-#{name}", GetoptLong::NO_ARGUMENT]] else [["--#{name}", GetoptLong::NO_ARGUMENT], ["--no-#{name}", GetoptLong::NO_ARGUMENT]] end end def optparse_args if short ["--[no-]#{name}", "-#{short}", desc, :NONE ] else ["--[no-]#{name}", desc, :NONE] end end def munge(value) case value when true, "true"; return true when false, "false"; return false else raise ArgumentError, "Invalid value '#{value.inspect}' for #{@name}" end end end diff --git a/lib/puppet/util/settings/dir_setting.rb b/lib/puppet/util/settings/dir_setting.rb new file mode 100644 index 000000000..cfa07e834 --- /dev/null +++ b/lib/puppet/util/settings/dir_setting.rb @@ -0,0 +1,7 @@ +require 'puppet/util/settings/file_setting' + +class Puppet::Util::Settings::DirSetting < Puppet::Util::Settings::FileSetting + def type + return :directory + end +end \ No newline at end of file diff --git a/lib/puppet/util/settings/file_setting.rb b/lib/puppet/util/settings/file_setting.rb index 2b1c9d1d4..813f82a4a 100644 --- a/lib/puppet/util/settings/file_setting.rb +++ b/lib/puppet/util/settings/file_setting.rb @@ -1,135 +1,138 @@ -require 'puppet/util/settings/setting' +require 'puppet/util/settings/string_setting' # A file. -class Puppet::Util::Settings::FileSetting < Puppet::Util::Settings::Setting +class Puppet::Util::Settings::FileSetting < Puppet::Util::Settings::StringSetting 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' # Preserve trailing slash (stripped by expand_path) isdir = value =~ /\/$/ value = File.expand_path(value) value += '/' if isdir end value end + # TODO cprice: this method is EVIL. We should get rid of it and replace it with a new subclass called DirectorySetting. + # I am going to do this very soon. --cprice 2012-03-14 + # 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] 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/lib/puppet/util/settings/setting.rb b/lib/puppet/util/settings/string_setting.rb similarity index 92% rename from lib/puppet/util/settings/setting.rb rename to lib/puppet/util/settings/string_setting.rb index 07409eefe..0a21871f6 100644 --- a/lib/puppet/util/settings/setting.rb +++ b/lib/puppet/util/settings/string_setting.rb @@ -1,94 +1,94 @@ # The base element type. -class Puppet::Util::Settings::Setting +class Puppet::Util::Settings::StringSetting attr_accessor :name, :section, :default, :setbycli, :call_on_define attr_reader :desc, :short def desc=(value) @desc = value.gsub(/^\s*/, '') end # get the arguments in getopt format def getopt_args if short [["--#{name}", "-#{short}", GetoptLong::REQUIRED_ARGUMENT]] else [["--#{name}", GetoptLong::REQUIRED_ARGUMENT]] end end # get the arguments in OptionParser format def optparse_args if short ["--#{name}", "-#{short}", desc, :REQUIRED] else ["--#{name}", desc, :REQUIRED] end end def hook=(block) meta_def :handle, &block end # Create the new element. Pretty much just sets the name. def initialize(args = {}) unless @settings = args.delete(:settings) raise ArgumentError.new("You must refer to a settings object") end args.each do |param, value| method = param.to_s + "=" - raise ArgumentError, "#{self.class} does not accept #{param}" unless self.respond_to? method + raise ArgumentError, "#{self.class} (setting '#{args[:name]}') does not accept #{param}" unless self.respond_to? method self.send(method, value) end raise ArgumentError, "You must provide a description for the #{self.name} config option" unless self.desc end def iscreated @iscreated = true end def iscreated? @iscreated end def set? !!(!@value.nil?) end # short name for the celement def short=(value) raise ArgumentError, "Short names can only be one character." if value.to_s.length != 1 @short = value.to_s end # Convert the object to a config statement. def to_config str = @desc.gsub(/^/, "# ") + "\n" # Add in a statement about the default. str += "# The default value is '#{@default}'.\n" if @default # If the value has not been overridden, then print it out commented # and unconverted, so it's clear that that's the default and how it # works. value = @settings.value(self.name) if value != @default line = "#{@name} = #{value}" else line = "# #{@name} = #{@default}" end str += line + "\n" str.gsub(/^/, " ") end # Retrieves the value, or if it's not set, retrieves the default. def value @settings.value(self.name) end end diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index 5bf89c778..2dd290f5c 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -1,315 +1,315 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/defaults' require 'puppet/rails' describe "Puppet defaults" do after { Puppet.settings.clear } describe "when setting the :factpath" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") Puppet.settings[:factpath] = "/my/fact/path" end end describe "when setting the :certname" do it "should fail if the certname is not downcased" do lambda { Puppet.settings[:certname] = "Host.Domain.Com" }.should raise_error(ArgumentError) end end describe "when setting :node_name_value" do it "should default to the value of :certname" do Puppet.settings[:certname] = 'blargle' Puppet.settings[:node_name_value].should == 'blargle' end end describe "when setting the :node_name_fact" do it "should fail when also setting :node_name_value" do lambda do Puppet.settings[:node_name_value] = "some value" Puppet.settings[:node_name_fact] = "some_fact" end.should raise_error("Cannot specify both the node_name_value and node_name_fact settings") end it "should not fail when using the default for :node_name_value" do lambda do Puppet.settings[:node_name_fact] = "some_fact" end.should_not raise_error end end describe "when :certdnsnames is set" do it "should not fail" do expect { Puppet[:certdnsnames] = 'fred:wilma' }.should_not raise_error end it "should warn the value is ignored" do Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ } Puppet[:certdnsnames] = 'fred:wilma' end end describe "when configuring the :crl" do it "should warn if :cacrl is set to false" do Puppet.expects(:warning) Puppet.settings[:cacrl] = 'false' end end describe "when setting the :catalog_format" do it "should log a deprecation notice" do Puppet.expects(:deprecation_warning) Puppet.settings[:catalog_format] = 'marshal' end it "should copy the value to :preferred_serialization_format" do Puppet.settings[:catalog_format] = 'marshal' Puppet.settings[:preferred_serialization_format].should == 'marshal' end end it "should have a clientyamldir setting" do Puppet.settings[:clientyamldir].should_not be_nil end it "should have different values for the yamldir and clientyamldir" do Puppet.settings[:yamldir].should_not == Puppet.settings[:clientyamldir] end it "should have a client_datadir setting" do Puppet.settings[:client_datadir].should_not be_nil end it "should have different values for the server_datadir and client_datadir" do Puppet.settings[:server_datadir].should_not == Puppet.settings[:client_datadir] end # See #1232 it "should not specify a user or group for the clientyamldir" do Puppet.settings.setting(:clientyamldir).owner.should be_nil Puppet.settings.setting(:clientyamldir).group.should be_nil end it "should use the service user and group for the yamldir" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user] Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group] end # See #1232 it "should not specify a user or group for the rundir" do Puppet.settings.setting(:rundir).owner.should be_nil Puppet.settings.setting(:rundir).group.should be_nil end it "should specify that the host private key should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostprivkey).owner.should == Puppet.settings[:user] end it "should specify that the host certificate should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostcert).owner.should == Puppet.settings[:user] end it "should use a bind address of ''" do Puppet.settings.clear Puppet.settings[:bindaddress].should == "" end [:modulepath, :factpath].each do |setting| it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do - Puppet.settings.setting(setting).should be_instance_of(Puppet::Util::Settings::Setting) + Puppet.settings.setting(setting).should be_instance_of(Puppet::Util::Settings::StringSetting) end end it "should add /usr/sbin and /sbin to the path if they're not there" do Puppet::Util.withenv("PATH" => "/usr/bin:/usr/local/bin") do Puppet.settings[:path] = "none" # this causes it to ignore the setting ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/usr/sbin") ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/sbin") end end it "should default to pson for the preferred serialization format" do Puppet.settings.value(:preferred_serialization_format).should == "pson" end describe "when enabling storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set the Catalog cache class to :store_configs" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should not set the Catalog cache class to :store_configs if asynchronous storeconfigs is enabled" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs).never Puppet.settings.expects(:value).with(:async_storeconfigs).returns true Puppet.settings[:storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling asynchronous storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:async_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end it "should set the Catalog cache class to :queue" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:queue) Puppet.settings[:async_storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling thin storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:thin_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end end it "should have a setting for determining the configuration version and should default to an empty string" do Puppet.settings[:config_version].should == "" end describe "when enabling reports" do it "should use the default server value when report server is unspecified" do Puppet.settings[:server] = "server" Puppet.settings[:report_server].should == "server" end it "should use the default masterport value when report port is unspecified" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port].should == "1234" end it "should set report_server when reportserver is set" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server].should == "reportserver" end it "should use report_port when set" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port] = "5678" Puppet.settings[:report_port].should == "5678" end it "should prefer report_server over reportserver" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server] = "report_server" Puppet.settings[:report_server].should == "report_server" end end it "should have a :caname setting that defaults to the cert name" do Puppet.settings[:certname] = "foo" Puppet.settings[:ca_name].should == "Puppet CA: foo" end it "should have a 'prerun_command' that defaults to the empty string" do Puppet.settings[:prerun_command].should == "" end it "should have a 'postrun_command' that defaults to the empty string" do Puppet.settings[:postrun_command].should == "" end it "should have a 'certificate_revocation' setting that defaults to true" do Puppet.settings[:certificate_revocation].should be_true end it "should have an http_compression setting that defaults to false" do Puppet.settings[:http_compression].should be_false end describe "reportdir" do subject { Puppet.settings[:reportdir] } it { should == "#{Puppet[:vardir]}/reports" } end describe "reporturl" do subject { Puppet.settings[:reporturl] } it { should == "http://localhost:3000/reports/upload" } end describe "when configuring color" do it "should default to ansi", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:color].should == 'ansi' end it "should default to false", :if => Puppet.features.microsoft_windows? do Puppet.settings[:color].should == 'false' end end describe "daemonize" do it "should default to true", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:daemonize].should == true end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should default to false" do Puppet.settings[:daemonize].should == false end it "should raise an error if set to true" do lambda { Puppet.settings[:daemonize] = true }.should raise_error(/Cannot daemonize on Windows/) end end end describe "diff" do it "should default to 'diff' on POSIX", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:diff].should == 'diff' end it "should default to '' on Windows", :if => Puppet.features.microsoft_windows? do Puppet.settings[:diff].should == '' end end end diff --git a/spec/integration/network/server/webrick_spec.rb b/spec/integration/network/server/webrick_spec.rb index 7fa83b05a..f50801e38 100755 --- a/spec/integration/network/server/webrick_spec.rb +++ b/spec/integration/network/server/webrick_spec.rb @@ -1,83 +1,84 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' require 'puppet/ssl/certificate_authority' require 'socket' describe Puppet::Network::Server, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files describe "when using webrick" do before :each do Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' @params = { :port => 34343, :handlers => [ :node ] } # Get a safe temporary file dir = tmpdir("webrick_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir + Puppet.settings[:logdir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.indirection.find(Puppet[:certname]) end after do Puppet.settings.clear Puppet::SSL::Host.ca_location = :none end describe "before listening" do it "should not be reachable at the specified address and port" do lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error end end describe "when listening" do it "should be reachable on the specified address and port" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.listen lambda { TCPSocket.new('127.0.0.1', 34343) }.should_not raise_error end it "should default to '0.0.0.0' as its bind address" do Puppet.settings.clear Puppet[:servertype] = 'webrick' Puppet[:bindaddress].should == '0.0.0.0' end it "should use any specified bind address" do Puppet[:bindaddress] = "127.0.0.1" @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "127.0.0.1" } @server.listen end it "should not allow multiple servers to listen on the same address and port" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.listen @server2 = Puppet::Network::Server.new(@params.merge(:port => 34343)) lambda { @server2.listen }.should raise_error end after :each do @server.unlisten if @server && @server.listening? end end describe "after unlistening" do it "should not be reachable on the port and address assigned" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.listen @server.unlisten lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error(Errno::ECONNREFUSED) end end end end diff --git a/spec/integration/util/autoload_spec.rb b/spec/integration/util/autoload_spec.rb index 44f4e583d..b5f1455a4 100755 --- a/spec/integration/util/autoload_spec.rb +++ b/spec/integration/util/autoload_spec.rb @@ -1,104 +1,107 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/autoload' require 'fileutils' class AutoloadIntegrator @things = [] def self.newthing(name) @things << name end def self.thing?(name) @things.include? name end def self.clear @things.clear end end require 'puppet_spec/files' describe Puppet::Util::Autoload do include PuppetSpec::Files def with_file(name, *path) path = File.join(*path) # Now create a file to load File.open(path, "w") { |f| f.puts "\nAutoloadIntegrator.newthing(:#{name.to_s})\n" } yield File.delete(path) end def with_loader(name, path) dir = tmpfile(name + path) $LOAD_PATH << dir Dir.mkdir(dir) rbdir = File.join(dir, path.to_s) Dir.mkdir(rbdir) loader = Puppet::Util::Autoload.new(name, path) yield rbdir, loader Dir.rmdir(rbdir) Dir.rmdir(dir) $LOAD_PATH.pop AutoloadIntegrator.clear end it "should make instances available by the loading class" do loader = Puppet::Util::Autoload.new("foo", "bar") Puppet::Util::Autoload["foo"].should == loader end it "should not fail when asked to load a missing file" do Puppet::Util::Autoload.new("foo", "bar").load(:eh).should be_false end it "should load and return true when it successfully loads a file" do with_loader("foo", "bar") { |dir,loader| with_file(:mything, dir, "mything.rb") { loader.load(:mything).should be_true loader.class.should be_loaded("bar/mything") AutoloadIntegrator.should be_thing(:mything) } } end it "should consider a file loaded when asked for the name without an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "noext.rb") { loader.load(:noext) loader.class.should be_loaded("bar/noext") } } end it "should consider a file loaded when asked for the name with an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "withext.rb") { loader.load(:withext) loader.class.should be_loaded("bar/withext.rb") } } end it "should be able to load files directly from modules" do + ## modulepath can't be used until after app settings are initialized, so we need to simulate that: + Puppet.settings.expects(:app_defaults_initialized?).returns(true).at_least_once + modulepath = tmpfile("autoload_module_testing") libdir = File.join(modulepath, "mymod", "lib", "foo") FileUtils.mkdir_p(libdir) file = File.join(libdir, "plugin.rb") Puppet[:modulepath] = modulepath with_loader("foo", "foo") do |dir, loader| with_file(:plugin, file.split("/")) do loader.load(:plugin) loader.class.should be_loaded("foo/plugin.rb") end end end end diff --git a/spec/integration/util/settings_spec.rb b/spec/integration/util/settings_spec.rb index 46d783c4e..57babcca3 100755 --- a/spec/integration/util/settings_spec.rb +++ b/spec/integration/util/settings_spec.rb @@ -1,29 +1,42 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet_spec/files' describe Puppet::Util::Settings do include PuppetSpec::Files def minimal_default_settings { :noop => {:default => false, :desc => "noop"} } end it "should be able to make needed directories" do settings = Puppet::Util::Settings.new - settings.setdefaults :main, minimal_default_settings.update( :maindir => [tmpfile("main"), "a"] ) + settings.setdefaults :main, minimal_default_settings.update( + :maindir => { + :default => tmpfile("main"), + :type => :directory, + :desc => "a", + } + ) settings.use(:main) File.should be_directory(settings[:maindir]) end it "should make its directories with the correct modes" do settings = Puppet::Util::Settings.new - settings.setdefaults :main, minimal_default_settings.update( :maindir => {:default => tmpfile("main"), :desc => "a", :mode => 0750} ) + settings.setdefaults :main, minimal_default_settings.update( + :maindir => { + :default => tmpfile("main"), + :type => :directory, + :desc => "a", + :mode => 0750 + } + ) settings.use(:main) (File.stat(settings[:maindir]).mode & 007777).should == (Puppet.features.microsoft_windows? ? 0755 : 0750) end end diff --git a/spec/lib/puppet_spec/settings.rb b/spec/lib/puppet_spec/settings.rb new file mode 100644 index 000000000..9d71f38ca --- /dev/null +++ b/spec/lib/puppet_spec/settings.rb @@ -0,0 +1,10 @@ +module PuppetSpec::Settings + TEST_APP_DEFAULTS = { + :run_mode => :user, + :name => :apply, + :logdir => "/dev/null", + :confdir => "/dev/null", + :vardir => "/dev/null", + :rundir => "/dev/null", + } +end \ No newline at end of file diff --git a/spec/shared_behaviours/file_serving.rb b/spec/shared_behaviours/file_serving.rb index bc1d46ca5..7f35f8dc3 100755 --- a/spec/shared_behaviours/file_serving.rb +++ b/spec/shared_behaviours/file_serving.rb @@ -1,68 +1,72 @@ #!/usr/bin/env rspec shared_examples_for "Puppet::FileServing::Files" do it "should use the rest terminus when the 'puppet' URI scheme is used and a host name is present" do uri = "puppet://myhost/fakemod/my/file" # It appears that the mocking somehow interferes with the caching subsystem. # This mock somehow causes another terminus to get generated. term = @indirection.terminus(:rest) @indirection.stubs(:terminus).with(:rest).returns term term.expects(:find) @indirection.find(uri) end it "should use the rest terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is not 'puppet' or 'apply'" do uri = "puppet:///fakemod/my/file" Puppet.settings.stubs(:value).returns "foo" Puppet.settings.stubs(:value).with(:name).returns("puppetd") Puppet.settings.stubs(:value).with(:modulepath).returns("") @indirection.terminus(:rest).expects(:find) @indirection.find(uri) end + + # TODO: This test may no longer be relevant. It's testing for scenarios when settings[:name] is set to "puppet", + # and I am not aware of any code path where the Puppet.settings[:name] could still end up having that as a value. + # --cprice 2012-03-14 it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'puppet'" do uri = "puppet:///fakemod/my/file" Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil, :modulepath => [])) Puppet.settings.stubs(:value).returns "" - Puppet.settings.stubs(:value).with(:name).returns("puppet") + Puppet.settings.stubs(:value).with(:name).returns(:puppet) Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever") @indirection.terminus(:file_server).expects(:find) @indirection.terminus(:file_server).stubs(:authorized?).returns(true) @indirection.find(uri) end it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'apply'" do uri = "puppet:///fakemod/my/file" Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil, :modulepath => [])) Puppet.settings.stubs(:value).returns "" - Puppet.settings.stubs(:value).with(:name).returns("apply") + Puppet.settings.stubs(:value).with(:name).returns(:apply) Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever") @indirection.terminus(:file_server).expects(:find) @indirection.terminus(:file_server).stubs(:authorized?).returns(true) @indirection.find(uri) end it "should use the file terminus when the 'file' URI scheme is used" do uri = Puppet::Util.path_to_uri(File.expand_path('/fakemod/my/other file')) uri.scheme.should == 'file' @indirection.terminus(:file).expects(:find) @indirection.find(uri.to_s) end it "should use the file terminus when a fully qualified path is provided" do uri = File.expand_path("/fakemod/my/file") @indirection.terminus(:file).expects(:find) @indirection.find(uri) end it "should use the configuration to test whether the request is allowed" do uri = "fakemod/my/file" mount = mock 'mount' config = stub 'configuration', :split_path => [mount, "eh"] @indirection.terminus(:file_server).stubs(:configuration).returns config @indirection.terminus(:file_server).expects(:find) mount.expects(:allowed?).returns(true) @indirection.find(uri, :node => "foo", :ip => "bar") end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e367671b5..b2537269a 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,143 +1,161 @@ dir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift File.join(dir, 'lib') # Don't want puppet getting the command line arguments for rake or autotest ARGV.clear require 'puppet' require 'mocha' gem 'rspec', '>=2.0.0' require 'rspec/expectations' # So everyone else doesn't have to include this base constant. module PuppetSpec FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) end require 'pathname' require 'tmpdir' require 'puppet_spec/verbose' require 'puppet_spec/files' +require 'puppet_spec/settings' require 'puppet_spec/fixtures' require 'puppet_spec/matchers' require 'puppet_spec/database' require 'monkey_patches/alias_should_to_must' require 'monkey_patches/publicize_methods' Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| require behaviour.relative_path_from(Pathname.new(dir)) end RSpec.configure do |config| include PuppetSpec::Fixtures config.mock_with :mocha config.before :each do # Disabling garbage collection inside each test, and only running it at # the end of each block, gives us an ~ 15 percent speedup, and more on # some platforms *cough* windows *cough* that are a little slower. GC.disable # We need to preserve the current state of all our indirection cache and # terminus classes. This is pretty important, because changes to these # are global and lead to order dependencies in our testing. # # We go direct to the implementation because there is no safe, sane public # API to manage restoration of these to their default values. This # should, once the value is proved, be moved to a standard API on the # indirector. # # To make things worse, a number of the tests stub parts of the # indirector. These stubs have very specific expectations that what # little of the public API we could use is, well, likely to explode # randomly in some tests. So, direct access. --daniel 2011-08-30 $saved_indirection_state = {} indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state[indirector.name] = { :@terminus_class => indirector.instance_variable_get(:@terminus_class), :@cache_class => indirector.instance_variable_get(:@cache_class) } end # these globals are set by Application $puppet_application_mode = nil $puppet_application_name = nil # REVISIT: I think this conceals other bad tests, but I don't have time to # fully diagnose those right now. When you read this, please come tell me # I suck for letting this float. --daniel 2011-04-21 Signal.stubs(:trap) # Longer keys are secure, but they sure make for some slow testing - both # in terms of generating keys, and in terms of anything the next step down # the line doing validation or whatever. Most tests don't care how long # or secure it is, just that it exists, so these are better and faster # defaults, in testing only. # # I would make these even shorter, but OpenSSL doesn't support anything # below 512 bits. Sad, really, because a 0 bit key would be just fine. Puppet[:req_bits] = 512 Puppet[:keylength] = 512 - # Set the confdir and vardir to gibberish so that tests - # have to be correctly mocked. - Puppet[:confdir] = "/dev/null" - Puppet[:vardir] = "/dev/null" + # TODO cprice: revisit this; is there an advantage to calling set_value directly as opposed to calling + # "initialize_app_defaults"? Maybe it would allow us to prevent that method from getting called twice? + ### Set the confdir and vardir to gibberish so that tests + ### have to be correctly mocked. + ##Puppet.settings.initialize_app_defaults({ + ## :run_mode => :user, + ## :logdir => "/dev/null", + ## :confdir => "/dev/null", + ##}) + ###Puppet[:confdir] = "/dev/null" + ###Puppet[:vardir] = "/dev/null" + #Puppet.settings.set_value(:run_mode, :user, :application_defaults) + #Puppet.settings.set_value(:name, :apply, :application_defaults) + #Puppet.settings.set_value(:logdir, "/dev/null", :application_defaults) + #Puppet.settings.set_value(:confdir, "/dev/null", :application_defaults) + #Puppet.settings.set_value(:vardir, "/dev/null", :application_defaults) + #Puppet.settings.set_value(:rundir, "/dev/null", :application_defaults) + PuppetSpec::Settings::TEST_APP_DEFAULTS.each do |key, value| + Puppet.settings.set_value(key, value, :application_defaults) + end + # Avoid opening ports to the outside world Puppet.settings[:bindaddress] = "127.0.0.1" # We don't want to depend upon the reported domain name of the # machine running the tests, nor upon the DNS setup of that # domain. Puppet.settings[:use_srv_records] = false @logs = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) @log_level = Puppet::Util::Log.level end config.after :each do Puppet.settings.send(:clear_for_tests) Puppet::Node::Environment.clear Puppet::Util::Storage.clear Puppet::Util::ExecutionStub.reset PuppetSpec::Files.cleanup @logs.clear Puppet::Util::Log.close_all Puppet::Util::Log.level = @log_level Puppet.clear_deprecation_warnings # uncommenting and manipulating this can be useful when tracking down calls to deprecated code #Puppet.log_deprecations_to_file("deprecations.txt", /^Puppet::Util.exec/) # Restore the indirector configuration. See before hook. indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state.fetch(indirector.name, {}).each do |variable, value| indirector.instance_variable_set(variable, value) end end $saved_indirection_state = {} # Some tests can cause us to connect, in which case the lingering # connection is a resource that can cause unexpected failure in later # tests, as well as sharing state accidentally. # We're testing if ActiveRecord::Base is defined because some test cases # may stub Puppet.features.rails? which is how we should normally # introspect for this functionality. ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base) # This will perform a GC between tests, but only if actually required. We # experimented with forcing a GC run, and that was less efficient than # just letting it run all the time. GC.enable end end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 9faba42db..0dce2cac6 100755 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -1,554 +1,575 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application' require 'puppet' require 'getoptlong' describe Puppet::Application do before do Puppet::Util::Instrumentation.stubs(:init) @app = Class.new(Puppet::Application).new @appclass = @app.class @app.stubs(:name).returns("test_app") # avoid actually trying to parse any settings Puppet.settings.stubs(:parse) + + #@app.stubs(:get_app_defaults).returns({ + # :run_mode => :user, + # :logdir => "/dev/null", + # :confdir => "/dev/null", + # :vardir => "/dev/null", + #}) + end + + describe "application defaults" do + it "should fail if required app default values are missing" do + @app.stubs(:app_defaults).returns({ :foo => 'bar' }) + Puppet.expects(:err).with(regexp_matches(/missing required app default setting/)) + expect { + @app.run + }.to exit_with 1 + end end describe "finding" do before do @klass = Puppet::Application @klass.stubs(:puts) end it "should find classes in the namespace" do @klass.find("Agent").should == @klass::Agent end it "should not find classes outside the namespace", :'fails_on_ruby_1.9.2' => true do expect { @klass.find("String") }.to exit_with 1 end it "should exit if it can't find a class" do reg = "Unable to find application 'ThisShallNeverEverEverExist'. " reg += "no such file to load -- puppet/application/thisshallneverevereverexist" @klass.expects(:puts).with(reg) expect { @klass.find("ThisShallNeverEverEverExist") }.to exit_with 1 end it "#12114: should prevent File namespace collisions" do # have to require the file face once, then the second time around it would fail @klass.find("File").should == Puppet::Application::File @klass.find("File").should == Puppet::Application::File end end describe ".run_mode" do it "should default to user" do @appclass.run_mode.name.should == :user end it "should set and get a value" do @appclass.run_mode :agent @appclass.run_mode.name.should == :agent end end - # TODO cprice: look into fixing this - - it "should sadly and frighteningly allow run_mode to change at runtime" do - class TestApp < Puppet::Application - run_mode :master - def run_command - # This is equivalent to calling these methods externally to the - # instance, but since this is what "real world" code is likely to do - # (and we need the class anyway) we may as well test that. --daniel 2011-02-03 - set_run_mode self.class.run_mode "agent" - end - end - - Puppet.features.stubs(:syslog?).returns(true) - Puppet[:run_mode].should == "user" - - expect { - app = TestApp.new - - Puppet[:run_mode].should == "master" - - app.run - - app.class.run_mode.name.should == :agent - $puppet_application_mode.name.should == :agent - }.should_not raise_error - - Puppet[:run_mode].should == "agent" - end - - # TODO cprice: look into fixing this - - it "it should not allow run mode to be set multiple times" do - pending "great floods of tears, you can do this right now" # --daniel 2011-02-03 - app = Puppet::Application.new - expect { - app.set_run_mode app.class.run_mode "master" - $puppet_application_mode.name.should == :master - app.set_run_mode app.class.run_mode "agent" - $puppet_application_mode.name.should == :agent - }.should raise_error - end - - # TODO cprice: look into fixing this - - it "should explode when an invalid run mode is set at runtime, for great victory" - # ...but you can, and while it will explode, that only happens too late for - # us to easily test. --daniel 2011-02-03 + # TODO cprice: look into fixing these + + #it "should sadly and frighteningly allow run_mode to change at runtime" do + # class TestApp < Puppet::Application + # run_mode :master + # def run_command + # # This is equivalent to calling these methods externally to the + # # instance, but since this is what "real world" code is likely to do + # # (and we need the class anyway) we may as well test that. --daniel 2011-02-03 + # set_run_mode self.class.run_mode "agent" + # end + # end + # + # Puppet.features.stubs(:syslog?).returns(true) + # Puppet[:run_mode].should == "user" + # + # expect { + # app = TestApp.new + # + # Puppet[:run_mode].should == "master" + # + # app.run + # + # app.class.run_mode.name.should == :agent + # $puppet_application_mode.name.should == :agent + # }.should_not raise_error + # + # Puppet[:run_mode].should == "agent" + #end + # + ## TODO cprice: look into fixing this + # + #it "it should not allow run mode to be set multiple times" do + # pending "great floods of tears, you can do this right now" # --daniel 2011-02-03 + # app = Puppet::Application.new + # expect { + # app.set_run_mode app.class.run_mode "master" + # $puppet_application_mode.name.should == :master + # app.set_run_mode app.class.run_mode "agent" + # $puppet_application_mode.name.should == :agent + # }.should raise_error + #end + # + ## TODO cprice: look into fixing this + # + #it "should explode when an invalid run mode is set at runtime, for great victory" + ## ...but you can, and while it will explode, that only happens too late for + ## us to easily test. --daniel 2011-02-03 it "should have a run entry-point" do @app.should respond_to(:run) end it "should have a read accessor to options" do @app.should respond_to(:options) end it "should include a default setup method" do @app.should respond_to(:setup) end it "should include a default preinit method" do @app.should respond_to(:preinit) end it "should include a default run_command method" do @app.should respond_to(:run_command) end it "should invoke main as the default" do @app.expects( :main ) @app.run_command end - it "should initialize the Puppet Instrumentation layer on creation" do - Puppet::Util::Instrumentation.expects(:init) - Class.new(Puppet::Application).new + it "should initialize the Puppet Instrumentation layer early in the life cycle" do + startup_sequence = sequence('startup') + @app.expects(:initialize_app_defaults).in_sequence(startup_sequence) + Puppet::Util::Instrumentation.expects(:init).in_sequence(startup_sequence) + @app.expects(:preinit).in_sequence(startup_sequence) + + expect { @app.run }.to exit_with(1) end describe 'when invoking clear!' do before :each do Puppet::Application.run_status = :stop_requested Puppet::Application.clear! end it 'should have nil run_status' do Puppet::Application.run_status.should be_nil end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return false for interrupted?' do Puppet::Application.interrupted?.should be_false end it 'should return true for clear?' do Puppet::Application.clear?.should be_true end end describe 'after invoking stop!' do before :each do Puppet::Application.run_status = nil Puppet::Application.stop! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :stop_requested' do Puppet::Application.run_status.should == :stop_requested end it 'should return true for stop_requested?' do Puppet::Application.stop_requested?.should be_true end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when invoking restart!' do before :each do Puppet::Application.run_status = nil Puppet::Application.restart! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :restart_requested' do Puppet::Application.run_status.should == :restart_requested end it 'should return true for restart_requested?' do Puppet::Application.restart_requested?.should be_true end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when performing a controlled_run' do it 'should not execute block if not :clear?' do Puppet::Application.run_status = :stop_requested target = mock 'target' target.expects(:some_method).never Puppet::Application.controlled_run do target.some_method end end it 'should execute block if :clear?' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once Puppet::Application.controlled_run do target.some_method end end describe 'on POSIX systems', :if => Puppet.features.posix? do it 'should signal process with HUP after block if restart requested during block execution', :'fails_on_ruby_1.9.2' => true do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once old_handler = trap('HUP') { target.some_method } begin Puppet::Application.controlled_run do Puppet::Application.run_status = :restart_requested end ensure trap('HUP', old_handler) end end end after :each do Puppet::Application.run_status = nil end end describe "when parsing command-line options" do before :each do @app.command_line.stubs(:args).returns([]) Puppet.settings.stubs(:optparse_addargs).returns([]) end it "should pass the banner to the option parser" do option_parser = stub "option parser" option_parser.stubs(:on) option_parser.stubs(:parse!) @app.class.instance_eval do banner "banner" end OptionParser.expects(:new).with("banner").returns(option_parser) @app.parse_options end it "should ask OptionParser to parse the command-line argument" do @app.command_line.stubs(:args).returns(%w{ fake args }) OptionParser.any_instance.expects(:parse!).with(%w{ fake args }) @app.parse_options end describe "when using --help" do it "should call exit" do @app.stubs(:puts) expect { @app.handle_help(nil) }.to exit_with 0 end end describe "when using --version" do it "should declare a version option" do @app.should respond_to(:handle_version) end it "should exit after printing the version" do @app.stubs(:puts) expect { @app.handle_version(nil) }.to exit_with 0 end end describe "when dealing with an argument not declared directly by the application" do it "should pass it to handle_unknown if this method exists" do Puppet.settings.stubs(:optparse_addargs).returns([["--not-handled", :REQUIRED]]) @app.expects(:handle_unknown).with("--not-handled", "value").returns(true) @app.command_line.stubs(:args).returns(["--not-handled", "value"]) @app.parse_options end it "should transform boolean option to normal form for Puppet.settings" do @app.expects(:handle_unknown).with("--option", true) @app.send(:handlearg, "--[no-]option", true) end it "should transform boolean option to no- form for Puppet.settings" do @app.expects(:handle_unknown).with("--no-option", false) @app.send(:handlearg, "--[no-]option", false) end end end describe "when calling default setup" do before :each do @app.options.stubs(:[]) end [ :debug, :verbose ].each do |level| it "should honor option #{level}" do @app.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.stubs(:newdestination) @app.setup Puppet::Util::Log.level.should == (level == :verbose ? :info : :debug) end end it "should honor setdest option" do @app.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:setup_default) @app.setup end end describe "when configuring routes" do include PuppetSpec::Files before :each do Puppet::Node.indirection.reset_terminus_class end after :each do Puppet::Node.indirection.reset_terminus_class end it "should use the routes specified for only the active application" do Puppet[:route_file] = tmpfile('routes') File.open(Puppet[:route_file], 'w') do |f| f.print <<-ROUTES test_app: node: terminus: exec other_app: node: terminus: plain catalog: terminus: invalid ROUTES end @app.configure_indirector_routes Puppet::Node.indirection.terminus_class.should == 'exec' end it "should not fail if the route file doesn't exist" do Puppet[:route_file] = "/dev/null/non-existent" expect { @app.configure_indirector_routes }.should_not raise_error end it "should raise an error if the routes file is invalid" do Puppet[:route_file] = tmpfile('routes') File.open(Puppet[:route_file], 'w') do |f| f.print <<-ROUTES invalid : : yaml ROUTES end expect { @app.configure_indirector_routes }.should raise_error end end describe "when running" do before :each do @app.stubs(:preinit) @app.stubs(:setup) @app.stubs(:parse_options) end it "should call preinit" do @app.stubs(:run_command) @app.expects(:preinit) @app.run end it "should call parse_options" do @app.stubs(:run_command) @app.expects(:parse_options) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call main as the default command" do @app.expects(:main) @app.run end it "should warn and exit if no command can be called" do Puppet.expects(:err) expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns no command" do @app.stubs(:get_command).returns(nil) Puppet.expects(:err) expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns an invalid command" do @app.stubs(:get_command).returns(:this_function_doesnt_exist) Puppet.expects(:err) expect { @app.run }.to exit_with 1 end end describe "when metaprogramming" do describe "when calling option" do it "should create a new method named after the option" do @app.class.option("--test1","-t") do end @app.should respond_to(:handle_test1) end it "should transpose in option name any '-' into '_'" do @app.class.option("--test-dashes-again","-t") do end @app.should respond_to(:handle_test_dashes_again) end it "should create a new method called handle_test2 with option(\"--[no-]test2\")" do @app.class.option("--[no-]test2","-t") do end @app.should respond_to(:handle_test2) end describe "when a block is passed" do it "should create a new method with it" do @app.class.option("--[no-]test2","-t") do raise "I can't believe it, it works!" end lambda { @app.handle_test2 }.should raise_error end it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with { |*arg| arg[0] == "--[no-]test3" } @app.class.option("--[no-]test3","-t") do end @app.parse_options end it "should pass a block that calls our defined method" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with('--test4','-t').yields(nil) @app.expects(:send).with(:handle_test4, nil) @app.class.option("--test4","-t") do end @app.parse_options end end describe "when no block is given" do it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with("--test4","-t") @app.class.option("--test4","-t") @app.parse_options end it "should give to OptionParser a block that adds the the value to the options array" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with("--test4","-t").yields(nil) @app.options.expects(:[]=).with(:test4,nil) @app.class.option("--test4","-t") @app.parse_options end end end end end diff --git a/spec/unit/configurer/plugin_handler_spec.rb b/spec/unit/configurer/plugin_handler_spec.rb index 9ffb1ce1a..f2c534c52 100755 --- a/spec/unit/configurer/plugin_handler_spec.rb +++ b/spec/unit/configurer/plugin_handler_spec.rb @@ -1,57 +1,60 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/configurer' require 'puppet/configurer/plugin_handler' class PluginHandlerTester include Puppet::Configurer::PluginHandler end describe Puppet::Configurer::PluginHandler do before do @pluginhandler = PluginHandlerTester.new # PluginHandler#load_plugin has an extra-strong rescue clause # this mock is to make sure that we don't silently ignore errors Puppet.expects(:err).never end it "should have a method for downloading plugins" do @pluginhandler.should respond_to(:download_plugins) end it "should have a boolean method for determining whether plugins should be downloaded" do @pluginhandler.should respond_to(:download_plugins?) end it "should download plugins when :pluginsync is true" do Puppet.settings.expects(:value).with(:pluginsync).returns true @pluginhandler.should be_download_plugins end it "should not download plugins when :pluginsync is false" do Puppet.settings.expects(:value).with(:pluginsync).returns false @pluginhandler.should_not be_download_plugins end it "should not download plugins when downloading is disabled" do Puppet::Configurer::Downloader.expects(:new).never @pluginhandler.expects(:download_plugins?).returns false @pluginhandler.download_plugins end it "should use an Agent Downloader, with the name, source, destination, and ignore set correctly, to download plugins when downloading is enabled" do downloader = mock 'downloader' + # This is needed in order to make sure we pass on windows + plugindest = File.expand_path("/tmp/pdest") + Puppet[:pluginsource] = "psource" - Puppet[:plugindest] = "pdest" + Puppet[:plugindest] = plugindest Puppet[:pluginsignore] = "pignore" - Puppet::Configurer::Downloader.expects(:new).with("plugin", "pdest", "psource", "pignore").returns downloader + Puppet::Configurer::Downloader.expects(:new).with("plugin", plugindest, "psource", "pignore").returns downloader downloader.expects(:evaluate).returns [] @pluginhandler.expects(:download_plugins?).returns true @pluginhandler.download_plugins end end diff --git a/spec/unit/face/instrumentation_probe.rb b/spec/unit/face/instrumentation_probe_spec.rb similarity index 100% rename from spec/unit/face/instrumentation_probe.rb rename to spec/unit/face/instrumentation_probe_spec.rb diff --git a/spec/unit/file_serving/indirection_hooks_spec.rb b/spec/unit/file_serving/indirection_hooks_spec.rb index 7bd713e78..93e815e9f 100755 --- a/spec/unit/file_serving/indirection_hooks_spec.rb +++ b/spec/unit/file_serving/indirection_hooks_spec.rb @@ -1,59 +1,59 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/file_serving/indirection_hooks' describe Puppet::FileServing::IndirectionHooks do before do @object = Object.new @object.extend(Puppet::FileServing::IndirectionHooks) @request = stub 'request', :key => "mymod/myfile", :options => {:node => "whatever"}, :server => nil, :protocol => nil end describe "when being used to select termini" do it "should return :file if the request key is fully qualified" do @request.expects(:key).returns File.expand_path('/foo') @object.select_terminus(@request).should == :file end it "should return :file if the URI protocol is set to 'file'" do @request.expects(:protocol).returns "file" @object.select_terminus(@request).should == :file end it "should fail when a protocol other than :puppet or :file is used" do @request.stubs(:protocol).returns "http" proc { @object.select_terminus(@request) }.should raise_error(ArgumentError) end describe "and the protocol is 'puppet'" do before do @request.stubs(:protocol).returns "puppet" end it "should choose :rest when a server is specified" do @request.stubs(:protocol).returns "puppet" @request.expects(:server).returns "foo" @object.select_terminus(@request).should == :rest end # This is so a given file location works when bootstrapping with no server. it "should choose :rest when the Settings name isn't 'puppet'" do @request.stubs(:protocol).returns "puppet" @request.stubs(:server).returns "foo" Puppet.settings.stubs(:value).with(:name).returns "foo" @object.select_terminus(@request).should == :rest end it "should choose :file_server when the settings name is 'puppet' and no server is specified" do modules = mock 'modules' @request.expects(:protocol).returns "puppet" @request.expects(:server).returns nil - Puppet.settings.expects(:value).with(:name).returns "puppet" + Puppet.settings.expects(:value).with(:name).returns :puppet @object.select_terminus(@request).should == :file_server end end end end diff --git a/spec/unit/indirector/facts/network_device_spec.rb b/spec/unit/indirector/facts/network_device_spec.rb index 93cd35d77..0ad2ca071 100755 --- a/spec/unit/indirector/facts/network_device_spec.rb +++ b/spec/unit/indirector/facts/network_device_spec.rb @@ -1,85 +1,86 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'puppet/util/network_device' require 'puppet/indirector/facts/network_device' describe Puppet::Node::Facts::NetworkDevice do it "should be a subclass of the Code terminus" do Puppet::Node::Facts::NetworkDevice.superclass.should equal(Puppet::Indirector::Code) end it "should have documentation" do Puppet::Node::Facts::NetworkDevice.doc.should_not be_nil end it "should be registered with the configuration store indirection" do indirection = Puppet::Indirector::Indirection.instance(:facts) Puppet::Node::Facts::NetworkDevice.indirection.should equal(indirection) end it "should have its name set to :facter" do Puppet::Node::Facts::NetworkDevice.name.should == :network_device end end describe Puppet::Node::Facts::NetworkDevice do before :each do @remote_device = stub 'remote_device', :facts => {} Puppet::Util::NetworkDevice.stubs(:current).returns(@remote_device) @device = Puppet::Node::Facts::NetworkDevice.new @name = "me" @request = stub 'request', :key => @name end describe Puppet::Node::Facts::NetworkDevice, " when finding facts" do it "should return a Facts instance" do @device.find(@request).should be_instance_of(Puppet::Node::Facts) end it "should return a Facts instance with the provided key as the name" do @device.find(@request).name.should == @name end it "should return the device facts as the values in the Facts instance" do @remote_device.expects(:facts).returns("one" => "two") facts = @device.find(@request) facts.values["one"].should == "two" end it "should add local facts" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:add_local_facts) @device.find(@request) end it "should convert all facts into strings" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:stringify) @device.find(@request) end it "should call the downcase hook" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:downcase_if_necessary) @device.find(@request) end end describe Puppet::Node::Facts::NetworkDevice, " when saving facts" do it "should fail" do proc { @device.save(@facts) }.should raise_error(Puppet::DevError) end end describe Puppet::Node::Facts::NetworkDevice, " when destroying facts" do it "should fail" do proc { @device.destroy(@facts) }.should raise_error(Puppet::DevError) end end end diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index 019d3a915..8310ee08f 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -1,264 +1,265 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/webrick' describe Puppet::Network::HTTP::WEBrick, "after initializing" do it "should not be listening" do Puppet::Network::HTTP::WEBrick.new.should_not be_listening end end describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :protocols => [ :rest ] } end it "should fail if already listening" do @server.listen(@listen_params) Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError) end it "should require a listening address to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError) end it "should require a listening port to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError) end it "should order a webrick server to start in a separate thread" do @mock_webrick.expects(:start) # If you remove this you'll sometimes get race condition problems Thread.expects(:new).yields @server.listen(@listen_params) end it "should tell webrick to listen on the specified address and port" do WEBrick::HTTPServer.expects(:new).with {|args| args[:Port] == 31337 and args[:BindAddress] == "127.0.0.1" }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure a logger for webrick" do @server.expects(:setup_logger).returns(:Logger => :mylogger) WEBrick::HTTPServer.expects(:new).with {|args| args[:Logger] == :mylogger }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure SSL for webrick" do @server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) WEBrick::HTTPServer.expects(:new).with {|args| args[:Ssl] == :testing and args[:Other] == :yay }.returns(@mock_webrick) @server.listen(@listen_params) end it "should be listening" do @server.listen(@listen_params) @server.should be_listening end describe "when the REST protocol is requested" do it "should register the REST handler at /" do # We don't care about the options here. @mock_webrick.expects(:mount).with { |path, klass, options| path == "/" and klass == Puppet::Network::HTTP::WEBrickREST } @server.listen(@listen_params.merge(:protocols => [:rest])) end end end describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail unless listening" do Proc.new { @server.unlisten }.should raise_error(RuntimeError) end it "should order webrick server to stop" do @mock_webrick.expects(:shutdown) @server.listen(@listen_params) @server.unlisten end it "should no longer be listening" do @server.listen(@listen_params) @server.unlisten @server.should_not be_listening end end describe Puppet::Network::HTTP::WEBrick do before do @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new end describe "when configuring an http logger" do before do Puppet.settings.stubs(:value).returns "something" Puppet.settings.stubs(:use) @filehandle = stub 'handle', :fcntl => nil, :sync= => nil File.stubs(:open).returns @filehandle end it "should use the settings for :main, :ssl, and the process name" do - Puppet.settings.stubs(:value).with(:name).returns "myname" - Puppet.settings.expects(:use).with(:main, :ssl, "myname") + #Puppet.settings.stubs(:value).with(:name).returns "myname" + #Puppet.settings.expects(:use).with(:main, :ssl, "myname") + Puppet.settings.expects(:use).with(:main, :ssl, :application) @server.setup_logger end it "should use the masterlog if the run_mode is master" do Puppet.run_mode.stubs(:master?).returns(true) Puppet.settings.expects(:value).with(:masterhttplog).returns "/master/log" File.expects(:open).with("/master/log", "a+").returns @filehandle @server.setup_logger end it "should use the httplog if the run_mode is not master" do Puppet.run_mode.stubs(:master?).returns(false) Puppet.settings.expects(:value).with(:httplog).returns "/other/log" File.expects(:open).with("/other/log", "a+").returns @filehandle @server.setup_logger end describe "and creating the logging filehandle" do it "should set the close-on-exec flag if supported" do if defined? Fcntl::FD_CLOEXEC @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) else @filehandle.expects(:fcntl).never end @server.setup_logger end it "should sync the filehandle" do @filehandle.expects(:sync=).with(true) @server.setup_logger end end it "should create a new WEBrick::Log instance with the open filehandle" do WEBrick::Log.expects(:new).with(@filehandle) @server.setup_logger end it "should set debugging if the current loglevel is :debug" do Puppet::Util::Log.expects(:level).returns :debug WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } @server.setup_logger end it "should return the logger as the main log" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:Logger].should == logger end it "should return the logger as the access log using both the Common and Referer log format" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:AccessLog].should == [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] ] end end describe "when configuring ssl" do before do @key = stub 'key', :content => "mykey" @cert = stub 'cert', :content => "mycert" @host = stub 'host', :key => @key, :certificate => @cert, :name => "yay", :ssl_store => "mystore" Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns @cert Puppet::SSL::Host.stubs(:localhost).returns @host end it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns @host @host.expects(:key).returns @key @server.setup_ssl[:SSLPrivateKey].should == "mykey" end it "should configure the certificate" do @server.setup_ssl[:SSLCertificate].should == "mycert" end it "should fail if no CA certificate can be found" do Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns nil lambda { @server.setup_ssl }.should raise_error(Puppet::Error) end it "should specify the path to the CA certificate" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns 'false' Puppet.settings.stubs(:value).with(:localcacert).returns '/ca/crt' @server.setup_ssl[:SSLCACertificateFile].should == "/ca/crt" end it "should start ssl immediately" do @server.setup_ssl[:SSLStartImmediately].should be_true end it "should enable ssl" do @server.setup_ssl[:SSLEnable].should be_true end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do @server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER end it "should add an x509 store" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns '/my/crl' @host.expects(:ssl_store).returns "mystore" @server.setup_ssl[:SSLCertificateStore].should == "mystore" end it "should set the certificate name to 'nil'" do @server.setup_ssl[:SSLCertName].should be_nil end end end diff --git a/spec/unit/network/server_spec.rb b/spec/unit/network/server_spec.rb index 75d70ee13..8da5cff60 100755 --- a/spec/unit/network/server_spec.rb +++ b/spec/unit/network/server_spec.rb @@ -1,409 +1,409 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' describe Puppet::Network::Server do before do @mock_http_server_class = mock('http server class') Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).with(:name).returns("me") Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns(8140) Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) end describe "when initializing" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns('') end it 'should fail if an unknown option is provided' do lambda { Puppet::Network::Server.new(:foo => 31337) }.should raise_error(ArgumentError) end it "should allow specifying a listening port" do Puppet.settings.stubs(:value).with(:bindaddress).returns('') @server = Puppet::Network::Server.new(:port => 31337) @server.port.should == 31337 end it "should use the :bindaddress setting to determine the default listening address" do Puppet.settings.stubs(:value).with(:masterport).returns('') Puppet.settings.expects(:value).with(:bindaddress).returns("10.0.0.1") @server = Puppet::Network::Server.new @server.address.should == "10.0.0.1" end it "should set the bind address to '127.0.0.1' if the default address is an empty string and the server type is mongrel" do Puppet.settings.stubs(:value).with(:servertype).returns("mongrel") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '127.0.0.1' end it "should set the bind address to '0.0.0.0' if the default address is an empty string and the server type is webrick" do Puppet.settings.stubs(:value).with(:servertype).returns("webrick") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '0.0.0.0' end it "should use the Puppet configurator to find a default listening port" do Puppet.settings.stubs(:value).with(:bindaddress).returns('') Puppet.settings.expects(:value).with(:masterport).returns(6667) @server = Puppet::Network::Server.new @server.port.should == 6667 end it "should fail to initialize if no listening port can be found" do Puppet.settings.stubs(:value).with(:bindaddress).returns("127.0.0.1") Puppet.settings.stubs(:value).with(:masterport).returns(nil) lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) end it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do Puppet.settings.expects(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) @server.server_type.should == :suparserver end it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do Puppet.settings.expects(:value).with(:servertype).returns(nil) lambda { Puppet::Network::Server.new(:port => 31337) }.should raise_error end it "should ask the Puppet::Network::HTTP class to fetch the proper HTTP server class" do Puppet::Network::HTTP.expects(:server_class_by_type).with(:suparserver).returns(@mock_http_server_class) @server = Puppet::Network::Server.new(:port => 31337) end it "should fail if the HTTP server class is unknown" do Puppet::Network::HTTP.stubs(:server_class_by_type).returns(nil) lambda { Puppet::Network::Server.new(:port => 31337) }.should raise_error(ArgumentError) end it "should allow registering REST handlers" do @server = Puppet::Network::Server.new(:port => 31337, :handlers => [ :foo, :bar, :baz]) lambda { @server.unregister(:foo, :bar, :baz) }.should_not raise_error end it "should not be listening after initialization" do Puppet::Network::Server.new(:port => 31337).should_not be_listening end it "should use the :main setting section" do Puppet.settings.expects(:use).with { |*args| args.include?(:main) } @server = Puppet::Network::Server.new(:port => 31337) end - it "should use the Puppet[:name] setting section" do - Puppet.settings.expects(:value).with(:name).returns "me" - Puppet.settings.expects(:use).with { |*args| args.include?("me") } + it "should use the :application setting section" do + #Puppet.settings.expects(:value).with(:name).returns "me" + Puppet.settings.expects(:use).with { |*args| args.include?(:application) } @server = Puppet::Network::Server.new(:port => 31337) end end # We don't test the method, because it's too much of a Unix-y pain. it "should be able to daemonize" do @server.should respond_to(:daemonize) end describe "when being started" do before do @server.stubs(:listen) @server.stubs(:create_pidfile) end it "should listen" do @server.expects(:listen) @server.start end it "should create its PID file" do @server.expects(:create_pidfile) @server.start end end describe "when being stopped" do before do @server.stubs(:unlisten) @server.stubs(:remove_pidfile) end it "should unlisten" do @server.expects(:unlisten) @server.stop end it "should remove its PID file" do @server.expects(:remove_pidfile) @server.stop end end describe "when creating its pidfile" do it "should use an exclusive mutex" do Puppet.settings.expects(:value).with(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",Sync::EX) @server.create_pidfile end it "should lock the pidfile using the Pidlock class" do pidfile = mock 'pidfile' Puppet.settings.stubs(:value).with(:name).returns "eh" Puppet.settings.expects(:value).with(:pidfile).returns "/my/file" Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile pidfile.expects(:lock).returns true @server.create_pidfile end it "should fail if it cannot lock" do pidfile = mock 'pidfile' Puppet.settings.stubs(:value).with(:name).returns "eh" Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile pidfile.expects(:lock).returns false lambda { @server.create_pidfile }.should raise_error end end describe "when removing its pidfile" do it "should use an exclusive mutex" do Puppet.settings.expects(:value).with(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",Sync::EX) @server.remove_pidfile end it "should do nothing if the pidfile is not present" do pidfile = mock 'pidfile', :unlock => false Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" @server.remove_pidfile end it "should unlock the pidfile using the Pidlock class" do pidfile = mock 'pidfile', :unlock => true Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" @server.remove_pidfile end end describe "when managing indirection registrations" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') end it "should allow registering an indirection for client access by specifying its indirection name" do lambda { @server.register(:foo) }.should_not raise_error end it "should require that the indirection be valid" do Puppet::Indirector::Indirection.expects(:model).with(:foo).returns nil lambda { @server.register(:foo) }.should raise_error(ArgumentError) end it "should require at least one indirection name when registering indirections for client access" do lambda { @server.register }.should raise_error(ArgumentError) end it "should allow for numerous indirections to be registered at once for client access" do lambda { @server.register(:foo, :bar, :baz) }.should_not raise_error end it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do @server.register(:foo) lambda { @server.unregister(:foo) }.should_not raise_error end it "should leave other indirections accessible to clients when turning off indirections" do @server.register(:foo, :bar) @server.unregister(:foo) lambda { @server.unregister(:bar)}.should_not raise_error end it "should allow specifying numerous indirections which are to be no longer accessible to clients" do @server.register(:foo, :bar) lambda { @server.unregister(:foo, :bar) }.should_not raise_error end it "should not turn off any indirections if given unknown indirection names to turn off" do @server.register(:foo, :bar) lambda { @server.unregister(:foo, :bar, :baz) }.should raise_error(ArgumentError) lambda { @server.unregister(:foo, :bar) }.should_not raise_error end it "should not allow turning off unknown indirection names" do @server.register(:foo, :bar) lambda { @server.unregister(:baz) }.should raise_error(ArgumentError) end it "should disable client access immediately when turning off indirections" do @server.register(:foo, :bar) @server.unregister(:foo) lambda { @server.unregister(:foo) }.should raise_error(ArgumentError) end it "should allow turning off all indirections at once" do @server.register(:foo, :bar) @server.unregister [ :foo, :bar, :baz].each do |indirection| lambda { @server.unregister(indirection) }.should raise_error(ArgumentError) end end end it "should provide a means of determining whether it is listening" do @server.should respond_to(:listening?) end it "should provide a means of determining which HTTP server will be used to provide access to clients" do @server.server_type.should == :suparserver end it "should provide a means of determining the listening address" do @server.address.should == "127.0.0.1" end it "should provide a means of determining the listening port" do @server.port.should == 31337 end it "should allow for multiple configurations, each handling different indirections" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server2 = Puppet::Network::Server.new(:port => 31337) @server.register(:foo, :bar) @server2.register(:foo, :xyzzy) @server.unregister(:foo, :bar) @server2.unregister(:foo, :xyzzy) lambda { @server.unregister(:xyzzy) }.should raise_error(ArgumentError) lambda { @server2.unregister(:bar) }.should raise_error(ArgumentError) end describe "when listening is off" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @server.stubs(:http_server).returns(@mock_http_server) end it "should indicate that it is not listening" do @server.should_not be_listening end it "should not allow listening to be turned off" do lambda { @server.unlisten }.should raise_error(RuntimeError) end it "should allow listening to be turned on" do lambda { @server.listen }.should_not raise_error end end describe "when listening is on" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @mock_http_server.stubs(:unlisten) @server.stubs(:http_server).returns(@mock_http_server) @server.listen end it "should indicate that it is listening" do @server.should be_listening end it "should not allow listening to be turned on" do lambda { @server.listen }.should raise_error(RuntimeError) end it "should allow listening to be turned off" do lambda { @server.unlisten }.should_not raise_error end end describe "when listening is being turned on" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server = Puppet::Network::Server.new(:port => 31337, :handlers => [:node]) @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) end it "should fetch an instance of an HTTP server" do @server.stubs(:http_server_class).returns(@mock_http_server_class) @mock_http_server_class.expects(:new).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to listen" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen) @server.listen end it "should pass the listening address to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:address] == '127.0.0.1' end @server.listen end it "should pass the listening port to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:port] == 31337 end @server.listen end it "should pass a list of REST handlers to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:handlers] == [ :node ] end @server.listen end end describe "when listening is being turned off" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @server.stubs(:http_server).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to stop listening" do @mock_http_server.expects(:unlisten) @server.unlisten end it "should not allow for indirections to be turned off" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server.register(:foo) lambda { @server.unregister(:foo) }.should raise_error(RuntimeError) end end end diff --git a/spec/unit/util/autoload_spec.rb b/spec/unit/util/autoload_spec.rb index f39cee654..587d54796 100755 --- a/spec/unit/util/autoload_spec.rb +++ b/spec/unit/util/autoload_spec.rb @@ -1,219 +1,222 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/autoload' describe Puppet::Util::Autoload do before do @autoload = Puppet::Util::Autoload.new("foo", "tmp") @autoload.stubs(:eachdir).yields "/my/dir" @loaded = {} @autoload.class.stubs(:loaded).returns(@loaded) end describe "when building the search path" do before :each do + ## modulepath/libdir can't be used until after app settings are initialized, so we need to simulate that: + Puppet.settings.expects(:app_defaults_initialized?).returns(true).at_least_once + @dira = File.expand_path('/a') @dirb = File.expand_path('/b') @dirc = File.expand_path('/c') end it "should collect all of the plugins and lib directories that exist in the current environment's module path" do Puppet.settings.expects(:value).with(:environment).returns "foo" Puppet.settings.expects(:value).with(:modulepath, :foo).returns "#{@dira}#{File::PATH_SEPARATOR}#{@dirb}#{File::PATH_SEPARATOR}#{@dirc}" Dir.expects(:entries).with(@dira).returns %w{one two} Dir.expects(:entries).with(@dirb).returns %w{one two} FileTest.stubs(:directory?).returns false FileTest.expects(:directory?).with(@dira).returns true FileTest.expects(:directory?).with(@dirb).returns true ["#{@dira}/one/plugins", "#{@dira}/two/lib", "#{@dirb}/one/plugins", "#{@dirb}/two/lib"].each do |d| FileTest.expects(:directory?).with(d).returns true end @autoload.class.module_directories.should == ["#{@dira}/one/plugins", "#{@dira}/two/lib", "#{@dirb}/one/plugins", "#{@dirb}/two/lib"] end it "should not look for lib directories in directories starting with '.'" do Puppet.settings.expects(:value).with(:environment).returns "foo" Puppet.settings.expects(:value).with(:modulepath, :foo).returns @dira Dir.expects(:entries).with(@dira).returns %w{. ..} FileTest.expects(:directory?).with(@dira).returns true FileTest.expects(:directory?).with("#{@dira}/./lib").never FileTest.expects(:directory?).with("#{@dira}/./plugins").never FileTest.expects(:directory?).with("#{@dira}/../lib").never FileTest.expects(:directory?).with("#{@dira}/../plugins").never @autoload.class.module_directories end it "should include the module directories, the Puppet libdir, and all of the Ruby load directories" do Puppet[:libdir] = %w{/libdir1 /lib/dir/two /third/lib/dir}.join(File::PATH_SEPARATOR) @autoload.class.expects(:module_directories).returns %w{/one /two} @autoload.class.search_directories.should == %w{/one /two} + Puppet[:libdir].split(File::PATH_SEPARATOR) + $LOAD_PATH end end describe "when loading a file" do before do @autoload.class.stubs(:search_directories).returns %w{/a} FileTest.stubs(:directory?).returns true @time_a = Time.utc(2010, 'jan', 1, 6, 30) File.stubs(:mtime).returns @time_a end [RuntimeError, LoadError, SyntaxError].each do |error| it "should die with Puppet::Error if a #{error.to_s} exception is thrown" do File.stubs(:exist?).returns true Kernel.expects(:load).raises error lambda { @autoload.load("foo") }.should raise_error(Puppet::Error) end end it "should not raise an error if the file is missing" do @autoload.load("foo").should == false end it "should register loaded files with the autoloader" do File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @autoload.class.loaded?("tmp/myfile.rb").should be $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should register loaded files with the main loaded file list so they are not reloaded by ruby" do File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") $LOADED_FEATURES.should be_include("tmp/myfile.rb") $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should load the first file in the searchpath" do @autoload.stubs(:search_directories).returns %w{/a /b} FileTest.stubs(:directory?).returns true File.stubs(:exist?).returns true Kernel.expects(:load).with("/a/tmp/myfile.rb", optionally(anything)) @autoload.load("myfile") $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should treat equivalent paths to a loaded file as loaded" do File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @autoload.class.loaded?("tmp/myfile").should be @autoload.class.loaded?("tmp/./myfile.rb").should be @autoload.class.loaded?("./tmp/myfile.rb").should be @autoload.class.loaded?("tmp/../tmp/myfile.rb").should be $LOADED_FEATURES.delete("tmp/myfile.rb") end end describe "when loading all files" do before do @autoload.class.stubs(:search_directories).returns %w{/a} FileTest.stubs(:directory?).returns true Dir.stubs(:glob).returns "/a/foo/file.rb" File.stubs(:exist?).returns true @time_a = Time.utc(2010, 'jan', 1, 6, 30) File.stubs(:mtime).returns @time_a @autoload.class.stubs(:loaded?).returns(false) end [RuntimeError, LoadError, SyntaxError].each do |error| it "should die an if a #{error.to_s} exception is thrown", :'fails_on_ruby_1.9.2' => true do Kernel.expects(:load).raises error lambda { @autoload.loadall }.should raise_error(Puppet::Error) end end it "should require the full path to the file", :'fails_on_ruby_1.9.2' => true do Kernel.expects(:load).with("/a/foo/file.rb", optionally(anything)) @autoload.loadall end end describe "when reloading files" do before :each do @file_a = "/a/file.rb" @file_b = "/b/file.rb" @first_time = Time.utc(2010, 'jan', 1, 6, 30) @second_time = @first_time + 60 end after :each do $LOADED_FEATURES.delete("a/file.rb") $LOADED_FEATURES.delete("b/file.rb") end describe "in one directory" do before :each do @autoload.class.stubs(:search_directories).returns %w{/a} File.expects(:mtime).with(@file_a).returns(@first_time) @autoload.class.mark_loaded("file", @file_a) end it "should reload if mtime changes" do File.stubs(:mtime).with(@file_a).returns(@first_time + 60) File.stubs(:exist?).with(@file_a).returns true Kernel.expects(:load).with(@file_a, optionally(anything)) @autoload.class.reload_changed end it "should do nothing if the file is deleted" do File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT) File.stubs(:exist?).with(@file_a).returns false Kernel.expects(:load).never @autoload.class.reload_changed end end describe "in two directories" do before :each do @autoload.class.stubs(:search_directories).returns %w{/a /b} end it "should load b/file when a/file is deleted" do File.expects(:mtime).with(@file_a).returns(@first_time) @autoload.class.mark_loaded("file", @file_a) File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT) File.stubs(:exist?).with(@file_a).returns false File.stubs(:exist?).with(@file_b).returns true File.stubs(:mtime).with(@file_b).returns @first_time Kernel.expects(:load).with(@file_b, optionally(anything)) @autoload.class.reload_changed @autoload.class.send(:loaded)["file"].should == [@file_b, @first_time] end it "should load a/file when b/file is loaded and a/file is created" do File.stubs(:mtime).with(@file_b).returns @first_time File.stubs(:exist?).with(@file_b).returns true @autoload.class.mark_loaded("file", @file_b) File.stubs(:mtime).with(@file_a).returns @first_time File.stubs(:exist?).with(@file_a).returns true Kernel.expects(:load).with(@file_a, optionally(anything)) @autoload.class.reload_changed @autoload.class.send(:loaded)["file"].should == [@file_a, @first_time] end end end end diff --git a/spec/unit/util/run_mode_spec.rb b/spec/unit/util/run_mode_spec.rb index f2303ccc2..96fed0372 100755 --- a/spec/unit/util/run_mode_spec.rb +++ b/spec/unit/util/run_mode_spec.rb @@ -1,54 +1,57 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Util::RunMode do before do @run_mode = Puppet::Util::RunMode.new('fake') end it "should have confdir /etc/puppet when run as root" do Puppet.features.stubs(:root?).returns(true) etcdir = Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") : '/etc/puppet' # REMIND: issue with windows backslashes @run_mode.conf_dir.should == File.expand_path(etcdir) end it "should have confdir ~/.puppet when run as non-root" do Puppet.features.stubs(:root?).returns(false) @run_mode.expects(:expand_path).with("~/.puppet").returns("~/.puppet") @run_mode.conf_dir.should == "~/.puppet" end it "should have vardir /var/lib/puppet when run as root" do Puppet.features.stubs(:root?).returns(true) vardir = Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var") : '/var/lib/puppet' # REMIND: issue with windows backslashes @run_mode.var_dir.should == File.expand_path(vardir) end it "should have vardir ~/.puppet/var when run as non-root" do Puppet.features.stubs(:root?).returns(false) @run_mode.expects(:expand_path).with("~/.puppet/var").returns("~/.puppet/var") @run_mode.var_dir.should == "~/.puppet/var" end it "should have rundir depend on vardir" do @run_mode.run_dir.should == '$vardir/run' end + # TODO cprice: determine where this test belongs when this functionality is re-introduced. it "should have logopts return an array with $vardir/log if runmode is not master" do + pending("runmode.logopts functionality is being moved") @run_mode.expects(:master?).returns false @run_mode.logopts.should == ["$vardir/log", "The Puppet log directory."] end it "should have logopts return a hash with $vardir/log and other metadata if runmode is master" do + pending("runmode.logopts functionality is being moved") @run_mode.expects(:master?).returns true @run_mode.logopts.should == { :default => "$vardir/log", :mode => 0750, :owner => "service", :group => "service", :desc => "The Puppet log directory.", } end end diff --git a/spec/unit/util/settings_spec.rb b/spec/unit/util/settings_spec.rb index 7add17664..5a63de7d2 100755 --- a/spec/unit/util/settings_spec.rb +++ b/spec/unit/util/settings_spec.rb @@ -1,1141 +1,1186 @@ #!/usr/bin/env rspec require 'spec_helper' require 'ostruct' describe Puppet::Util::Settings do include PuppetSpec::Files describe "when specifying defaults" do before do @settings = Puppet::Util::Settings.new end it "should start with no defined parameters" do @settings.params.length.should == 0 end - it "should allow specification of default values associated with a section as an array" do - @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + it "should not allow specification of default values associated with a section as an array" do + expect { + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + }.to raise_error end it "should not allow duplicate parameter specifications" do - @settings.setdefaults(:section, :myvalue => ["a", "b"]) - lambda { @settings.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) + @settings.setdefaults(:section, :myvalue => { :default => "a", :desc => "b" }) + lambda { @settings.setdefaults(:section, :myvalue => { :default => "c", :desc => "d" }) }.should raise_error(ArgumentError) end it "should allow specification of default values associated with a section as a hash" do @settings.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) end it "should consider defined parameters to be valid" do - @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + @settings.setdefaults(:section, :myvalue => { :default => "defaultval", :desc => "my description" }) @settings.valid?(:myvalue).should be_true end - it "should require a description when defaults are specified with an array" do - lambda { @settings.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) - end - it "should require a description when defaults are specified with a hash" do lambda { @settings.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) end - it "should raise an error if we can't guess the type" do - lambda { @settings.setdefaults(:section, :myvalue => {:default => Object.new, :desc => "An impossible object"}) }.should raise_error(ArgumentError) - end + # We no longer try to guess the type + #it "should raise an error if we can't guess the type" do + # lambda { @settings.setdefaults(:section, :myvalue => {:default => Object.new, :desc => "An impossible object"}) }.should raise_error(ArgumentError) + #end it "should support specifying owner, group, and mode when specifying files" do - @settings.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"}) + @settings.setdefaults(:section, :myvalue => {:type => :file, :default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"}) end it "should support specifying a short name" do @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) end it "should support specifying the setting type" do - @settings.setdefaults(:section, :myvalue => {:default => "/w", :desc => "b", :type => :setting}) - @settings.setting(:myvalue).should be_instance_of(Puppet::Util::Settings::Setting) + @settings.setdefaults(:section, :myvalue => {:default => "/w", :desc => "b", :type => :string}) + @settings.setting(:myvalue).should be_instance_of(Puppet::Util::Settings::StringSetting) end it "should fail if an invalid setting type is specified" do lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :type => :foo}) }.should raise_error(ArgumentError) end it "should fail when short names conflict" do @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) end end + describe "when initializing application defaults do" do + # TODO cprice: write these tests + end + describe "when setting values" do before do @settings = Puppet::Util::Settings.new - @settings.setdefaults :main, :myval => ["val", "desc"] - @settings.setdefaults :main, :bool => [true, "desc"] + @settings.setdefaults :main, :myval => { :default => "val", :desc => "desc" } + @settings.setdefaults :main, :bool => { :type => :boolean, :default => true, :desc => "desc" } end it "should provide a method for setting values from other objects" do @settings[:myval] = "something else" @settings[:myval].should == "something else" end it "should support a getopt-specific mechanism for setting values" do @settings.handlearg("--myval", "newval") @settings[:myval].should == "newval" end it "should support a getopt-specific mechanism for turning booleans off" do @settings[:bool] = true @settings.handlearg("--no-bool", "") @settings[:bool].should == false end it "should support a getopt-specific mechanism for turning booleans on" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool", "") @settings[:bool].should == true end it "should consider a cli setting with no argument to be a boolean" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool") @settings[:bool].should == true end it "should consider a cli setting with an empty string as an argument to be a boolean, if the setting itself is a boolean" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool", "") @settings[:bool].should == true end it "should consider a cli setting with an empty string as an argument to be an empty argument, if the setting itself is not a boolean" do @settings[:myval] = "bob" @settings.handlearg("--myval", "") @settings[:myval].should == "" end it "should consider a cli setting with a boolean as an argument to be a boolean" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool", "true") @settings[:bool].should == true end it "should not consider a cli setting of a non boolean with a boolean as an argument to be a boolean" do # Turn it off first @settings[:myval] = "bob" @settings.handlearg("--no-myval", "") @settings[:myval].should == "" end it "should flag settings from the CLI" do @settings.handlearg("--myval") @settings.setting(:myval).setbycli.should be_true end it "should not flag settings memory" do @settings[:myval] = "12" @settings.setting(:myval).setbycli.should be_false end it "should clear the cache when setting getopt-specific values" do - @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] + @settings.setdefaults :mysection, + :one => { :default => "whah", :desc => "yay" }, + :two => { :default => "$one yay", :desc => "bah" } @settings[:two].should == "whah yay" @settings.handlearg("--one", "else") @settings[:two].should == "else yay" end it "should not clear other values when setting getopt-specific values" do @settings[:myval] = "yay" @settings.handlearg("--no-bool", "") @settings[:myval].should == "yay" end it "should clear the list of used sections" do @settings.expects(:clearused) @settings[:myval] = "yay" end it "should call passed blocks when values are set" do values = [] @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @settings[:hooker] = "something" values.should == %w{something} end it "should call passed blocks when values are set via the command line" do values = [] @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @settings.handlearg("--hooker", "yay") values.should == %w{yay} end it "should provide an option to call passed blocks during definition" do values = [] @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :call_on_define => true, :hook => lambda { |v| values << v }}) values.should == %w{yay} end it "should pass the fully interpolated value to the hook when called on definition" do values = [] - @settings.setdefaults(:section, :one => ["test", "a"]) + @settings.setdefaults(:section, :one => { :default => "test", :desc => "a" }) @settings.setdefaults(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_on_define => true, :hook => lambda { |v| values << v }}) values.should == %w{test/yay} end it "should munge values using the setting-specific methods" do @settings[:bool] = "false" @settings[:bool].should == false end it "should prefer cli values to values set in Ruby code" do @settings.handlearg("--myval", "cliarg") @settings[:myval] = "memarg" @settings[:myval].should == "cliarg" end it "should clear the list of environments" do Puppet::Node::Environment.expects(:clear).at_least(1) @settings[:myval] = "memarg" end it "should raise an error if we try to set 'name'" do lambda{ @settings[:name] = "foo" }.should raise_error(ArgumentError) end it "should raise an error if we try to set 'run_mode'" do lambda{ @settings[:run_mode] = "foo" }.should raise_error(ArgumentError) end it "should warn and use [master] if we ask for [puppetmasterd]" do Puppet.expects(:deprecation_warning) @settings.set_value(:myval, "foo", :puppetmasterd) @settings.stubs(:run_mode).returns(:master) @settings.value(:myval).should == "foo" end it "should warn and use [agent] if we ask for [puppetd]" do Puppet.expects(:deprecation_warning) @settings.set_value(:myval, "foo", :puppetd) @settings.stubs(:run_mode).returns(:agent) @settings.value(:myval).should == "foo" end end describe "when returning values" do before do @settings = Puppet::Util::Settings.new - @settings.setdefaults :section, :config => ["/my/file", "eh"], :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] + @settings.setdefaults :section, + :config => { :type => :file, :default => "/my/file", :desc => "eh" }, + :one => { :default => "ONE", :desc => "a" }, + :two => { :default => "$one TWO", :desc => "b"}, + :three => { :default => "$one $two THREE", :desc => "c"}, + :four => { :default => "$two $three FOUR", :desc => "d"} FileTest.stubs(:exist?).returns true end it "should provide a mechanism for returning set values" do @settings[:one] = "other" @settings[:one].should == "other" end it "should interpolate default values for other parameters into returned parameter values" do @settings[:one].should == "ONE" @settings[:two].should == "ONE TWO" @settings[:three].should == "ONE ONE TWO THREE" end it "should interpolate default values that themselves need to be interpolated" do @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" end it "should provide a method for returning uninterpolated values" do @settings[:two] = "$one tw0" @settings.uninterpolated_value(:two).should == "$one tw0" @settings.uninterpolated_value(:four).should == "$two $three FOUR" end it "should interpolate set values for other parameters into returned parameter values" do @settings[:one] = "on3" @settings[:two] = "$one tw0" @settings[:three] = "$one $two thr33" @settings[:four] = "$one $two $three f0ur" @settings[:one].should == "on3" @settings[:two].should == "on3 tw0" @settings[:three].should == "on3 on3 tw0 thr33" @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" end it "should not cache interpolated values such that stale information is returned" do @settings[:two].should == "ONE TWO" @settings[:one] = "one" @settings[:two].should == "one TWO" end it "should not cache values such that information from one environment is returned for another environment" do text = "[env1]\none = oneval\n[env2]\none = twoval\n" @settings.stubs(:read_file).returns(text) @settings.parse @settings.value(:one, "env1").should == "oneval" @settings.value(:one, "env2").should == "twoval" end it "should have a run_mode that defaults to user" do @settings.run_mode.should == :user end end describe "when choosing which value to return" do before do @settings = Puppet::Util::Settings.new @settings.setdefaults :section, - :config => ["/my/file", "a"], - :one => ["ONE", "a"], - :two => ["TWO", "b"] + :config => { :type => :file, :default => "/my/file", :desc => "a" }, + :one => { :default => "ONE", :desc => "a" }, + :two => { :default => "TWO", :desc => "b" } FileTest.stubs(:exist?).returns true Puppet.stubs(:run_mode).returns stub('run_mode', :name => :mymode) end it "should return default values if no values have been set" do @settings[:one].should == "ONE" end it "should return values set on the cli before values set in the configuration file" do text = "[main]\none = fileval\n" @settings.stubs(:read_file).returns(text) @settings.handlearg("--one", "clival") @settings.parse @settings[:one].should == "clival" end it "should return values set on the cli before values set in Ruby" do @settings[:one] = "rubyval" @settings.handlearg("--one", "clival") @settings[:one].should == "clival" end it "should return values set in the mode-specific section before values set in the main section" do text = "[main]\none = mainval\n[mymode]\none = modeval\n" @settings.stubs(:read_file).returns(text) @settings.parse @settings[:one].should == "modeval" end it "should not return values outside of its search path" do text = "[other]\none = oval\n" file = "/some/file" @settings.stubs(:read_file).returns(text) @settings.parse @settings[:one].should == "ONE" end it "should return values in a specified environment" do text = "[env]\none = envval\n" @settings.stubs(:read_file).returns(text) @settings.parse @settings.value(:one, "env").should == "envval" end it 'should use the current environment for $environment' do - @settings.setdefaults :main, :myval => ["$environment/foo", "mydocs"] + @settings.setdefaults :main, :myval => { :default => "$environment/foo", :desc => "mydocs" } @settings.value(:myval, "myenv").should == "myenv/foo" end it "should interpolate found values using the current environment" do text = "[main]\none = mainval\n[myname]\none = nameval\ntwo = $one/two\n" @settings.stubs(:read_file).returns(text) @settings.parse @settings.value(:two, "myname").should == "nameval/two" end it "should return values in a specified environment before values in the main or name sections" do text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" @settings.stubs(:read_file).returns(text) @settings.parse @settings.value(:one, "env").should == "envval" end end describe "when parsing its configuration" do before do @settings = Puppet::Util::Settings.new @settings.stubs(:service_user_available?).returns true @file = "/some/file" - @settings.setdefaults :section, :user => ["suser", "doc"], :group => ["sgroup", "doc"] - @settings.setdefaults :section, :config => ["/some/file", "eh"], :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - FileTest.stubs(:exist?).returns true + @settings.setdefaults :section, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } + @settings.setdefaults :section, + :config => { :type => :file, :default => "/some/file", :desc => "eh" }, + :one => { :default => "ONE", :desc => "a" }, + :two => { :default => "$one TWO", :desc => "b" }, + :three => { :default => "$one $two THREE", :desc => "c" } + @settings.stubs(:user_config_file).returns("/test/userconfigfile") + FileTest.stubs(:exist?).with("/some/file").returns true + FileTest.stubs(:exist?).with("/test/userconfigfile").returns false end it "should not ignore the report setting" do - @settings.setdefaults :section, :report => ["false", "a"] - myfile = stub "myfile" + @settings.setdefaults :section, :report => { :default => "false", :desc => "a" } + # This is needed in order to make sure we pass on windows + myfile = File.expand_path("/my/file") @settings[:config] = myfile text = <<-CONF [puppetd] report=true CONF + FileTest.expects(:exist?).with(myfile).returns(true) @settings.expects(:read_file).returns(text) @settings.parse @settings[:report].should be_true end it "should use its current ':config' value for the file to parse" do myfile = make_absolute("/my/file") # do not stub expand_path here, as this leads to a stack overflow, when mocha tries to use it @settings[:config] = myfile + FileTest.expects(:exist?).with(myfile).returns(true) + File.expects(:read).with(myfile).returns "[main]" @settings.parse end - it "should fail if no configuration setting is defined" do - @settings = Puppet::Util::Settings.new - lambda { @settings.parse }.should raise_error(RuntimeError) - end + # TODO cprice: this test needs to be replaced with one that checks to ensure it uses the proper default files if + # no config setting is defined + #it "should fail if no configuration setting is defined" do + # @settings = Puppet::Util::Settings.new + # #lambda { + # @settings.parse + # #}.should raise_error(RuntimeError) + #end it "should not try to parse non-existent files" do FileTest.expects(:exist?).with("/some/file").returns false File.expects(:read).with("/some/file").never @settings.parse end it "should set a timer that triggers reparsing, even if the file does not exist" do FileTest.expects(:exist?).returns false @settings.expects(:set_filetimeout_timer) @settings.parse end it "should return values set in the configuration file" do text = "[main] one = fileval " @settings.expects(:read_file).returns(text) @settings.parse @settings[:one].should == "fileval" end #484 - this should probably be in the regression area it "should not throw an exception on unknown parameters" do text = "[main]\nnosuchparam = mval\n" @settings.expects(:read_file).returns(text) lambda { @settings.parse }.should_not raise_error end it "should convert booleans in the configuration file into Ruby booleans" do text = "[main] one = true two = false " @settings.expects(:read_file).returns(text) @settings.parse @settings[:one].should == true @settings[:two].should == false end it "should convert integers in the configuration file into Ruby Integers" do text = "[main] one = 65 " @settings.expects(:read_file).returns(text) @settings.parse @settings[:one].should == 65 end it "should support specifying all metadata (owner, group, mode) in the configuration file" do - @settings.setdefaults :section, :myfile => ["/myfile", "a"] + @settings.setdefaults :section, :myfile => { :type => :file, :default => "/myfile", :desc => "a" } otherfile = make_absolute("/other/file") text = "[main] myfile = #{otherfile} {owner = service, group = service, mode = 644} " @settings.expects(:read_file).returns(text) @settings.parse @settings[:myfile].should == otherfile @settings.metadata(:myfile).should == {:owner => "suser", :group => "sgroup", :mode => "644"} end it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do - @settings.setdefaults :section, :myfile => ["/myfile", "a"] + @settings.setdefaults :section, :myfile => { :type => :file, :default => "/myfile", :desc => "a" } otherfile = make_absolute("/other/file") text = "[main] myfile = #{otherfile} {owner = service} " file = "/some/file" @settings.expects(:read_file).returns(text) @settings.parse @settings[:myfile].should == otherfile @settings.metadata(:myfile).should == {:owner => "suser"} end it "should call hooks associated with values set in the configuration file" do values = [] @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = setval " @settings.expects(:read_file).returns(text) @settings.parse values.should == ["setval"] end it "should not call the same hook for values set multiple times in the configuration file" do values = [] @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[user] mysetting = setval [main] mysetting = other " @settings.expects(:read_file).returns(text) @settings.parse values.should == ["setval"] end it "should pass the environment-specific value to the hook when one is available" do values = [] @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} - @settings.setdefaults :section, :environment => ["yay", "a"] - @settings.setdefaults :section, :environments => ["yay,foo", "a"] + @settings.setdefaults :section, :environment => { :default => "yay", :desc => "a" } + @settings.setdefaults :section, :environments => { :default => "yay,foo", :desc => "a" } text = "[main] mysetting = setval [yay] mysetting = other " @settings.expects(:read_file).returns(text) @settings.parse values.should == ["other"] end it "should pass the interpolated value to the hook when one is available" do values = [] @settings.setdefaults :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }} @settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = $base/setval " @settings.expects(:read_file).returns(text) @settings.parse values.should == ["yay/setval"] end it "should allow empty values" do - @settings.setdefaults :section, :myarg => ["myfile", "a"] + @settings.setdefaults :section, :myarg => { :default => "myfile", :desc => "a" } text = "[main] myarg = " @settings.stubs(:read_file).returns(text) @settings.parse @settings[:myarg].should == "" end describe "and when reading a non-positive filetimeout value from the config file" do before do - @settings.setdefaults :foo, :filetimeout => [5, "eh"] + @settings.setdefaults :foo, :filetimeout => { :default => 5, :desc => "eh" } somefile = "/some/file" text = "[main] filetimeout = -1 " File.expects(:read).with(somefile).returns(text) File.expects(:expand_path).with(somefile).returns somefile @settings[:config] = somefile end it "should not set a timer" do EventLoop::Timer.expects(:new).never @settings.parse end end end describe "when reparsing its configuration" do before do @settings = Puppet::Util::Settings.new - @settings.setdefaults :section, :config => ["/test/file", "a"], :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - FileTest.stubs(:exist?).returns true + @settings.setdefaults :section, + :config => { :type => :file, :default => "/test/file", :desc => "a" }, + :one => { :default => "ONE", :desc => "a" }, + :two => { :default => "$one TWO", :desc => "b" }, + :three => { :default => "$one $two THREE", :desc => "c" } + FileTest.stubs(:exist?).with("/test/file").returns true + FileTest.stubs(:exist?).with("/test/userconfigfile").returns false + @settings.stubs(:user_config_file).returns("/test/userconfigfile") end it "should use a LoadedFile instance to determine if the file has changed" do file = mock 'file' Puppet::Util::LoadedFile.expects(:new).with("/test/file").returns file file.expects(:changed?) @settings.stubs(:parse) @settings.reparse end it "should not create the LoadedFile instance and should not parse if the file does not exist" do FileTest.expects(:exist?).with("/test/file").returns false Puppet::Util::LoadedFile.expects(:new).never @settings.expects(:parse).never @settings.reparse end it "should not reparse if the file has not changed" do file = mock 'file' Puppet::Util::LoadedFile.expects(:new).with("/test/file").returns file file.expects(:changed?).returns false @settings.expects(:parse).never @settings.reparse end it "should reparse if the file has changed" do file = stub 'file', :file => "/test/file" Puppet::Util::LoadedFile.expects(:new).with("/test/file").returns file file.expects(:changed?).returns true @settings.expects(:parse) @settings.reparse end it "should replace in-memory values with on-file values" do # Init the value text = "[main]\none = disk-init\n" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/test/file") @settings[:one] = "init" - @settings.file = file + @settings.files = [file] # Now replace the value text = "[main]\none = disk-replace\n" # This is kinda ridiculous - the reason it parses twice is that # it goes to parse again when we ask for the value, because the # mock always says it should get reparsed. @settings.stubs(:read_file).returns(text) @settings.reparse @settings[:one].should == "disk-replace" end it "should retain parameters set by cli when configuration files are reparsed" do @settings.handlearg("--one", "clival") text = "[main]\none = on-disk\n" @settings.stubs(:read_file).returns(text) @settings.parse @settings[:one].should == "clival" end it "should remove in-memory values that are no longer set in the file" do # Init the value text = "[main]\none = disk-init\n" @settings.expects(:read_file).returns(text) @settings.parse @settings[:one].should == "disk-init" # Now replace the value text = "[main]\ntwo = disk-replace\n" @settings.expects(:read_file).returns(text) @settings.parse #@settings.reparse # The originally-overridden value should be replaced with the default @settings[:one].should == "ONE" # and we should now have the new value in memory @settings[:two].should == "disk-replace" end it "should retain in-memory values if the file has a syntax error" do # Init the value text = "[main]\none = initial-value\n" - @settings.expects(:read_file).returns(text) + @settings.expects(:read_file).with("/test/file").returns(text) + #@settings.expects(:new).with("/test/userconfigfile").returns @settings.parse @settings[:one].should == "initial-value" # Now replace the value with something bogus text = "[main]\nkenny = killed-by-what-follows\n1 is 2, blah blah florp\n" - @settings.expects(:read_file).returns(text) + @settings.expects(:read_file).with("/test/file").returns(text) @settings.parse # The originally-overridden value should not be replaced with the default @settings[:one].should == "initial-value" # and we should not have the new value in memory @settings[:kenny].should be_nil end end it "should provide a method for creating a catalog of resources from its configuration" do Puppet::Util::Settings.new.should respond_to(:to_catalog) end describe "when creating a catalog" do before do @settings = Puppet::Util::Settings.new @settings.stubs(:service_user_available?).returns true @prefix = Puppet.features.posix? ? "" : "C:" end it "should add all file resources to the catalog if no sections have been specified" do - @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"], :seconddir => [@prefix+"/seconddir", "a"] - @settings.setdefaults :other, :otherdir => [@prefix+"/otherdir", "a"] + @settings.setdefaults :main, + :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a"}, + :seconddir => { :type => :directory, :default => @prefix+"/seconddir", :desc => "a"} + @settings.setdefaults :other, + :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog [@prefix+"/maindir", @prefix+"/seconddir", @prefix+"/otherdir"].each do |path| catalog.resource(:file, path).should be_instance_of(Puppet::Resource) end end it "should add only files in the specified sections if section names are provided" do - @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"] - @settings.setdefaults :other, :otherdir => [@prefix+"/otherdir", "a"] + @settings.setdefaults :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } + @settings.setdefaults :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog(:main) catalog.resource(:file, @prefix+"/otherdir").should be_nil catalog.resource(:file, @prefix+"/maindir").should be_instance_of(Puppet::Resource) end it "should not try to add the same file twice" do - @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"] - @settings.setdefaults :other, :otherdir => [@prefix+"/maindir", "a"] + @settings.setdefaults :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } + @settings.setdefaults :other, :otherdir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } lambda { @settings.to_catalog }.should_not raise_error end it "should ignore files whose :to_resource method returns nil" do - @settings.setdefaults :main, :maindir => [@prefix+"/maindir", "a"] + @settings.setdefaults :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.setting(:maindir).expects(:to_resource).returns nil Puppet::Resource::Catalog.any_instance.expects(:add_resource).never @settings.to_catalog end describe "on Microsoft Windows" do before :each do Puppet.features.stubs(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns true - @settings.setdefaults :foo, :mkusers => [true, "e"], :user => ["suser", "doc"], :group => ["sgroup", "doc"] - @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} + @settings.setdefaults :foo, + :mkusers => { :type => :boolean, :default => true, :desc => "e" }, + :user => { :default => "suser", :desc => "doc" }, + :group => { :default => "sgroup", :desc => "doc" } + @settings.setdefaults :other, + :otherdir => { :type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "it should not add users and groups to the catalog" do @catalog.resource(:user, "suser").should be_nil @catalog.resource(:group, "sgroup").should be_nil end end describe "when adding users and groups to the catalog" do before do Puppet.features.stubs(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false - @settings.setdefaults :foo, :mkusers => [true, "e"], :user => ["suser", "doc"], :group => ["sgroup", "doc"] - @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} + @settings.setdefaults :foo, + :mkusers => { :type => :boolean, :default => true, :desc => "e" }, + :user => { :default => "suser", :desc => "doc" }, + :group => { :default => "sgroup", :desc => "doc" } + @settings.setdefaults :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "should add each specified user and group to the catalog if :mkusers is a valid setting, is enabled, and we're running as root" do @catalog.resource(:user, "suser").should be_instance_of(Puppet::Resource) @catalog.resource(:group, "sgroup").should be_instance_of(Puppet::Resource) end it "should only add users and groups to the catalog from specified sections" do - @settings.setdefaults :yay, :yaydir => {:default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} + @settings.setdefaults :yay, :yaydir => { :type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} catalog = @settings.to_catalog(:other) catalog.resource(:user, "jane").should be_nil catalog.resource(:group, "billy").should be_nil end it "should not add users or groups to the catalog if :mkusers not running as root" do Puppet.features.stubs(:root?).returns false catalog = @settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not add users or groups to the catalog if :mkusers is not a valid setting" do Puppet.features.stubs(:root?).returns true settings = Puppet::Util::Settings.new - settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} + settings.setdefaults :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} catalog = settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not add users or groups to the catalog if :mkusers is a valid setting but is disabled" do @settings[:mkusers] = false catalog = @settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not try to add users or groups to the catalog twice" do - @settings.setdefaults :yay, :yaydir => {:default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} + @settings.setdefaults :yay, :yaydir => {:type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} # This would fail if users/groups were added twice lambda { @settings.to_catalog }.should_not raise_error end it "should set :ensure to :present on each created user and group" do @catalog.resource(:user, "suser")[:ensure].should == :present @catalog.resource(:group, "sgroup")[:ensure].should == :present end it "should set each created user's :gid to the service group" do @settings.to_catalog.resource(:user, "suser")[:gid].should == "sgroup" end it "should not attempt to manage the root user" do Puppet.features.stubs(:root?).returns true - @settings.setdefaults :foo, :foodir => {:default => "/foodir", :desc => "a", :owner => "root", :group => "service"} + @settings.setdefaults :foo, :foodir => {:type => :directory, :default => "/foodir", :desc => "a", :owner => "root", :group => "service"} @settings.to_catalog.resource(:user, "root").should be_nil end end end it "should be able to be converted to a manifest" do Puppet::Util::Settings.new.should respond_to(:to_manifest) end describe "when being converted to a manifest" do it "should produce a string with the code for each resource joined by two carriage returns" do @settings = Puppet::Util::Settings.new - @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + @settings.setdefaults :main, + :maindir => { :type => :directory, :default => "/maindir", :desc => "a"}, + :seconddir => { :type => :directory, :default => "/seconddir", :desc => "a"} main = stub 'main_resource', :ref => "File[/maindir]" main.expects(:to_manifest).returns "maindir" second = stub 'second_resource', :ref => "File[/seconddir]" second.expects(:to_manifest).returns "seconddir" @settings.setting(:maindir).expects(:to_resource).returns main @settings.setting(:seconddir).expects(:to_resource).returns second @settings.to_manifest.split("\n\n").sort.should == %w{maindir seconddir} end end describe "when using sections of the configuration to manage the local host" do before do @settings = Puppet::Util::Settings.new @settings.stubs(:service_user_available?).returns true - @settings.setdefaults :main, :noop => [false, ""] - @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] - @settings.setdefaults :main, :user => ["suser", "doc"], :group => ["sgroup", "doc"] - @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "service", :group => "service", :mode => 0755} - @settings.setdefaults :third, :thirddir => ["/thirddir", "b"] - @settings.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} + @settings.setdefaults :main, :noop => { :default => false, :desc => "", :type => :boolean } + @settings.setdefaults :main, + :maindir => { :type => :directory, :default => "/maindir", :desc => "a" }, + :seconddir => { :type => :directory, :default => "/seconddir", :desc => "a"} + @settings.setdefaults :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } + @settings.setdefaults :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service", :mode => 0755} + @settings.setdefaults :third, :thirddir => { :type => :directory, :default => "/thirddir", :desc => "b"} + @settings.setdefaults :files, :myfile => {:type => :file, :default => "/myfile", :desc => "a", :mode => 0755} end it "should provide a method that writes files with the correct modes" do @settings.should respond_to(:write) end it "should provide a method that creates directories with the correct modes" do Puppet::Util::SUIDManager.expects(:asuser).with("suser", "sgroup").yields Dir.expects(:mkdir).with("/otherdir", 0755) @settings.mkdir(:otherdir) end it "should create a catalog with the specified sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should canonicalize the sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use("main", "other") end it "should ignore sections that have already been used" do @settings.expects(:to_catalog).with(:main).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main) @settings.expects(:to_catalog).with(:other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should ignore tags and schedules when creating files and directories" it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do pending "Not converted from test/unit yet" end it "should convert the created catalog to a RAL catalog" do @catalog = Puppet::Resource::Catalog.new("foo") @settings.expects(:to_catalog).with(:main).returns @catalog @catalog.expects(:to_ral).returns @catalog @settings.use(:main) end it "should specify that it is not managing a host catalog" do catalog = Puppet::Resource::Catalog.new("foo") catalog.expects(:apply) @settings.expects(:to_catalog).returns catalog catalog.stubs(:to_ral).returns catalog catalog.expects(:host_config=).with false @settings.use(:main) end it "should support a method for re-using all currently used sections" do @settings.expects(:to_catalog).with(:main, :third).times(2).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :third) @settings.reuse end it "should fail with an appropriate message if any resources fail" do @catalog = Puppet::Resource::Catalog.new("foo") @catalog.stubs(:to_ral).returns @catalog @settings.expects(:to_catalog).returns @catalog @trans = mock("transaction") @catalog.expects(:apply).yields(@trans) @trans.expects(:any_failed?).returns(true) report = mock 'report' @trans.expects(:report).returns report log = mock 'log', :to_s => "My failure", :level => :err report.expects(:logs).returns [log] @settings.expects(:raise).with { |msg| msg.include?("My failure") } @settings.use(:whatever) end end describe "when dealing with printing configs" do before do @settings = Puppet::Util::Settings.new #these are the magic default values @settings.stubs(:value).with(:configprint).returns("") @settings.stubs(:value).with(:genconfig).returns(false) @settings.stubs(:value).with(:genmanifest).returns(false) @settings.stubs(:value).with(:environment).returns(nil) end describe "when checking print_config?" do it "should return false when the :configprint, :genconfig and :genmanifest are not set" do @settings.print_configs?.should be_false end it "should return true when :configprint has a value" do @settings.stubs(:value).with(:configprint).returns("something") @settings.print_configs?.should be_true end it "should return true when :genconfig has a value" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.print_configs?.should be_true end it "should return true when :genmanifest has a value" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.print_configs?.should be_true end end describe "when printing configs" do describe "when :configprint has a value" do it "should call print_config_options" do @settings.stubs(:value).with(:configprint).returns("something") @settings.expects(:print_config_options) @settings.print_configs end it "should get the value of the option using the environment" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.expects(:value).with(:environment).returns("env") @settings.expects(:value).with("something", "env").returns("foo") @settings.stubs(:puts).with("foo") @settings.print_configs end it "should print the value of the option" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.expects(:puts).with("foo") @settings.print_configs end it "should print the value pairs if there are multiple options" do @settings.stubs(:value).with(:configprint).returns("bar,baz") @settings.stubs(:include?).with("bar").returns(true) @settings.stubs(:include?).with("baz").returns(true) @settings.stubs(:value).with("bar", nil).returns("foo") @settings.stubs(:value).with("baz", nil).returns("fud") @settings.expects(:puts).with("bar = foo") @settings.expects(:puts).with("baz = fud") @settings.print_configs end it "should print a whole bunch of stuff if :configprint = all" it "should return true after printing" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.stubs(:puts).with("foo") @settings.print_configs.should be_true end it "should return false if a config param is not found" do @settings.stubs :puts @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(false) @settings.print_configs.should be_false end end describe "when genconfig is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.expects(:to_config) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.stubs(:to_config) @settings.print_configs.should be_true end end describe "when genmanifest is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.expects(:to_manifest) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.stubs(:to_manifest) @settings.print_configs.should be_true end end end end describe "when setting a timer to trigger configuration file reparsing" do before do @settings = Puppet::Util::Settings.new - @settings.setdefaults :foo, :filetimeout => [5, "eh"] + @settings.setdefaults :foo, :filetimeout => { :default => 5, :desc => "eh"} end it "should do nothing if no filetimeout setting is available" do @settings.expects(:value).with(:filetimeout).returns nil EventLoop::Timer.expects(:new).never @settings.set_filetimeout_timer end it "should always convert the timer interval to an integer" do @settings.expects(:value).with(:filetimeout).returns "10" EventLoop::Timer.expects(:new).with(:interval => 10, :start? => true, :tolerance => 1) @settings.set_filetimeout_timer end it "should do nothing if the filetimeout setting is not greater than 0" do @settings.expects(:value).with(:filetimeout).returns -2 EventLoop::Timer.expects(:new).never @settings.set_filetimeout_timer end it "should create a timer with its interval set to the filetimeout, start? set to true, and a tolerance of 1" do @settings.expects(:value).with(:filetimeout).returns 5 EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1) @settings.set_filetimeout_timer end it "should reparse when the timer goes off" do EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).yields @settings.expects(:reparse) @settings.set_filetimeout_timer end end describe "when determining if the service user is available" do it "should return false if there is no user setting" do Puppet::Util::Settings.new.should_not be_service_user_available end it "should return false if the user provider says the user is missing" do settings = Puppet::Util::Settings.new - settings.setdefaults :main, :user => ["foo", "doc"] + settings.setdefaults :main, :user => { :default => "foo", :desc => "doc" } user = mock 'user' user.expects(:exists?).returns false Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == "foo" }.returns user settings.should_not be_service_user_available end it "should return true if the user provider says the user is present" do settings = Puppet::Util::Settings.new - settings.setdefaults :main, :user => ["foo", "doc"] + settings.setdefaults :main, :user => { :default => "foo", :desc => "doc" } user = mock 'user' user.expects(:exists?).returns true Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == "foo" }.returns user settings.should be_service_user_available end it "should cache the result" end describe "#writesub" do it "should only pass valid arguments to File.open" do settings = Puppet::Util::Settings.new settings.stubs(:get_config_file_default).with(:privatekeydir).returns(OpenStruct.new(:mode => "750")) File.expects(:open).with("/path/to/keydir", "w", 750).returns true settings.writesub(:privatekeydir, "/path/to/keydir") end end end diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 4d32f1408..a6d1e0831 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -1,317 +1,321 @@ # Add .../test/lib testlib = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift(testlib) unless $LOAD_PATH.include?(testlib) # Add .../lib mainlib = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) $LOAD_PATH.unshift(mainlib) unless $LOAD_PATH.include?(mainlib) require 'puppet' require 'mocha' # Only load the test/unit class if we're not in the spec directory. # Else we get the bogus 'no tests, no failures' message. unless Dir.getwd =~ /spec/ require 'test/unit' end # Yay; hackish but it works if ARGV.include?("-d") ARGV.delete("-d") $console = true end require File.expand_path(File.join(File.dirname(__FILE__), '../../spec/monkey_patches/publicize_methods')) module PuppetTest # These need to be here for when rspec tests use these # support methods. @@tmpfiles = [] # Munge cli arguments, so we can enable debugging if we want # and so we can run just specific methods. def self.munge_argv require 'getoptlong' result = GetoptLong.new( [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--resolve", "-r", GetoptLong::REQUIRED_ARGUMENT ], [ "-n", GetoptLong::REQUIRED_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ) usage = "USAGE: TESTOPTS='[-n -n ...] [-d]' rake [target] [target] ..." opts = [] dir = method = nil result.each { |opt,arg| case opt when "--resolve" dir, method = arg.split(",") when "--debug" $puppet_debug = true Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) when "--help" puts usage exit else opts << opt << arg end } suites = nil args = ARGV.dup # Reset the options, so the test suite can deal with them (this is # what makes things like '-n' work). opts.each { |o| ARGV << o } args end # Find the root of the Puppet tree; this is not the test directory, but # the parent of that dir. def basedir(*list) unless defined?(@@basedir) Dir.chdir(File.dirname(__FILE__)) do @@basedir = File.dirname(File.dirname(Dir.getwd)) end end if list.empty? @@basedir else File.join(@@basedir, *list) end end def datadir(*list) File.join(basedir, "test", "data", *list) end def exampledir(*args) @@exampledir = File.join(basedir, "examples") unless defined?(@@exampledir) if args.empty? return @@exampledir else return File.join(@@exampledir, *args) end end module_function :basedir, :datadir, :exampledir def cleanup(&block) @@cleaners << block end # Rails clobbers RUBYLIB, thanks def libsetup curlibs = ENV["RUBYLIB"].split(":") $LOAD_PATH.reject do |dir| dir =~ /^\/usr/ end.each do |dir| curlibs << dir unless curlibs.include?(dir) end ENV["RUBYLIB"] = curlibs.join(":") end def logcollector collector = [] Puppet::Util::Log.newdestination(collector) cleanup do Puppet::Util::Log.close(collector) end collector end def rake? $0 =~ /test_loader/ end # Redirect stdout and stderr def redirect @stderr = tempfile @stdout = tempfile $stderr = File.open(@stderr, "w") $stdout = File.open(@stdout, "w") cleanup do $stderr = STDERR $stdout = STDOUT end end def setup ENV["PATH"] += File::PATH_SEPARATOR + "/usr/sbin" unless ENV["PATH"].split(File::PATH_SEPARATOR).include?("/usr/sbin") @memoryatstart = Puppet::Util.memory if defined?(@@testcount) @@testcount += 1 else @@testcount = 0 end @configpath = File.join( tmpdir, "configdir" + @@testcount.to_s + "/" ) unless defined? $user and $group $user = nonrootuser.uid.to_s $group = nonrootgroup.gid.to_s end Puppet.settings.clear Puppet[:user] = $user Puppet[:group] = $group + + Puppet.settings.set_value(:name, "unittest", :application_defaults) Puppet[:confdir] = @configpath Puppet[:vardir] = @configpath + Puppet[:logdir] = @configpath + Puppet[:rundir] = @configpath Dir.mkdir(@configpath) unless File.exists?(@configpath) @@tmpfiles << @configpath << tmpdir @@tmppids = [] @@cleaners = [] @logs = [] # If we're running under rake, then disable debugging and such. #if rake? or ! Puppet[:debug] #if defined?($puppet_debug) or ! rake? Puppet[:color] = false if textmate? Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) if defined? $console Puppet.info @method_name Puppet::Util::Log.newdestination(:console) Puppet[:trace] = true end Puppet::Util::Log.level = :debug #$VERBOSE = 1 #else # Puppet::Util::Log.close # Puppet::Util::Log.newdestination(@logs) # Puppet[:httplog] = tempfile #end Puppet[:ignoreschedules] = true #@start = Time.now #Facter.stubs(:value).returns "stubbed_value" #Facter.stubs(:to_hash).returns({}) end def tempfile(suffix = '') if defined?(@@tmpfilenum) @@tmpfilenum += 1 else @@tmpfilenum = 1 end f = File.join(self.tmpdir, "tempfile_" + @@tmpfilenum.to_s + suffix) @@tmpfiles ||= [] @@tmpfiles << f f end def textmate? !!ENV["TM_FILENAME"] end def tstdir dir = tempfile Dir.mkdir(dir) dir end def tmpdir unless @tmpdir @tmpdir = case Facter["operatingsystem"].value when "Darwin" then "/private/tmp" when "SunOS" then "/var/tmp" else "/tmp" end @tmpdir = File.join(@tmpdir, "puppettesting#{Process.pid}") unless File.exists?(@tmpdir) FileUtils.mkdir_p(@tmpdir) File.chmod(01777, @tmpdir) end end @tmpdir end def remove_tmp_files @@tmpfiles.each { |file| unless file =~ /tmp/ puts "Not deleting tmpfile #{file}" next end if FileTest.exists?(file) system("chmod -R 755 #{file}") system("rm -rf #{file}") end } @@tmpfiles.clear end def teardown #@stop = Time.now #File.open("/tmp/test_times.log", ::File::WRONLY|::File::CREAT|::File::APPEND) { |f| f.puts "%0.4f %s %s" % [@stop - @start, @method_name, self.class] } @@cleaners.each { |cleaner| cleaner.call } remove_tmp_files @@tmppids.each { |pid| %x{kill -INT #{pid} 2>/dev/null} } @@tmppids.clear Puppet::Util::Storage.clear Puppet.clear Puppet.settings.clear @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart Puppet.info "#{self.class}##{@method_name} memory growth (#{@memoryatstart} to #{@memoryatend}): #{diff}" if diff > 1000 # reset all of the logs Puppet::Util::Log.close_all @logs.clear # Just in case there are processes waiting to die... require 'timeout' begin Timeout::timeout(5) do Process.waitall end rescue Timeout::Error # just move on end end def logstore @logs = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) end end require 'puppettest/support' require 'puppettest/filetesting' require 'puppettest/fakes' require 'puppettest/exetest' require 'puppettest/parsertesting' require 'puppettest/servertest' require 'puppettest/testcase' diff --git a/test/util/settings.rb b/test/util/settings.rb index 7a7150983..a9c36a94c 100755 --- a/test/util/settings.rb +++ b/test/util/settings.rb @@ -1,773 +1,776 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'mocha' require 'puppettest' require 'puppet/util/settings' require 'puppettest/parsertesting' class TestSettings < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting - Setting = Puppet::Util::Settings::Setting + Setting = Puppet::Util::Settings::StringSetting BooleanSetting = Puppet::Util::Settings::BooleanSetting def setup super @config = mkconfig end def set_configs(config = nil) config ||= @config config.setdefaults( "main", - :one => ["a", "one"], - :two => ["a", "two"], - :yay => ["/default/path", "boo"], - :mkusers => [true, "uh, yeah"], + :one => { :type => :string, :default => "a", :desc => "one" }, + :two => { :type => :string, :default => "a", :desc => "two" }, + :yay => { :type => :string, :default => "/default/path", :desc => "boo" }, + :mkusers => { :type => :boolean, :default => true, :desc => "uh, yeah" }, - :name => ["testing", "a"] + :name => { :type => :string, :default => "testing", :desc => "a" } ) config.setdefaults( "section1", - :attr => ["a", "one"], - :attrdir => ["/another/dir", "two"], + :attr => { :type => :string, :default => "a", :desc => "one" }, + :attrdir => { :type => :directory, :default => "/another/dir", :desc => "two" }, - :attr3 => ["$attrdir/maybe", "boo"] + :attr3 => { :type => :string, :default => "$attrdir/maybe", :desc => "boo" } ) end def check_for_users count = Puppet::Type.type(:user).inject(0) { |c,o| c + 1 } assert(count > 0, "Found no users") end def test_to_config set_configs newc = mkconfig set_configs(newc) # Reset all of the values, so we know they're changing. newc.each do |name, obj| next if name == :name newc[name] = true end newfile = tempfile File.open(newfile, "w") { |f| @config.to_config.split("\n").each do |line| # Uncomment the settings, so they actually take. if line =~ / = / f.puts line.sub(/^\s*#/, '') else f.puts line end end } - newc.setdefaults :section, :config => [newfile, "eh"] + newc.setdefaults :section, :config => { :type => :file, :default => newfile, :desc => "eh" } assert_nothing_raised("Could not parse generated configuration") { newc.parse } @config.each do |name, object| assert_equal(@config[name], newc[name], "Parameter #{name} is not the same") end end def mkconfig c = Puppet::Util::Settings.new - c.setdefaults :main, :noop => [false, "foo"] + c.setdefaults :main, :noop => { :type => :boolean, :default => false, :desc => "foo" } c end def test_addbools assert_nothing_raised { - @config.setdefaults(:testing, :booltest => [true, "testing"]) + @config.setdefaults(:testing, :booltest => { :type => :boolean, :default => true, :desc => "testing" }) } assert(@config[:booltest]) @config = mkconfig assert_nothing_raised { - @config.setdefaults(:testing, :booltest => ["true", "testing"]) + @config.setdefaults(:testing, :booltest => { :type => :boolean, :default => "true", :desc => "testing" }) } assert(@config[:booltest]) assert_nothing_raised { @config[:booltest] = false } assert(! @config[:booltest], "Booltest is not false") assert_nothing_raised { @config[:booltest] = "false" } assert(! @config[:booltest], "Booltest is not false") assert_raise(ArgumentError) { @config[:booltest] = "yayness" } assert_raise(ArgumentError) { @config[:booltest] = "/some/file" } end def test_strings val = "this is a string" assert_nothing_raised { - @config.setdefaults(:testing, :strtest => [val, "testing"]) + @config.setdefaults(:testing, :strtest => { :type => :string, :default => val, :desc => "testing" }) } assert_equal(val, @config[:strtest]) # Verify that variables are interpolated assert_nothing_raised { - @config.setdefaults(:testing, :another => ["another $strtest", "testing"]) + @config.setdefaults(:testing, :another => { :type => :string, :default => "another $strtest", :desc => "testing" }) } assert_equal("another #{val}", @config[:another]) end def test_files c = mkconfig parent = "/puppet" assert_nothing_raised { - @config.setdefaults(:testing, :parentdir => [parent, "booh"]) + @config.setdefaults(:testing, :parentdir => { :type => :directory, :default => parent, :desc => "booh" }) } assert_nothing_raised { - @config.setdefaults(:testing, :child => ["$parent/child", "rah"]) + @config.setdefaults(:testing, :child => { :type => :file, :default => "$parent/child", :desc => "rah" }) } assert_equal(parent, @config[:parentdir]) assert_equal("/puppet/child", File.join(@config[:parentdir], "child")) end def test_getset initial = "an initial value" assert_raise(ArgumentError) { @config[:yayness] = initial } default = "this is a default" assert_nothing_raised { - @config.setdefaults(:testing, :yayness => [default, "rah"]) + @config.setdefaults(:testing, :yayness => { :type => :string, :default => default, :desc => "rah" }) } assert_equal(default, @config[:yayness]) assert_nothing_raised { @config[:yayness] = initial } assert_equal(initial, @config[:yayness]) assert_nothing_raised { @config.clear } assert_equal(default, @config[:yayness], "'clear' did not remove old values") assert_nothing_raised { @config[:yayness] = "not default" } assert_equal("not default", @config[:yayness]) end def test_parse_file text = %{ one = this is a test two = another test owner = root group = root yay = /a/path [main] four = five six = seven [section1] attr = value owner = puppet group = puppet attrdir = /some/dir attr3 = $attrdir/other } file = tempfile File.open(file, "w") { |f| f.puts text } result = nil assert_nothing_raised { result = @config.send(:parse_file, file) } main = result[:main] assert(main, "Did not get section for main") { :one => "this is a test", :two => "another test", :owner => "root", :group => "root", :yay => "/a/path", :four => "five", :six => "seven" }.each do |param, value| assert_equal(value, main[param], "Param #{param} was not set correctly in main") end section1 = result[:section1] assert(section1, "Did not get section1") { :attr => "value", :owner => "puppet", :group => "puppet", :attrdir => "/some/dir", :attr3 => "$attrdir/other" }.each do |param, value| assert_equal(value, section1[param], "Param #{param} was not set correctly in section1") end end def test_arghandling c = mkconfig assert_nothing_raised { @config.setdefaults( "testing", - :onboolean => [true, "An on bool"], - :offboolean => [false, "An off bool"], - :string => ["a string", "A string arg"], + :onboolean => { :type => :boolean, :default => true, :desc => "An on bool" }, + :offboolean => { :type => :boolean, :default => false, :desc => "An off bool" }, + :string => { :type => :string, :default => "a string", :desc => "A string arg" }, - :file => ["/path/to/file", "A file arg"] + :file => { :type => :file, :default => "/path/to/file", :desc => "A file arg" } ) } data = { :onboolean => [true, false], :offboolean => [true, false], :string => ["one string", "another string"], :file => %w{/a/file /another/file} } data.each { |param, values| values.each { |val| opt = nil arg = nil if @config.boolean?(param) if val opt = "--#{param}" else opt = "--no-#{param}" end else opt = "--#{param}" arg = val end assert_nothing_raised("Could not handle arg #{opt} with value #{val}") { @config.handlearg(opt, arg) } } } end def test_addargs @config.setdefaults( "testing", - :onboolean => [true, "An on bool"], - :offboolean => [false, "An off bool"], - :string => ["a string", "A string arg"], + :onboolean => { :type => :boolean, :default => true, :desc => "An on bool" }, + :offboolean => { :type => :boolean, :default => false, :desc => "An off bool" }, + :string => { :type => :string, :default => "a string", :desc => "A string arg" }, - :file => ["/path/to/file", "A file arg"] + :file => { :type => :file, :default => "/path/to/file", :desc => "A file arg" } ) should = [] @config.each { |name, element| element.expects(:getopt_args).returns([name]) should << name } result = [] assert_nothing_raised("Add args failed") do @config.addargs(result) end assert_equal(should, result, "Did not call addargs correctly.") end def test_addargs_functional @config = Puppet::Util::Settings.new @config.setdefaults( "testing", - :onboolean => [true, "An on bool"], + :onboolean => { :type => :boolean, :default => true, :desc => "An on bool" }, - :string => ["a string", "A string arg"] + :string => { :type => :string, :default => "a string", :desc => "A string arg" } ) result = [] should = [] assert_nothing_raised("Add args failed") do @config.addargs(result) end @config.each do |name, element| if name == :onboolean should << ["--onboolean", GetoptLong::NO_ARGUMENT] should << ["--no-onboolean", GetoptLong::NO_ARGUMENT] elsif name == :string should << ["--string", GetoptLong::REQUIRED_ARGUMENT] end end assert_equal(should, result, "Add args functional test failed") end def test_groupsetting cfile = tempfile group = "yayness" File.open(cfile, "w") do |f| f.puts "[main] group = #{group} " end config = mkconfig - config.setdefaults(Puppet[:name], :group => ["puppet", "a group"], :config => [cfile, "eh"]) + config.setdefaults(:application, + :group => { :type => :string, :default => "puppet", :desc => "a group" }, + :config => { :type => :file, :default => cfile, :desc => "eh" }) assert_nothing_raised { config.parse } assert_equal(group, config[:group], "Group did not take") end # provide a method to modify and create files w/out specifying the info # already stored in a config def test_writingfiles File.umask(0022) path = tempfile mode = 0644 config = mkconfig - args = { :default => path, :mode => mode, :desc => "yay" } + args = { :type => :file, :default => path, :mode => mode, :desc => "yay" } user = nonrootuser group = nonrootgroup if Puppet.features.root? args[:owner] = user.name args[:group] = group.name end config.setdefaults(:testing, :myfile => args) assert_nothing_raised { config.write(:myfile) do |file| file.puts "yay" end } assert_equal(mode, filemode(path), "Modes are not equal") # OS X is broken in how it chgrps files if Puppet.features.root? assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") case Facter["operatingsystem"].value when /BSD/, "Darwin" # nothing else assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") end end end def test_mkdir File.umask(0022) path = tempfile mode = 0755 config = mkconfig - args = { :default => path, :mode => mode, :desc => "a file" } + args = { :type => :directory, :default => path, :mode => mode, :desc => "a file" } user = nonrootuser group = nonrootgroup if Puppet.features.root? args[:owner] = user.name args[:group] = group.name end config.setdefaults(:testing, :mydir => args) assert_nothing_raised { config.mkdir(:mydir) } assert_equal(mode, filemode(path), "Modes are not equal") # OS X and *BSD is broken in how it chgrps files if Puppet.features.root? assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") case Facter["operatingsystem"].value when /BSD/, "Darwin" # nothing else assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") end end end # Make sure that tags are ignored when configuring def test_configs_ignore_tags config = mkconfig file = tempfile config.setdefaults( :mysection, - :mydir => [file, "a file"] + :mydir => { :type => :directory, :default => file, :desc => "a file" } ) Puppet[:tags] = "yayness" assert_nothing_raised { config.use(:mysection) } assert(FileTest.directory?(file), "Directory did not get created") assert_equal( "yayness", Puppet[:tags], "Tags got changed during config") end def test_configs_replace_in_url config = mkconfig - config.setdefaults(:mysection, :host => ["yayness", "yay"]) - config.setdefaults(:mysection, :url => ["http://$host/rahness", "yay"]) + config.setdefaults(:mysection, :host => { :type => :string, :default => "yayness", :desc => "yay" }) + config.setdefaults(:mysection, :url => { :type => :string, :default => "http://$host/rahness", :desc => "yay" }) val = nil assert_nothing_raised { val = config[:url] } assert_equal( "http://yayness/rahness", val, "Settings got messed up") end - def test_correct_type_assumptions - file = Puppet::Util::Settings::FileSetting - setting = Puppet::Util::Settings::Setting - bool = Puppet::Util::Settings::BooleanSetting - - # We have to keep these ordered, unfortunately. - [ - ["/this/is/a/file", file], - ["true", bool], - [true, bool], - ["false", bool], - ["server", setting], - ["http://$server/yay", setting], - ["$server/yayness", file], - ["$server/yayness.conf", file] - ].each do |ary| - config = mkconfig - value, type = ary - name = value.to_s + "_setting" - assert_nothing_raised { - config.setdefaults(:yayness, name => { :default => value, :desc => name.to_s}) - } - elem = config.setting(name) - - - assert_instance_of( - type, elem, - - "#{value.inspect} got created as wrong type") - end - end + # These tests are all about guessing the setting type, which we're not doing any longer. + #def test_correct_type_assumptions + # file = Puppet::Util::Settings::FileSetting + # setting = Puppet::Util::Settings::StringSetting + # bool = Puppet::Util::Settings::BooleanSetting + # + # # We have to keep these ordered, unfortunately. + # [ + # ["/this/is/a/file", file], + # ["true", bool], + # [true, bool], + # ["false", bool], + # ["server", setting], + # ["http://$server/yay", setting], + # ["$server/yayness", file], + # ["$server/yayness.conf", file] + # ].each do |ary| + # config = mkconfig + # value, type = ary + # name = value.to_s + "_setting" + # assert_nothing_raised { + # config.setdefaults(:yayness, name => { :default => value, :desc => name.to_s}) + # } + # elem = config.setting(name) + # + # + # assert_instance_of( + # type, elem, + # + # "#{value.inspect} got created as wrong type") + # end + #end def test_parse_removes_quotes config = mkconfig - config.setdefaults(:mysection, :singleq => ["single", "yay"]) - config.setdefaults(:mysection, :doubleq => ["double", "yay"]) - config.setdefaults(:mysection, :none => ["noquote", "yay"]) - config.setdefaults(:mysection, :middle => ["midquote", "yay"]) + config.setdefaults(:mysection, :singleq => { :type => :string, :default => "single", :desc => "yay" }) + config.setdefaults(:mysection, :doubleq => { :type => :string, :default => "double", :desc => "yay" }) + config.setdefaults(:mysection, :none => { :type => :string, :default => "noquote", :desc => "yay" }) + config.setdefaults(:mysection, :middle => { :type => :string, :default => "midquote", :desc => "yay" }) file = tempfile # Set one parameter in the file File.open(file, "w") { |f| f.puts %{[main]\n singleq = 'one' doubleq = "one" none = one middle = mid"quote } } - config.setdefaults(:mysection, :config => [file, "eh"]) + config.setdefaults(:mysection, :config => { :type => :file, :default => file, :desc => "eh" }) assert_nothing_raised { config.parse } %w{singleq doubleq none}.each do |p| assert_equal("one", config[p], "#{p} did not match") end assert_equal('mid"quote', config["middle"], "middle did not match") end # Test that config parameters correctly call passed-in blocks when the value # is set. def test_paramblocks config = mkconfig testing = nil assert_nothing_raised do config.setdefaults :test, :blocktest => {:default => "yay", :desc => "boo", :hook => proc { |value| testing = value }} end elem = config.setting(:blocktest) assert_nothing_raised do assert_equal("yay", elem.value) end assert_nothing_raised do config[:blocktest] = "yaytest" end assert_nothing_raised do assert_equal("yaytest", elem.value) end assert_equal("yaytest", testing) assert_nothing_raised do config[:blocktest] = "another" end assert_nothing_raised do assert_equal("another", elem.value) end assert_equal("another", testing) # Now verify it works from setdefault assert_nothing_raised do config.setdefaults :test, :blocktest2 => { :default => "yay", :desc => "yay", :hook => proc { |v| testing = v } } end assert_equal("yay", config[:blocktest2]) assert_nothing_raised do config[:blocktest2] = "footest" end assert_equal("footest", config[:blocktest2]) assert_equal("footest", testing) end def test_no_modify_root config = mkconfig - config.setdefaults( - :yay, - :mydir => {:default => tempfile, - - :mode => 0644, - :owner => "root", - :group => "service", - :desc => "yay" - }, - :mkusers => [false, "yay"] + config.setdefaults(:yay, + :mydir => { + :type => :file, + :default => tempfile, + :mode => 0644, + :owner => "root", + :group => "service", + :desc => "yay" + }, + :mkusers => { :type => :boolean, :default => false, :desc => "yay" } ) assert_nothing_raised do config.use(:yay) end # Now enable it so they'll be added config[:mkusers] = true comp = config.to_catalog comp.vertices.find_all { |r| r.class.name == :user }.each do |u| assert(u.name != "root", "Tried to manage root user") end comp.vertices.find_all { |r| r.class.name == :group }.each do |u| assert(u.name != "root", "Tried to manage root group") assert(u.name != "wheel", "Tried to manage wheel group") end # assert(yay, "Did not find yay component") # yay.each do |c| # puts @config.ref # end # assert(! yay.find { |o| o.class.name == :user and o.name == "root" }, # "Found root user") # assert(! yay.find { |o| o.class.name == :group and o.name == "root" }, # "Found root group") end # #415 def test_remove_trailing_spaces config = mkconfig file = tempfile File.open(file, "w") { |f| f.puts "rah = something " } - config.setdefaults(:yay, :config => [file, "eh"], :rah => ["testing", "a desc"]) + config.setdefaults(:yay, :config => { :type => :file, :default => file, :desc => "eh" }, :rah => { :type => :string, :default => "testing", :desc => "a desc" }) assert_nothing_raised { config.parse } assert_equal("something", config[:rah], "did not remove trailing whitespace in parsing") end # #484 def test_parsing_unknown_variables logstore config = mkconfig file = tempfile File.open(file, "w") { |f| f.puts %{[main]\n one = one two = yay } } - config.setdefaults(:mysection, :config => [file, "eh"], :one => ["yay", "yay"]) + config.setdefaults(:mysection, :config => { :type => :file, :default => file, :desc => "eh" }, :one => { :type => :string, :default => "yay", :desc => "yay" }) assert_nothing_raised("Unknown parameter threw an exception") do config.parse end end def test_multiple_interpolations @config.setdefaults( :section, - :one => ["oneval", "yay"], - :two => ["twoval", "yay"], + :one => { :type => :string, :default => "oneval", :desc => "yay" }, + :two => { :type => :string, :default => "twoval", :desc => "yay" }, - :three => ["$one/$two", "yay"] + :three => { :type => :string, :default => "$one/$two", :desc => "yay" } ) assert_equal( "oneval/twoval", @config[:three], "Did not interpolate multiple variables") end # Make sure we can replace ${style} var names def test_curly_replacements @config.setdefaults( :section, - :one => ["oneval", "yay"], - :two => ["twoval", "yay"], + :one => { :type => :string, :default => "oneval", :desc => "yay" }, + :two => { :type => :string, :default => "twoval", :desc => "yay" }, - :three => ["$one/${two}/${one}/$two", "yay"] + :three => { :type => :string, :default => "$one/${two}/${one}/$two", :desc => "yay" } ) assert_equal( "oneval/twoval/oneval/twoval", @config[:three], "Did not interpolate curlied variables") end # Test to make sure that we can set and get a short name def test_setting_short_name setting= nil assert_nothing_raised("Could not create setting") do setting= Setting.new :short => "n", :desc => "anything", :settings => Puppet::Util::Settings.new end assert_equal("n", setting.short, "Short value is not retained") assert_raise(ArgumentError,"Allowed multicharactered short names.") do setting= Setting.new :short => "no", :desc => "anything", :settings => Puppet::Util::Settings.new end end # Test to make sure that no two celements have the same short name def test_celement_short_name_not_duplicated config = mkconfig assert_nothing_raised("Could not create celement with short name.") do config.setdefaults( :main, :one => { :default => "blah", :desc => "anything", :short => "o" }) end assert_nothing_raised("Could not create second celement with short name.") do config.setdefaults( :main, :two => { :default => "blah", :desc => "anything", :short => "i" }) end assert_raise(ArgumentError, "Could create second celement with duplicate short name.") do config.setdefaults( :main, :three => { :default => "blah", :desc => "anything", :short => "i" }) end # make sure that when the above raises an expection that the config is not included assert(!config.include?(:three), "Invalid configuration item was retained") end # Tell getopt which arguments are valid def test_get_getopt_args element = Setting.new :name => "foo", :desc => "anything", :settings => Puppet::Util::Settings.new assert_equal([["--foo", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") element.short = "n" assert_equal([["--foo", "-n", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") element = BooleanSetting.new :name => "foo", :desc => "anything", :settings => Puppet::Util::Settings.new assert_equal( [["--foo", GetoptLong::NO_ARGUMENT], ["--no-foo", GetoptLong::NO_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") element.short = "n" assert_equal( [["--foo", "-n", GetoptLong::NO_ARGUMENT],["--no-foo", GetoptLong::NO_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") end end