diff --git a/acceptance/tests/environment/cmdline_overrides_environment.rb b/acceptance/tests/environment/cmdline_overrides_environment.rb index 1518431b8..a50dd2268 100644 --- a/acceptance/tests/environment/cmdline_overrides_environment.rb +++ b/acceptance/tests/environment/cmdline_overrides_environment.rb @@ -1,197 +1,258 @@ test_name "Commandline modulepath and manifest settings override environment" testdir = master.tmpdir('cmdline_and_environment') environmentpath = "#{testdir}/environments" modulepath = "#{testdir}/modules" manifests = "#{testdir}/manifests" sitepp = "#{manifests}/site.pp" other_manifestdir = "#{testdir}/other_manifests" other_sitepp = "#{other_manifestdir}/site.pp" other_modulepath = "#{testdir}/some_other_modulepath" cmdline_manifest = "#{testdir}/cmdline.pp" step "Prepare manifests and modules" apply_manifest_on(master, <<-MANIFEST, :catch_failures => true) File { ensure => directory, owner => puppet, mode => 0700, } ############################################## -# The default production directory environment +# A production directory environment file { "#{testdir}":; "#{environmentpath}":; "#{environmentpath}/production":; "#{environmentpath}/production/manifests":; "#{environmentpath}/production/modules":; "#{environmentpath}/production/modules/amod":; "#{environmentpath}/production/modules/amod/manifests":; } file { "#{environmentpath}/production/modules/amod/manifests/init.pp": ensure => file, content => 'class amod { notify { "amod from production environment": } }' } file { "#{environmentpath}/production/manifests/production.pp": ensure => file, content => ' notify { "in production.pp": } include amod ' } ############################################################## # To be set as default manifests and modulepath in puppet.conf file { "#{modulepath}":; "#{modulepath}/amod/":; "#{modulepath}/amod/manifests":; } file { "#{modulepath}/amod/manifests/init.pp": ensure => file, content => 'class amod { notify { "amod from modulepath": } }' } file { "#{manifests}": } file { "#{sitepp}": ensure => file, content => ' notify { "in site.pp": } include amod ' } file { "#{other_manifestdir}": } file { "#{other_sitepp}": ensure => file, content => ' notify { "in other manifestdir site.pp": } include amod ' } ################################ # To be specified on commandline file { "#{other_modulepath}":; "#{other_modulepath}/amod/":; "#{other_modulepath}/amod/manifests":; } file { "#{other_modulepath}/amod/manifests/init.pp": ensure => file, content => 'class amod { notify { "amod from commandline modulepath": } }' } file { "#{cmdline_manifest}": ensure => file, content => ' notify { "in cmdline.pp": } include amod ' } MANIFEST master_opts = { 'master' => { 'environmentpath' => environmentpath, 'manifest' => sitepp, 'modulepath' => modulepath, } } # Note: this is the semantics seen with legacy environments if commandline # manifest/modulepath are set. -step "puppet master with --manifest and --modulepath overrides existing default production directory environment" do +step "puppet master with --manifest and --modulepath overrides set production directory environment" do master_opts = master_opts.merge(:__commandline_args__ => "--manifest=#{cmdline_manifest} --modulepath=#{other_modulepath}") with_puppet_running_on master, master_opts, testdir do agents.each do |agent| on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in cmdline\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) assert_no_match(/production/, stdout) end step "even if environment is specified" on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do assert_match(/in cmdline\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) assert_no_match(/production/, stdout) end end end step "or if you set --manifestdir" do master_opts = master_opts.merge(:__commandline_args__ => "--manifestdir=#{other_manifestdir} --modulepath=#{other_modulepath}") step "it is ignored if manifest is set in puppet.conf to something not using $manifestdir" with_puppet_running_on master, master_opts, testdir do agents.each do |agent| on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2]) do assert_match(/in production\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) end end end step "but does pull in the default manifest via manifestdir if manifest is not set" master_opts = master_opts.merge(:__commandline_args__ => "--manifestdir=#{other_manifestdir} --modulepath=#{other_modulepath}") master_opts['master'].delete('manifest') with_puppet_running_on master, master_opts, testdir do agents.each do |agent| on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2]) do assert_match(/in other manifestdir site\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) assert_no_match(/production/, stdout) end end end end end -step "puppet master with manifest and modulepath set in puppet.conf is overriden by an existing default production directory" do +step "puppet master with manifest and modulepath set in puppet.conf is overriden by an existing and set production directory environment" do with_puppet_running_on master, master_opts, testdir do agents.each do |agent| step "this case is unfortunate, but will be irrelevant when we remove legacyenv in 4.0" on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) end step "if environment is specified" on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) end end end end -step "puppet master with default manifest, modulepath, environment, environmentpath and an existing default production directory environment directory" do +step "puppet master with default manifest, modulepath, environment, environmentpath and an existing '#{environmentpath}/production' directory environment that has not been set" do ssldir = on(master, puppet("master --configprint ssldir")).stdout.chomp master_opts = { :__commandline_args__ => "--confdir=#{testdir} --ssldir=#{ssldir}" } with_puppet_running_on master, master_opts, testdir do agents.each do |agent| - step "default production directory environment takes precedence" + step "#{environmentpath}/production directory environment does not take precedence because default environmentpath is ''" + on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do + assert_match(/in site\.pp/, stdout) + assert_match(/amod from modulepath/, stdout) + end + on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do + assert_match(/in site\.pp/, stdout) + assert_match(/amod from modulepath/, stdout) + end + end + end +end + +step "puppet master with explicit dynamic environment settings and empty environmentpath" do + step "Prepare an additional modulepath module" + apply_manifest_on(master, <<-MANIFEST, :catch_failures => true) + File { + ensure => directory, + owner => puppet, + mode => 0700, + } + + # A second module in another modules dir + file { + "#{other_modulepath}":; + "#{other_modulepath}/bmod/":; + "#{other_modulepath}/bmod/manifests":; + } + + file { "#{other_modulepath}/bmod/manifests/init.pp": + ensure => file, + content => 'class bmod { + notify { "bmod from other modulepath": } + }' + } + + file { "#{environmentpath}/production/manifests/production.pp": + ensure => file, + content => ' + notify { "in production.pp": } + include amod + include bmod + ' + } + MANIFEST + + master_opts = { + 'master' => { + 'manifest' => "#{environmentpath}/$environment/manifests", + 'modulepath' => "#{environmentpath}/$environment/modules:#{other_modulepath}", + } + } + + with_puppet_running_on master, master_opts, testdir do + agents.each do |agent| + step "pulls in the production environment based on $environment default" on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) + step "and sees modules located in later elements of the modulepath (which would not be seen by a directory env (PUP-2158)" + assert_match(/bmod from other modulepath/, stdout) end - on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do + + step "pulls in the production environment when explicitly set" + on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2] ) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) + step "and sees modules located in later elements of the modulepath (which would not be seen by a directory env (PUP-2158)" + assert_match(/bmod from other modulepath/, stdout) end end end end diff --git a/lib/puppet.rb b/lib/puppet.rb index 4c74230f6..9e5e8baa8 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,247 +1,256 @@ require 'puppet/version' # see the bottom of the file for further inclusions # Also see the new Vendor support - towards the end # require 'facter' require 'puppet/error' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' require 'puppet/util/run_mode' require 'puppet/external/pson/common' require 'puppet/external/pson/version' require 'puppet/external/pson/pure' #------------------------------------------------------------ # 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' # The main Puppet class. Everything is contained here. # # @api public module Puppet require 'puppet/file_system' require 'puppet/context' require 'puppet/environments' class << self include Puppet::Util attr_reader :features attr_writer :name end # the hash that determines how our system behaves @@settings = Puppet::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.define_settings(section, hash) @@settings.define_settings(section, hash) end # Get the value for a setting # # @param [Symbol] param the setting to retrieve # # @api public def self.[](param) if param == :debug return Puppet::Util::Log.level == :debug else return @@settings[param] end end # setting 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 # This sucks (the existence of this method); there are a lot of places in our code that branch based the value of # "run mode", but there used to be some really confusing code paths that made it almost impossible to determine # when during the lifecycle of a puppet application run the value would be set properly. A lot of the lifecycle # stuff has been cleaned up now, but it still seems frightening that we rely so heavily on this value. # # I'd like to see about getting rid of the concept of "run_mode" entirely, but there are just too many places in # the code that call this method at the moment... so I've settled for isolating it inside of the Settings class # (rather than using a global variable, as we did previously...). Would be good to revisit this at some point. # # --cprice 2012-03-16 Puppet::Util::RunMode[@@settings.preferred_run_mode] end # Load all of the settings. 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. # @deprecated Use {initialize_settings} def self.parse_config() Puppet.deprecation_warning("Puppet.parse_config is deprecated; please use Faces API (which will handle settings and state management for you), or (less desirable) call Puppet.initialize_settings") Puppet.initialize_settings end # Initialize puppet's settings. This is intended only for use by external tools that are not # built off of the Faces API or the Puppet::Util::Application class. It may also be used # to initialize state so that a Face may be used programatically, rather than as a stand-alone # command-line tool. # # @api public # @param args [Array] the command line arguments to use for initialization # @return [void] def self.initialize_settings(args = []) do_initialize_settings_for_run_mode(:user, args) end # Initialize puppet's settings for a specified run_mode. # # @deprecated Use {initialize_settings} def self.initialize_settings_for_run_mode(run_mode) Puppet.deprecation_warning("initialize_settings_for_run_mode may be removed in a future release, as may run_mode itself") do_initialize_settings_for_run_mode(run_mode, []) end # private helper method to provide the implementation details of initializing for a run mode, # but allowing us to control where the deprecation warning is issued def self.do_initialize_settings_for_run_mode(run_mode, args) Puppet.settings.initialize_global_settings(args) run_mode = Puppet::Util::RunMode[run_mode] Puppet.settings.initialize_app_defaults(Puppet::Settings.app_defaults_for_run_mode(run_mode)) Puppet.push_context(Puppet.base_context(Puppet.settings), "Initial context after settings initialization") Puppet::Parser::Functions.reset end private_class_method :do_initialize_settings_for_run_mode # 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 # Load vendored (setup paths, and load what is needed upfront). # See the Vendor class for how to add additional vendored gems/code require "puppet/vendor" Puppet::Vendor.load_vendored # Set default for YAML.load to unsafe so we don't affect programs # requiring puppet -- in puppet we will call safe explicitly SafeYAML::OPTIONS[:default_mode] = :unsafe # The bindings used for initialization of puppet # @api private def self.base_context(settings) environments = settings[:environmentpath] modulepath = Puppet::Node::Environment.split_path(settings[:basemodulepath]) - loaders = Puppet::Environments::Directories.from_path(environments, modulepath) - loaders << Puppet::Environments::Legacy.new + if environments.empty? + loaders = [Puppet::Environments::Legacy.new] + else + loaders = Puppet::Environments::Directories.from_path(environments, modulepath) + # in case the configured environment (used for the default sometimes) + # doesn't exist + loaders << Puppet::Environments::StaticPrivate.new( + Puppet::Node::Environment.create(Puppet[:environment].to_sym, + [], + Puppet::Node::Environment::NO_MANIFEST)) + end { :environments => Puppet::Environments::Combined.new(*loaders) } end # A simple set of bindings that is just enough to limp along to # initialization where the {base_context} bindings are put in place # @api private def self.bootstrap_context root_environment = Puppet::Node::Environment.create(:'*root*', [], '') { :current_environment => root_environment, :root_environment => root_environment } end # @param overrides [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @api private def self.push_context(overrides, description = "") @context.push(overrides, description) end # Return to the previous context. # @raise [StackUnderflow] if the current context is the root # @api private def self.pop_context @context.pop end # Lookup a binding by name or return a default value provided by a passed block (if given). # @api private def self.lookup(name, &block) @context.lookup(name, &block) end # @param bindings [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @yield [] A block executed in the context of the temporarily pushed bindings. # @api private def self.override(bindings, description = "", &block) @context.override(bindings, description, &block) end require 'puppet/node' # The single instance used for normal operation @context = Puppet::Context.new(bootstrap_context) end # This feels weird to me; I would really like for us to get to a state where there is never a "require" statement # anywhere besides the very top of a file. That would not be possible at the moment without a great deal of # effort, but I think we should strive for it and revisit this at some point. --cprice 2012-03-16 require 'puppet/indirector' require 'puppet/type' require 'puppet/resource' require 'puppet/parser' require 'puppet/network' require 'puppet/ssl' require 'puppet/module' require 'puppet/data_binding' require 'puppet/util/storage' require 'puppet/status' require 'puppet/file_bucket/file' diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index f5fb424f2..c91274134 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,1876 +1,1877 @@ module Puppet def self.default_diffargs if (Facter.value(:kernel) == "AIX" && Facter.value(:kernelmajversion) == "5300") "" else "-u" end end ############################################################################################ # NOTE: For information about the available values for the ":type" property of settings, # see the docs for Settings.define_settings ############################################################################################ AS_DURATION = %q{This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y).} STORECONFIGS_ONLY = %q{This setting is only used by the ActiveRecord storeconfigs and inventory backends, which are deprecated.} define_settings(:main, :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 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 => { :default => nil, :type => :directory, :owner => "service", :group => "service", :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", }, ### NOTE: this setting is usually being set to a symbol value. We don't officially have a ### setting type for that yet, but we might want to consider creating one. :name => { :default => nil, :desc => "The name of the application, if we are running as one. The default is essentially $0 without the path or `.rb`.", } ) define_settings(:main, :logdir => { :default => nil, :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store log files", } ) define_settings(:main, :priority => { :default => nil, :type => :priority, :desc => "The scheduling priority of the process. Valid values are 'high', 'normal', 'low', or 'idle', which are mapped to platform-specific values. The priority can also be specified as an integer value and will be passed as is, e.g. -5. Puppet must be running as a privileged user in order to increase scheduling priority.", }, :trace => { :default => false, :type => :boolean, :desc => "Whether to print stack traces on some errors", }, :profile => { :default => false, :type => :boolean, :desc => "Whether to enable experimental performance profiling", }, :autoflush => { :default => true, :type => :boolean, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => { :default => "daemon", :desc => "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", :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 => nil, :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "Where Puppet PID files are kept." }, :genconfig => { :default => false, :type => :boolean, :desc => "When true, causes Puppet applications to print an example config file to stdout and exit. The example will include descriptions of each setting, and the current (or default) value of each setting, incorporating any settings overridden on the CLI (with the exception of `genconfig` itself). This setting only makes sense when specified on the command line as `--genconfig`.", }, :genmanifest => { :default => false, :type => :boolean, :desc => "Whether to just print a manifest to stdout and exit. Only makes sense when specified on the command line as `--genmanifest`. Takes into account arguments specified on the CLI.", }, :configprint => { :default => "", :desc => "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'.", }, :color => { :default => "ansi", :type => :string, :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 => { :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 => "Perform one configuration run and exit, rather than spawning a long-running daemon. This is useful for interactively running puppet agent, or running puppet agent from cron.", :short => 'o', }, :path => { :default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process.", :call_hook => :on_define_and_write, :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) Puppet::Util::Platform.default_paths.each do |path| ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) end value end }, :libdir => { :type => :directory, :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\n", :call_hook => :on_initialize_and_write, :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => { :default => false, :type => :boolean, :desc => "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.", }, :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." }, :environmentpath => { - :default => "$confdir/environments", - :desc => "A path of environment directories", + :default => "", + :desc => "A search path for environment directories, as a list of directories separated by the system + path separator character.", :type => :path, }, :diff_args => { :default => default_diffargs, :desc => "Which arguments to pass to the diff command when printing differences between 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 => { :type => :boolean, :default => false, :desc => "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.", }, :daemonize => { :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 => { :default => 4294967290, :desc => "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 => { :default => "$confdir/routes.yaml", :desc => "The YAML file containing indirector route configuration.", }, :node_terminus => { :type => :terminus, :default => "plain", :desc => "Where to find information about nodes.", }, :node_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached nodes. Valid values are (none), 'json', 'msgpack', 'yaml' or write only yaml ('write_only_yaml'). The master application defaults to 'write_only_yaml', all others to none.", }, :data_binding_terminus => { :type => :terminus, :default => "hiera", :desc => "Where to retrive information about data.", }, :hiera_config => { :default => "$confdir/hiera.yaml", :desc => "The hiera configuration file. Puppet only reads this file on startup, so you must restart the puppet master every time you edit it.", :type => :file, }, :binder => { :default => false, :desc => "Turns the binding system on or off. This includes bindings in modules. The binding system aggregates data from modules and other locations and makes them available for lookup. The binding system is experimental and any or all of it may change.", :type => :boolean, }, :binder_config => { :default => nil, :desc => "The binder configuration file. Puppet reads this file on each request to configure the bindings system. If set to nil (the default), a $confdir/binder_config.yaml is optionally loaded. If it does not exists, a default configuration is used. If the setting :binding_config is specified, it must reference a valid and existing yaml file.", :type => :file, }, :catalog_terminus => { :type => :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.", }, :catalog_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached catalogs. Valid values are 'json', 'msgpack' and 'yaml'. The agent application defaults to 'json'." }, :facts_terminus => { :default => 'facter', :desc => "The node facts terminus.", :call_hook => :on_initialize_and_write, :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.info "configuring the YAML fact cache because a remote terminus is active" Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => { :type => :terminus, :default => "$facts_terminus", :desc => "Should usually be the same as the facts terminus", }, :default_file_terminus => { :type => :terminus, :default => "rest", :desc => "The default source for files if no server is given in a uri, e.g. puppet:///file. The default of `rest` causes the file to be retrieved using the `server` setting. When running `apply` the default is `file_server`, causing requests to be filled locally." }, :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 => "15s", :type => :duration, :desc => "The minimum time to wait 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. #{AS_DURATION}", }, :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 `puppet queue` be running.", :hook => proc do |value| if value # This reconfigures the termini for Node, Facts, and Catalog Puppet.settings.override_default(:storeconfigs, true) # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue Puppet.settings.override_default(:catalog_cache_terminus, :queue) else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => { :default => false, :type => :boolean, :desc => "Boolean; whether Puppet should store only facts and exported resources in the storeconfigs database. This will improve the performance of exported resources with the older `active_record` backend, but will disable external tools that search the storeconfigs database. Thinning catalogs is generally unnecessary when using PuppetDB to store catalogs.", :hook => proc do |value| Puppet.settings.override_default(:storeconfigs, true) if value end }, :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 => { :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 => { :default => false, :type => :boolean, :desc => "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.", }, :stringify_facts => { :default => true, :type => :boolean, :desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or hashes as fact values.", }, :trusted_node_data => { :default => false, :type => :boolean, :desc => "Stores trusted node data in a hash called $trusted. When true also prevents $trusted from being overridden in any scope.", }, :immutable_node_data => { :default => '$trusted_node_data', :type => :boolean, :desc => "When true, also prevents $trusted and $facts from being overridden in any scope", } ) Puppet.define_settings(:module_tool, :module_repository => { :default => 'https://forge.puppetlabs.com', :desc => "The module repository", }, :module_working_dir => { :default => '$vardir/puppet-module', :desc => "The directory into which module tool data is stored", }, :module_skeleton_dir => { :default => '$module_working_dir/skeleton', :desc => "The directory which the skeleton for module tool generate is stored.", } ) Puppet.define_settings( :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 => Puppet::Settings.default_certname.downcase, :desc => "The name to use when handling certificates. Defaults to the fully qualified domain name.", :call_hook => :on_define_and_write, # 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 => "$confdir/csr_attributes.yaml", :type => :file, :desc => < { :default => "$ssldir/certs", :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :type => :directory, :mode => 0771, :owner => "service", :group => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :type => :file, :mode => 0640, :owner => "service", :group => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :type => :file, :mode => 0640, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where each client stores the CA certificate." }, :ssl_client_ca_auth => { :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Certificate authorities who issue server certificates. SSL servers will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :ssl_server_ca_auth => { :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Certificate authorities who issue client certificates. SSL clients will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :hostcrl => { :default => "$ssldir/crl.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :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.", }, :certificate_expire_warning => { :default => "60d", :type => :duration, :desc => "The window of time leading up to a certificate's expiration that a notification will be logged. This applies to CA, master, and agent certificates. #{AS_DURATION}" } ) define_settings( :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 => 0755, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :type => :file, :owner => "service", :group => "service", :mode => 0640, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", }, :caprivatedir => { :default => "$cadir/private", :type => :directory, :owner => "service", :group => "service", :mode => 0750, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :type => :directory, :owner => "service", :group => "service", :mode => 0755, :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :type => :directory, :owner => "service", :group => "service", :mode => 0755, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :type => :file, :owner => "service", :group => "service", :mode => 0640, :desc => "Where the CA stores the password for the private key." }, :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", :type => :autosign, :desc => "Whether (and how) to autosign certificate requests. This setting is only relevant on a puppet master acting as a certificate authority (CA). Valid values are true (autosigns all certificate requests; not recommended), false (disables autosigning certificates), or the absolute path to a file. The file specified in this setting may be either a **configuration file** or a **custom policy executable.** Puppet will automatically determine what it is: If the Puppet user (see the `user` setting) can execute the file, it will be treated as a policy executable; otherwise, it will be treated as a config file. If a custom policy executable is configured, the CA puppet master will run it every time it receives a CSR. The executable will be passed the subject CN of the request _as a command line argument,_ and the contents of the CSR in PEM format _on stdin._ It should exit with a status of 0 if the cert should be autosigned and non-zero if the cert should not be autosigned. If a certificate request is not autosigned, it will persist for review. An admin user can use the `puppet cert sign` command to manually sign it, or can delete the request. For info on autosign configuration files, see [the guide to Puppet's config files](http://docs.puppetlabs.com/guides/configuring.html).", }, :allow_duplicate_certs => { :default => false, :type => :boolean, :desc => "Whether to allow a new certificate request to overwrite an existing certificate.", }, :ca_ttl => { :default => "5y", :type => :duration, :desc => "The default TTL for new certificates. #{AS_DURATION}" }, :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 => "The inventory file. This is a text file to which the CA writes a complete listing of all certificates." } ) # Define the config default. define_settings(:application, :config_file_name => { :type => :string, :default => Puppet::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 the current puppet application.", }, :pidfile => { :type => :file, :default => "$rundir/${run_mode}.pid", :desc => "The file containing the PID of a running process. This file is intended to be used by service management frameworks and monitoring systems to determine if a puppet process is still in the process table.", }, :bindaddress => { :default => "0.0.0.0", :desc => "The address a listening server should bind to.", } ) define_settings(:master, :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_or_directory, :desc => "The entry-point manifest file for puppet master or a directory of manifests to be evaluated in alphabetical order. Puppet manages this path as a directory if it exists or if the path ends with a / or \\.", }, :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", :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", :type => :file, :owner => "service", :group => "service", :mode => 0660, :create => true, :desc => "Where the puppet master web server logs." }, :masterport => { :default => 8140, :desc => "The port for puppet master traffic. For puppet master, this is the port to listen on; for puppet agent, this is the port to make requests on. Both applications use this setting to get the port.", }, :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)", }, :bucketdir => { :default => "$vardir/bucket", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :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 => { :default => true, :type => :boolean, :desc => "Whether the master should function as a certificate authority.", }, :basemodulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :type => :path, :desc => "The base non-environment specific search path for modules, included also in all directory environment and default legacy environment modulepaths.", }, :modulepath => { :default => "$basemodulepath", :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 => { :default => "HTTP_X_CLIENT_DN", :desc => "The header containing an authenticated client's SSL DN. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). Puppet will parse out the Common Name (CN) from the Distinguished Name (DN) and use the value of the CN field for authorization. Note that the name of the HTTP header gets munged by the web server common gateway inteface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-DN` header, this setting should be `HTTP_X_CLIENT_DN`.", }, :ssl_client_verify_header => { :default => "HTTP_X_CLIENT_VERIFY", :desc => "The header containing the status message of the client verification. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. Note that the name of the HTTP header gets munged by the web server common gateway inteface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-Verify` header, this setting should be `HTTP_X_CLIENT_VERIFY`.", }, # 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", :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", :type => :directory, :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => { :default => "store", :desc => "The list of report handlers to use. When using multiple report handlers, their names should be comma-separated, with whitespace allowed. (For example, `reports = http, tagmail`.) This setting is relevant to puppet master and puppet apply. The puppet master will call these report handlers with the reports it receives from agent nodes, and puppet apply will call them with its own report. (In all cases, the node applying the catalog must have `report = true`.) See the report reference for information on the built-in report handlers; custom report handlers can also be loaded from modules. (Report handlers are loaded from the lib directory, at `puppet/reports/NAME.rb`.)", }, :reportdir => { :default => "$vardir/reports", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports. Each node gets a separate subdirectory in this directory. This setting is only used when the `store` report processor is enabled (see the `reports` setting)."}, :reporturl => { :default => "http://localhost:3000/reports/upload", :desc => "The URL that reports should be forwarded to. This setting is only used when the `http` report processor is enabled (see the `reports` setting).", }, :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.", } ) define_settings(:metrics, :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 => { :default => "$runinterval", :type => :duration, :desc => "How often RRD should expect data. This should match how often the hosts report back to the server. #{AS_DURATION}", } ) define_settings(: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.", } ) define_settings(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :localconfig => { :default => "$statedir/localconfig", :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", :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", :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 => 0640, :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", :type => :file, :owner => "root", :mode => 0640, :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :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 puppet master server to which the puppet agent should connect." }, :use_srv_records => { :default => false, :type => :boolean, :desc => "Whether the server will search for SRV records in DNS for the current domain.", }, :srv_domain => { :default => "#{Puppet::Settings.domain_fact}", :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.", }, :default_schedules => { :default => true, :type => :boolean, :desc => "Boolean; whether to generate the default schedule resources. Setting this to false is useful for keeping external report processors clean of skipped schedule resources.", }, :puppetport => { :default => 8139, :desc => "Which port puppet agent listens on.", }, :noop => { :default => false, :type => :boolean, :desc => "Whether to apply catalogs in noop mode, which allows Puppet to partially simulate a normal run. This setting affects puppet agent and puppet apply. When running in noop mode, Puppet will check whether each resource is in sync, like it does when running normally. However, if a resource attribute is not in the desired state (as declared in the catalog), Puppet will take no action, and will instead report the changes it _would_ have made. These simulated changes will appear in the report sent to the puppet master, or be shown on the console if running puppet agent or puppet apply in the foreground. The simulated changes will not send refresh events to any subscribing or notified resources, although Puppet will log that a refresh event _would_ have been sent. **Important note:** [The `noop` metaparameter](http://docs.puppetlabs.com/references/latest/metaparameter.html#noop) allows you to apply individual resources in noop mode, and will override the global value of the `noop` setting. This means a resource with `noop => false` _will_ be changed if necessary, even when running puppet agent with `noop = true` or `--noop`. (Conversely, a resource with `noop => true` will only be simulated, even when noop mode is globally disabled.)", }, :runinterval => { :default => "30m", :type => :duration, :desc => "How often puppet agent applies the catalog. 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. #{AS_DURATION}", }, :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 => { :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 => { :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.override_default(:preferred_serialization_format, value) end } }, :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.", }, :report_serialization_format => { :default => "pson", :type => :enum, :values => ["pson", "yaml"], :desc => "The serialization format to use when sending reports to the `report_server`. Possible values are `pson` and `yaml`. This setting affects puppet agent, but not puppet apply (which processes its own reports). This should almost always be set to `pson`. It can be temporarily set to `yaml` to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to 'yaml' automatically if the agent detects an older master, so should never need to be set explicitly." }, :legacy_query_parameter_serialization => { :default => false, :type => :boolean, :desc => "The serialization format to use when sending file_metadata query parameters. Older versions of puppet master expect certain query parameters to be serialized as yaml, which is deprecated. This should almost always be false. It can be temporarily set to true to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to true automatically if the agent detects an older master, so should never need to be set explicitly." }, :agent_catalog_run_lockfile => { :default => "$statedir/agent_catalog_run.lock", :type => :string, # (#2888) Ensure this file is not added to the settings catalog. :desc => "A lock file to indicate that a puppet agent catalog run is currently in progress. The file contains the pid of the process that holds the lock on the catalog run.", }, :agent_disabled_lockfile => { :default => "$statedir/agent_disabled.lock", :type => :file, :desc => "A lock file to indicate that puppet agent runs have been administratively disabled. File contains a JSON object with state information.", }, :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 => { :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.", }, :ignoremissingtypes => { :default => false, :type => :boolean, :desc => "Skip searching for classes and definitions that were missing during a prior compilation. The list of missing objects is maintained per-environment and persists until the environment is cleared or the master is restarted.", }, :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.", }, :dynamicfacts => { :default => "memorysize,memoryfree,swapsize,swapfree", :desc => "(Deprecated) 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.", :hook => proc { |value| if value Puppet.deprecation_warning "The dynamicfacts setting is deprecated and will be ignored." end } }, :splaylimit => { :default => "$runinterval", :type => :duration, :desc => "The maximum time to delay before runs. Defaults to being the same as the run interval. #{AS_DURATION}", }, :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", :type => :directory, :mode => 0750, :desc => "Where FileBucket files are stored locally." }, :configtimeout => { :default => "2m", :type => :duration, :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. #{AS_DURATION}", }, :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", :type => :file, :mode => 0640, :desc => "Where puppet agent stores the last run report in yaml format." }, :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 => { :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 => { :default => "2m", :type => :duration, :desc => "How frequently puppet agent should ask for a signed certificate. When starting for the first time, puppet agent will submit a certificate signing request (CSR) to the server named in the `ca_server` setting (usually the puppet master); this may be autosigned, or may need to be approved by a human, depending on the CA server's configuration. Puppet agent cannot apply configurations until its approved certificate is available. Since the certificate may or may not be available immediately, puppet agent will repeatedly try to fetch it at this interval. You can turn off waiting for certificates by specifying a time of 0, in which case puppet agent will exit if it cannot get a cert. #{AS_DURATION}", }, :ordering => { :type => :enum, :values => ["manifest", "title-hash", "random"], :default => "title-hash", :desc => "How unrelated resources should be ordered when applying a catalog. Allowed values are `title-hash`, `manifest`, and `random`. This setting affects puppet agent and puppet apply, but not puppet master. * `title-hash` (the default) will order resources randomly, but will use the same order across runs and across nodes. * `manifest` will use the order in which the resources were declared in their manifest files. * `random` will order resources randomly and change their order with each run. This can work like a fuzzer for shaking out undeclared dependencies. Regardless of this setting's value, Puppet will always obey explicit dependencies set with the before/require/notify/subscribe metaparameters and the `->`/`~>` chaining arrows; this setting only affects the relative ordering of _unrelated_ resources." } ) define_settings(:inspect, :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. define_settings( :main, :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.", }, :pluginfactdest => { :type => :directory, :default => "$vardir/facts.d", :desc => "Where Puppet should store external facts that are being handled by pluginsync", }, :pluginfactsource => { :default => "puppet://$server/pluginfacts", :desc => "Where to retrieve external facts for pluginsync", }, :pluginsync => { :default => true, :type => :boolean, :desc => "Whether plugins should be synced with the central server.", }, :pluginsignore => { :default => ".svn CVS .git", :desc => "What files to ignore when pulling down plugins.", } ) # Central fact information. define_settings( :main, :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_hook => :on_initialize_and_write, # Call our hook with the default value, so we always get the value added to facter. :hook => proc do |value| paths = value.split(File::PATH_SEPARATOR) Facter.search(*paths) end } ) define_settings( :tagmail, :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.", }, :smtpserver => { :default => "none", :desc => "The server through which to send email reports.", }, :smtpport => { :default => 25, :desc => "The TCP port through which to send email reports.", }, :smtphelo => { :default => Facter["fqdn"].value, :desc => "The name by which we identify ourselves in SMTP HELO for reports. If you send to a smtpserver which does strict HELO checking (as with Postfix's `smtpd_helo_restrictions` access controls), you may need to ensure this resolves.", } ) define_settings( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :type => :file, :mode => 0660, :owner => "service", :group => "service", :desc => "The sqlite database file. #{STORECONFIGS_ONLY}" }, :dbadapter => { :default => "sqlite3", :desc => "The type of database to use. #{STORECONFIGS_ONLY}", }, :dbmigrate => { :default => false, :type => :boolean, :desc => "Whether to automatically migrate the database. #{STORECONFIGS_ONLY}", }, :dbname => { :default => "puppet", :desc => "The name of the database to use. #{STORECONFIGS_ONLY}", }, :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. #{STORECONFIGS_ONLY}", }, :dbuser => { :default => "puppet", :desc => "The database user for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbpassword => { :default => "puppet", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbconnections => { :default => '', :desc => "The number of database connections for networked databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}", }, :dbsocket => { :default => "", :desc => "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}", }, :railslog => { :default => "$logdir/rails.log", :type => :file, :mode => 0600, :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent. #{STORECONFIGS_ONLY}" }, :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`. #{STORECONFIGS_ONLY}", } ) define_settings( :couchdb, :couchdb_url => { :default => "http://127.0.0.1:5984/puppet", :desc => "The url where the puppet couchdb database will be created. Only used when `facts_terminus` is set to `couch`.", } ) define_settings( :transaction, :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 => { :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 => { :default => false, :type => :boolean, :desc => "Whether to print a transaction summary.", } ) define_settings( :main, :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.", } ) define_settings( :ldap, :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 => { :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 => { :default => "ldap", :desc => "The LDAP server. Only used if `node_terminus` is set to `ldap`.", }, :ldapport => { :default => 389, :desc => "The LDAP port. Only used if `node_terminus` is set to `ldap`.", }, :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 => { :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 => { :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.", } ) define_settings(: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_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value if not Puppet.settings[:async_storeconfigs] Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet.settings.override_default(:catalog_cache_terminus, :store_configs) end Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :type => :terminus, :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." } ) define_settings(:parser, :templatedir => { :default => "$vardir/templates", :type => :directory, :desc => "Where Puppet looks for template files. Can be a list of colon-separated directories.", }, :allow_variables_with_dashes => { :default => false, :desc => <<-'EOT' Permit hyphens (`-`) in variable names and issue deprecation warnings about them. This setting **should always be `false`;** setting it to `true` will cause subtle and wide-ranging bugs. It will be removed in a future version. Hyphenated variables caused major problems in the language, but were allowed between Puppet 2.7.3 and 2.7.14. If you used them during this window, we apologize for the inconvenience --- you can temporarily set this to `true` in order to upgrade, and can rename your variables at your leisure. Please revert it to `false` after you have renamed all affected variables. EOT }, :parser => { :default => "current", :desc => <<-'EOT' Selects the parser to use for parsing puppet manifests (in puppet DSL language/'.pp' files). Available choices are `current` (the default) and `future`. The `curent` parser means that the released version of the parser should be used. The `future` parser is a "time travel to the future" allowing early exposure to new language features. What these features are will vary from release to release and they may be invididually configurable. Available Since Puppet 3.2. EOT }, :evaluator => { :default => "future", :hook => proc do |value| if !['future', 'current'].include?(value) raise "evaluator can only be set to 'future' or 'current', got '#{value}'" end end, :desc => <<-'EOT' Which evaluator to use when compiling Puppet manifests. Valid values are `current` and `future` (the default). **Note:** This setting is only used when `parser = future`. It allows testers to turn off the `future` evaluator when doing detailed tests and comparisons of the new compilation system. Evaluation is the second stage of catalog compilation. After the parser converts a manifest to a model of expressions, the evaluator processes each expression. (For example, a resource declaration signals the evaluator to add a resource to the catalog). The `future` parser and evaluator are slated to become default in Puppet 4. Their purpose is to add new features and improve consistency and reliability. Available Since Puppet 3.5. EOT }, :max_errors => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation errors in case multiple errors have been detected. A value of 0 is the same as value 1. The count is per manifest. EOT }, :max_warnings => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation warnings in case multiple errors have been detected. A value of 0 is the same as value 1. The count is per manifest. EOT }, :max_deprecations => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation deprecation warnings in case multiple errors have been detected. A value of 0 is the same as value 1. The count is per manifest. EOT }, :strict_variables => { :default => false, :type => :boolean, :desc => <<-'EOT' Makes the parser raise errors when referencing unknown variables. (This does not affect referencing variables that are explicitly set to undef). EOT } ) define_settings(:puppetdoc, :document_all => { :default => false, :type => :boolean, :desc => "Whether to document all resources when using `puppet doc` to generate manifest documentation.", } ) end diff --git a/lib/puppet/environments.rb b/lib/puppet/environments.rb index 75471de89..1fe8c61cd 100644 --- a/lib/puppet/environments.rb +++ b/lib/puppet/environments.rb @@ -1,187 +1,203 @@ # @api private module Puppet::Environments # @api private module EnvironmentCreator # Create an anonymous environment. # # @param module_path [String] A list of module directories separated by the # PATH_SEPARATOR # @param manifest [String] The path to the manifest # @return A new environment with the `name` `:anonymous` # # @api private def for(module_path, manifest) Puppet::Node::Environment.create(:anonymous, module_path.split(File::PATH_SEPARATOR), manifest) end end # @!macro [new] loader_search_paths # A list of indicators of where the loader is getting its environments from. # @return [Array] The URIs of the load locations # # @!macro [new] loader_list # @return [Array] All of the environments known # to the loader # # @!macro [new] loader_get # Find a named environment # # @param name [String,Symbol] The name of environment to find # @return [Puppet::Node::Environment, nil] the requested environment or nil # if it wasn't found # A source of pre-defined environments. # # @api private class Static include EnvironmentCreator def initialize(*environments) @environments = environments end # @!macro loader_search_paths def search_paths ["data:text/plain,internal"] end # @!macro loader_list def list @environments end # @!macro loader_get def get(name) @environments.find do |env| env.name == name.intern end end end + # A source of unlisted pre-defined environments. + # + # Used only for internal bootstrapping environments which are not relevant + # to an end user (such as the fall back 'configured' environment). + # + # @api private + class StaticPrivate < Static + + # Unlisted + # + # @!macro loader_list + def list + [] + end + end + # Old-style environments that come either from explicit stanzas in # puppet.conf or from dynamic environments created from use of `$environment` # in puppet.conf. # # @example Explicit Stanza # [environment_name] # modulepath=/var/my_env/modules # # @example Dynamic Environments # [master] # modulepath=/var/$environment/modules # # @api private class Legacy include EnvironmentCreator # @!macro loader_search_paths def search_paths ["file://#{Puppet[:config]}"] end # @note The list of environments for the Legacy environments is always # empty. # # @!macro loader_list def list [] end # @note Because the Legacy system cannot list out all of its environments, # get is able to return environments that are not returned by a call to # {#list}. # # @!macro loader_get def get(name) Puppet::Node::Environment.new(name) end end # Reads environments from a directory on disk. Each environment is # represented as a sub-directory. The environment's manifest setting is the # `manifest` directory of the environment directory. The environment's # modulepath setting is the global modulepath (from the `[master]` section # for the master) prepended with the `modules` directory of the environment # directory. # # @api private class Directories def initialize(environment_dir, global_module_path) @environment_dir = environment_dir @global_module_path = global_module_path end # Generate an array of directory loaders from a path string. # @param path [String] path to environment directories # @param global_module_path [String] the global modulepath setting # @return [Array] An array # of configured directory loaders. def self.from_path(path, global_module_path) environments = path.split(File::PATH_SEPARATOR) environments.map do |dir| Puppet::Environments::Directories.new(dir, global_module_path) end end # @!macro loader_search_paths def search_paths ["file://#{@environment_dir}"] end # @!macro loader_list def list base = Puppet::FileSystem.path_string(@environment_dir) if Puppet::FileSystem.directory?(@environment_dir) Puppet::FileSystem.children(@environment_dir).select do |child| name = Puppet::FileSystem.basename_string(child) Puppet::FileSystem.directory?(child) && Puppet::Node::Environment.valid_name?(name) end.collect do |child| name = Puppet::FileSystem.basename_string(child) Puppet::Node::Environment.create( name.intern, [File.join(base, name, "modules")] + @global_module_path, File.join(base, name, "manifests")) end else [] end end # @!macro loader_get def get(name) list.find { |env| env.name == name.intern } end end # Combine together multiple loaders to act as one. # @api private class Combined def initialize(*loaders) @loaders = loaders end # @!macro loader_search_paths def search_paths @loaders.collect(&:search_paths).flatten end # @!macro loader_list def list @loaders.collect(&:list).flatten end # @!macro loader_get def get(name) @loaders.each do |loader| if env = loader.get(name) return env end end nil end end end diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb index 6bbdd87c3..eca80ed07 100644 --- a/lib/puppet/network/http/api/v1.rb +++ b/lib/puppet/network/http/api/v1.rb @@ -1,218 +1,222 @@ require 'puppet/network/authorization' class Puppet::Network::HTTP::API::V1 include Puppet::Network::Authorization # How we map http methods and the indirection name in the URI # to an indirection method. METHOD_MAP = { "GET" => { :plural => :search, :singular => :find }, "POST" => { :singular => :find, }, "PUT" => { :singular => :save }, "DELETE" => { :singular => :destroy }, "HEAD" => { :singular => :head } } def self.routes Puppet::Network::HTTP::Route.path(/.*/).any(new) end # handle an HTTP request def call(request, response) indirection_name, method, key, params = uri2indirection(request.method, request.path, request.params) certificate = request.client_cert check_authorization(method, "/#{indirection_name}/#{key}", params) indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym) raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless indirection if !indirection.allow_remote_requests? # TODO: should we tell the user we found an indirection but it doesn't # allow remote requests, or just pretend there's no handler at all? what # are the security implications for the former? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("No handler for #{indirection.name}", :NO_INDIRECTION_REMOTE_REQUESTS) end trusted = Puppet::Context::TrustedInformation.remote(params[:authenticated], params[:node], certificate) Puppet.override(:trusted_information => trusted) do send("do_#{method}", indirection, key, params, request, response) end rescue Puppet::Network::HTTP::Error::HTTPError => e return do_http_control_exception(response, e) rescue Exception => e return do_exception(response, e) end def uri2indirection(http_method, uri, params) environment, indirection, key = uri.split("/", 4)[1..-1] # the first field is always nil because of the leading slash raise ArgumentError, "The environment must be purely alphanumeric, not '#{environment}'" unless Puppet::Node::Environment.valid_name?(environment) raise ArgumentError, "The indirection name must be purely alphanumeric, not '#{indirection}'" unless indirection =~ /^\w+$/ method = indirection_method(http_method, indirection) configured_environment = Puppet.lookup(:environments).get(environment) - configured_environment = configured_environment.override_from_commandline(Puppet.settings) - params[:environment] = configured_environment + if configured_environment.nil? + raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Could not find #{environment}", Puppet::Network::HTTP::Issues::ENVIRONMENT_NOT_FOUND) + else + configured_environment = configured_environment.override_from_commandline(Puppet.settings) + params[:environment] = configured_environment + end params.delete(:bucket_path) raise ArgumentError, "No request key specified in #{uri}" if key == "" or key.nil? key = URI.unescape(key) [indirection, method, key, params] end private def do_http_control_exception(response, exception) msg = exception.message Puppet.info(msg) response.respond_with(exception.status, "text/plain", msg) end def do_exception(response, exception, status=400) if exception.is_a?(Puppet::Network::AuthorizationError) # make sure we return the correct status code # for authorization issues status = 403 if status == 400 end Puppet.log_exception(exception) response.respond_with(status, "text/plain", exception.to_s) end # Execute our find. def do_find(indirection, key, params, request, response) unless result = indirection.find(key, params) raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Could not find #{indirection.name} #{key}", Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end format = accepted_response_formatter_for(indirection.model, request) rendered_result = result if result.respond_to?(:render) Puppet::Util::Profiler.profile("Rendered result in #{format}") do rendered_result = result.render(format) end end Puppet::Util::Profiler.profile("Sent response") do response.respond_with(200, format, rendered_result) end end # Execute our head. def do_head(indirection, key, params, request, response) unless indirection.head(key, params) raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Could not find #{indirection.name} #{key}", Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end # No need to set a response because no response is expected from a # HEAD request. All we need to do is not die. end # Execute our search. def do_search(indirection, key, params, request, response) result = indirection.search(key, params) if result.nil? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Could not find instances in #{indirection.name} with '#{key}'", Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end format = accepted_response_formatter_for(indirection.model, request) response.respond_with(200, format, indirection.model.render_multiple(format, result)) end # Execute our destroy. def do_destroy(indirection, key, params, request, response) formatter = accepted_response_formatter_or_yaml_for(indirection.model, request) result = indirection.destroy(key, params) response.respond_with(200, formatter, formatter.render(result)) end # Execute our save. def do_save(indirection, key, params, request, response) formatter = accepted_response_formatter_or_yaml_for(indirection.model, request) sent_object = read_body_into_model(indirection.model, request) result = indirection.save(sent_object, key) response.respond_with(200, formatter, formatter.render(result)) end def accepted_response_formatter_for(model_class, request) accepted_formats = request.headers['accept'] or raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new("Missing required Accept header", Puppet::Network::HTTP::Issues::MISSING_HEADER_FIELD) request.response_formatter_for(model_class.supported_formats, accepted_formats) end def accepted_response_formatter_or_yaml_for(model_class, request) accepted_formats = request.headers['accept'] || "yaml" request.response_formatter_for(model_class.supported_formats, accepted_formats) end def read_body_into_model(model_class, request) data = request.body.to_s format = request.format model_class.convert_from(format, data) end def indirection_method(http_method, indirection) raise ArgumentError, "No support for http method #{http_method}" unless METHOD_MAP[http_method] unless method = METHOD_MAP[http_method][plurality(indirection)] raise ArgumentError, "No support for plurality #{plurality(indirection)} for #{http_method} operations" end method end def self.indirection2uri(request) indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s "/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}#{request.query_string}" end def self.request_to_uri_and_body(request) indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s ["/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}", request.query_string.sub(/^\?/,'')] end def self.pluralize(indirection) return(indirection == "status" ? "statuses" : indirection + "s") end def plurality(indirection) # NOTE This specific hook for facts is ridiculous, but it's a *many*-line # fix to not need this, and our goal is to move away from the complication # that leads to the fix being too long. return :singular if indirection == "facts" return :singular if indirection == "status" return :singular if indirection == "certificate_status" return :plural if indirection == "inventory" result = (indirection =~ /s$|_search$/) ? :plural : :singular indirection.sub!(/s$|_search$/, '') indirection.sub!(/statuse$/, 'status') result end end diff --git a/lib/puppet/network/http/issues.rb b/lib/puppet/network/http/issues.rb index 9582675a8..2fd9000d7 100644 --- a/lib/puppet/network/http/issues.rb +++ b/lib/puppet/network/http/issues.rb @@ -1,9 +1,10 @@ module Puppet::Network::HTTP::Issues NO_INDIRECTION_REMOTE_REQUESTS = :NO_INDIRECTION_REMOTE_REQUESTS HANDLER_NOT_FOUND = :HANDLER_NOT_FOUND RESOURCE_NOT_FOUND = :RESOURCE_NOT_FOUND + ENVIRONMENT_NOT_FOUND = :ENVIRONMENT_NOT_FOUND RUNTIME_ERROR = :RUNTIME_ERROR MISSING_HEADER_FIELD = :MISSING_HEADER_FIELD UNSUPPORTED_FORMAT = :UNSUPPORTED_FORMAT UNSUPPORTED_METHOD = :UNSUPPORTED_METHOD end diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index 84a046922..f661fd01b 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,492 +1,496 @@ require 'puppet/util' require 'puppet/util/cacher' require 'monitor' require 'puppet/parser/parser_factory' # Just define it, so this class has fewer load dependencies. class Puppet::Node end # Puppet::Node::Environment acts as a container for all configuration # that is expected to vary between environments. # # ## The root environment # # In addition to normal environments that are defined by the user,there is a # special 'root' environment. It is defined as an instance variable on the # Puppet::Node::Environment metaclass. The environment name is `*root*` and can # be accessed by looking up the `:root_environment` using {Puppet.lookup}. # # The primary purpose of the root environment is to contain parser functions # that are not bound to a specific environment. The main case for this is for # logging functions. Logging functions are attached to the 'root' environment # when {Puppet::Parser::Functions.reset} is called. class Puppet::Node::Environment - include Puppet::Util::Cacher + NO_MANIFEST = :no_manifest + # @api private def self.seen @seen ||= {} end # Create a new environment with the given name, or return an existing one # # The environment class memoizes instances so that attempts to instantiate an # environment with the same name with an existing environment will return the # existing environment. # # @overload self.new(environment) # @param environment [Puppet::Node::Environment] # @return [Puppet::Node::Environment] the environment passed as the param, # this is implemented so that a calling class can use strings or # environments interchangeably. # # @overload self.new(string) # @param string [String, Symbol] # @return [Puppet::Node::Environment] An existing environment if it exists, # else a new environment with that name # # @overload self.new() # @return [Puppet::Node::Environment] The environment as set by # Puppet.settings[:environment] # # @api public def self.new(name = nil) return name if name.is_a?(self) name ||= Puppet.settings.value(:environment) raise ArgumentError, "Environment name must be specified" unless name symbol = name.to_sym return seen[symbol] if seen[symbol] obj = self.create(symbol, split_path(Puppet.settings.value(:modulepath, symbol)), Puppet.settings.value(:manifest, symbol)) seen[symbol] = obj end # Create a new environment with the given name # # @param name [Symbol] the name of the # @param modulepath [Array] the list of paths from which to load modules - # @param manifest [String] the path to the manifest for the environment + # @param manifest [String] the path to the manifest for the environment or + # the constant Puppet::Node::Environment::NO_MANIFEST if there is none. # @return [Puppet::Node::Environment] # # @api public - def self.create(name, modulepath, manifest) + def self.create(name, modulepath, manifest = NO_MANIFEST) obj = self.allocate obj.send(:initialize, name, expand_dirs(extralibs() + modulepath), - File.expand_path(manifest)) + manifest == NO_MANIFEST ? manifest : File.expand_path(manifest)) obj end # Instantiate a new environment # # @note {Puppet::Node::Environment.new} is overridden to return memoized # objects, so this will not be invoked with the normal Ruby initialization # semantics. # # @param name [Symbol] The environment name def initialize(name, modulepath, manifest) @name = name @modulepath = modulepath @manifest = manifest end # Creates a new Puppet::Node::Environment instance, overriding any of the passed # parameters. # # @param env_params [Hash<{Symbol => String,Array}>] new environment # parameters (:modulepath, :manifest) # @return [Puppet::Node::Environment] def override_with(env_params) return self.class.create(name, env_params[:modulepath] || modulepath, env_params[:manifest] || manifest) end # Creates a new Puppet::Node::Environment instance, overriding manfiest # and modulepath from the passed settings if they were originally set from # the commandline, or returns self if there is nothing to override. # # @param settings [Puppet::Settings] an initialized puppet settings instance # @return [Puppet::Node::Environment] new overridden environment or self if # there are no commandline changes from settings. def override_from_commandline(settings) overrides = {} overrides[:modulepath] = self.class.split_path(settings[:modulepath]) if settings.set_by_cli?(:modulepath) if settings.set_by_cli?(:manifest) || (settings.set_by_cli?(:manifestdir) && settings[:manifest].start_with?(settings[:manifestdir])) overrides[:manifest] = settings[:manifest] end overrides.empty? ? self : self.override_with(overrides) end # Retrieve the environment for the current process. # # @note This should only used when a catalog is being compiled. # # @api private # # @return [Puppet::Node::Environment] the currently set environment if one # has been explicitly set, else it will return the '*root*' environment def self.current Puppet.deprecation_warning("Puppet::Node::Environment.current has been replaced by Puppet.lookup(:current_environment), see http://links.puppetlabs.com/current-env-deprecation") Puppet.lookup(:current_environment) end # @param [String] name Environment name to check for valid syntax. # @return [Boolean] true if name is valid # @api public def self.valid_name?(name) !!name.match(/\A\w+\Z/) end # Clear all memoized environments and the 'current' environment # # @api private def self.clear seen.clear end # @!attribute [r] name # @api public # @return [Symbol] the human readable environment name that serves as the # environment identifier attr_reader :name # @api public # @return [Array] All directories present on disk in the modulepath def modulepath @modulepath.find_all do |p| Puppet::FileSystem.directory?(p) end end # @api public # @return [Array] All directories in the modulepath (even if they are not present on disk) def full_modulepath @modulepath end # @!attribute [r] manifest # @api public # @return [String] path to the manifest file or directory. attr_reader :manifest # Return an environment-specific Puppet setting. # # @api public # # @param param [String, Symbol] The environment setting to look up # @return [Object] The resolved setting value def [](param) Puppet.settings.value(param, self.name) end # @api public # @return [Puppet::Resource::TypeCollection] The current global TypeCollection def known_resource_types if @known_resource_types.nil? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import(), '') end @known_resource_types end # Yields each modules' plugin directory if the plugin directory (modulename/lib) # is present on the filesystem. # # @yield [String] Yields the plugin directory from each module to the block. # @api public def each_plugin_directory(&block) modules.map(&:plugin_directory).each do |lib| lib = Puppet::Util::Autoload.cleanpath(lib) yield lib if File.directory?(lib) end end # Locate a module instance by the module name alone. # # @api public # # @param name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module(name) modules.find {|mod| mod.name == name} end # Locate a module instance by the full forge name (EG authorname/module) # # @api public # # @param forge_name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module_by_forge_name(forge_name) author, modname = forge_name.split('/') found_mod = self.module(modname) found_mod and found_mod.forge_name == forge_name ? found_mod : nil end # @!attribute [r] modules # Return all modules for this environment in the order they appear in the # modulepath. # @note If multiple modules with the same name are present they will # both be added, but methods like {#module} and {#module_by_forge_name} # will return the first matching entry in this list. # @note This value is cached so that the filesystem doesn't have to be # re-enumerated every time this method is invoked, since that # enumeration could be a costly operation and this method is called # frequently. The cache expiry is determined by `Puppet[:filetimeout]`. # @see Puppet::Util::Cacher.cached_attr # @api public # @return [Array] All modules for this environment cached_attr(:modules, Puppet[:filetimeout]) do module_references = [] seen_modules = {} modulepath.each do |path| Dir.entries(path).each do |name| warn_about_mistaken_path(path, name) next if module_references.include?(name) if not seen_modules[name] module_references << {:name => name, :path => File.join(path, name)} seen_modules[name] = true end end end module_references.collect do |reference| begin Puppet::Module.new(reference[:name], reference[:path], self) rescue Puppet::Module::Error nil end end.compact end # Generate a warning if the given directory in a module path entry is named `lib`. # # @api private # # @param path [String] The module directory containing the given directory # @param name [String] The directory name def warn_about_mistaken_path(path, name) if name == "lib" Puppet.debug("Warning: Found directory named 'lib' in module path ('#{path}/lib'); unless " + "you are expecting to load a module named 'lib', your module path may be set " + "incorrectly.") end end # Modules broken out by directory in the modulepath # # @note This method _changes_ the current working directory while enumerating # the modules. This seems rather dangerous. # # @api public # # @return [Hash>] A hash whose keys are file # paths, and whose values is an array of Puppet Modules for that path def modules_by_path modules_by_path = {} modulepath.each do |path| Dir.chdir(path) do module_names = Dir.glob('*').select do |d| FileTest.directory?(d) && (File.basename(d) =~ /\A\w+(-\w+)*\Z/) end modules_by_path[path] = module_names.sort.map do |name| Puppet::Module.new(name, File.join(path, name), self) end end end modules_by_path end # All module requirements for all modules in the environment modulepath # # @api public # # @comment This has nothing to do with an environment. It seems like it was # stuffed into the first convenient class that vaguely involved modules. # # @example # environment.module_requirements # # => { # # 'username/amodule' => [ # # { # # 'name' => 'username/moduledep', # # 'version' => '1.2.3', # # 'version_requirement' => '>= 1.0.0', # # }, # # { # # 'name' => 'username/anotherdep', # # 'version' => '4.5.6', # # 'version_requirement' => '>= 3.0.0', # # } # # ] # # } # # # # @return [Hash>>] See the method example # for an explanation of the return value. def module_requirements deps = {} modules.each do |mod| next unless mod.forge_name deps[mod.forge_name] ||= [] mod.dependencies and mod.dependencies.each do |mod_dep| deps[mod_dep['name']] ||= [] dep_details = { 'name' => mod.forge_name, 'version' => mod.version, 'version_requirement' => mod_dep['version_requirement'] } deps[mod_dep['name']] << dep_details end end deps.each do |mod, mod_deps| deps[mod] = mod_deps.sort_by {|d| d['name']} end deps end # Set a periodic watcher on the file, so we can tell if it has changed. # @param filename [File,String] File instance or filename # @api private def watch_file(file) known_resource_types.watch_file(file.to_s) end def check_for_reparse if (Puppet[:code] != @parsed_code) || (@known_resource_types && @known_resource_types.require_reparse?) @parsed_code = nil @known_resource_types = nil end end # @return [String] The stringified value of the `name` instance variable # @api public def to_s name.to_s end # @return [Symbol] The `name` value, cast to a string, then cast to a symbol. # # @api public # # @note the `name` instance variable is a Symbol, but this casts the value # to a String and then converts it back into a Symbol which will needlessly # create an object that needs to be garbage collected def to_sym to_s.to_sym end # Return only the environment name when serializing. # # The only thing we care about when serializing an environment is its # identity; everything else is ephemeral and should not be stored or # transmitted. # # @api public def to_zaml(z) self.to_s.to_zaml(z) end def self.split_path(path_string) path_string.split(File::PATH_SEPARATOR) end def ==(other) return true if other.kind_of?(Puppet::Node::Environment) && self.name == other.name && self.full_modulepath == other.full_modulepath && self.manifest == other.manifest end def hash [self.class, name, full_modulepath, manifest].hash end private def self.extralibs() if ENV["PUPPETLIB"] split_path(ENV["PUPPETLIB"]) else [] end end def self.expand_dirs(dirs) dirs.collect do |dir| File.expand_path(dir) end end # Reparse the manifests for the given environment # # There are two sources that can be used for the initial parse: # # 1. The value of `Puppet.settings[:code]`: Puppet can take a string from # its settings and parse that as a manifest. This is used by various # Puppet applications to read in a manifest and pass it to the # environment as a side effect. This is attempted first. # 2. The contents of `Puppet.settings[:manifest]`: Puppet will try to load # the environment manifest. By default this is `$manifestdir/site.pp` # # @note This method will return an empty hostclass if # `Puppet.settings[:ignoreimport]` is set to true. # # @return [Puppet::Parser::AST::Hostclass] The AST hostclass object # representing the 'main' hostclass def perform_initial_import return empty_parse_result if Puppet[:ignoreimport] parser = Puppet::Parser::ParserFactory.parser(self) @parsed_code = Puppet[:code] if @parsed_code != "" parser.string = @parsed_code parser.parse else file = self.manifest # if the manifest file is a reference to a directory, parse and combine all .pp files in that # directory - if File.directory?(file) + if file == NO_MANIFEST + Puppet::Parser::AST::Hostclass.new('') + elsif File.directory?(file) parse_results = Dir.entries(file).find_all { |f| f =~ /\.pp$/ }.sort.map do |pp_file| parser.file = File.join(file, pp_file) parser.parse end # Use a parser type specific merger to concatenate the results Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results)) else parser.file = file parser.parse end end rescue => detail @known_resource_types.parse_failed = true msg = "Could not parse for environment #{self}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end # Return an empty toplevel hostclass to indicate that no file was loaded # # This is used as the return value of {#perform_initial_import} when # `Puppet.settings[:ignoreimport]` is true. # # @return [Puppet::Parser::AST::Hostclass] def empty_parse_result return Puppet::Parser::AST::Hostclass.new('') end end diff --git a/lib/puppet/test/test_helper.rb b/lib/puppet/test/test_helper.rb index 5c083715a..8bc5f43b3 100644 --- a/lib/puppet/test/test_helper.rb +++ b/lib/puppet/test/test_helper.rb @@ -1,216 +1,209 @@ require 'puppet/indirector/data_binding/hiera' require 'tmpdir' require 'fileutils' module Puppet::Test # This class is intended to provide an API to be used by external projects # when they are running tests that depend on puppet core. This should # allow us to vary the implementation details of managing puppet's state # for testing, from one version of puppet to the next--without forcing # the external projects to do any of that state management or be aware of # the implementation details. # # For now, this consists of a few very simple signatures. The plan is # that it should be the responsibility of the puppetlabs_spec_helper # to broker between external projects and this API; thus, if any # hacks are required (e.g. to determine whether or not a particular) # version of puppet supports this API, those hacks will be consolidated in # one place and won't need to be duplicated in every external project. # # This should also alleviate the anti-pattern that we've been following, # wherein each external project starts off with a copy of puppet core's # test_helper.rb and is exposed to risk of that code getting out of # sync with core. # # Since this class will be "library code" that ships with puppet, it does # not use API from any existing test framework such as rspec. This should # theoretically allow it to be used with other unit test frameworks in the # future, if desired. # # Note that in the future this API could potentially be expanded to handle # other features such as "around_test", but we didn't see a compelling # reason to deal with that right now. class TestHelper # Call this method once, as early as possible, such as before loading tests # that call Puppet. # @return nil def self.initialize() owner = Process.pid - @environmentdir = Dir.mktmpdir('environments') Puppet.push_context(Puppet.base_context({ - :environmentpath => @environmentdir, + :environmentpath => "", :basemodulepath => "", :manifest => "/dev/null" }), "Initial for specs") Puppet::Parser::Functions.reset - - ObjectSpace.define_finalizer(Puppet.lookup(:environments), proc { - if Process.pid == owner - FileUtils.rm_rf(@environmentdir) - end - }) end # Call this method once, when beginning a test run--prior to running # any individual tests. # @return nil def self.before_all_tests() # Make sure that all of the setup is also done for any before(:all) blocks end # Call this method once, at the end of a test run, when no more tests # will be run. # @return nil def self.after_all_tests() end # Call this method once per test, prior to execution of each invididual test. # @return nil def self.before_each_test() # 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 # The process environment is a shared, persistent resource. $old_env = ENV.to_hash # So is the load_path $old_load_path = $LOAD_PATH.dup initialize_settings_before_each() Puppet.push_context( { :trusted_information => Puppet::Context::TrustedInformation.new('local', 'testing', {}), }, "Context for specs") Puppet::Parser::Functions.reset Puppet::Node::Environment.clear Puppet::Application.clear! Puppet::Util::Profiler.clear Puppet.clear_deprecation_warnings Puppet::DataBinding::Hiera.instance_variable_set("@hiera", nil) end # Call this method once per test, after execution of each individual test. # @return nil def self.after_each_test() Puppet.settings.send(:clear_everything_for_tests) Puppet::Util::Storage.clear Puppet::Util::ExecutionStub.reset 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 = nil # Restore the global process environment. Can't just assign because this # is a magic variable, sadly, and doesn't do that™. It is sufficiently # faster to use the compare-then-set model to avoid excessive work that it # justifies the complexity. --daniel 2012-03-15 unless ENV.to_hash == $old_env ENV.clear $old_env.each {|k, v| ENV[k] = v } end # 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) # Restore the load_path late, to avoid messing with stubs from the test. $LOAD_PATH.clear $old_load_path.each {|x| $LOAD_PATH << x } Puppet.pop_context end ######################################################################################### # PRIVATE METHODS (not part of the public TestHelper API--do not call these from outside # of this class!) ######################################################################################### def self.app_defaults_for_tests() { :logdir => "/dev/null", :confdir => "/dev/null", :vardir => "/dev/null", :rundir => "/dev/null", :hiera_config => "/dev/null", } end private_class_method :app_defaults_for_tests def self.initialize_settings_before_each() Puppet.settings.preferred_run_mode = "user" # Initialize "app defaults" settings to a good set of test values Puppet.settings.initialize_app_defaults(app_defaults_for_tests) # 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 # 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 # Although we setup a testing context during initialization, some tests # will end up creating their own context using the real context objects # and use the setting for the environments. In order to avoid those tests # having to deal with a missing environmentpath we can just set it right # here. Puppet[:environmentpath] = @environmentpath end private_class_method :initialize_settings_before_each end end diff --git a/spec/integration/application/apply_spec.rb b/spec/integration/application/apply_spec.rb index 9847a14dc..902a59413 100755 --- a/spec/integration/application/apply_spec.rb +++ b/spec/integration/application/apply_spec.rb @@ -1,108 +1,110 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/application/apply' describe "apply" do include PuppetSpec::Files before :each do Puppet[:reports] = "none" end describe "when applying provided catalogs" do it "can apply catalogs provided in a file in pson" do file_to_create = tmpfile("pson_catalog") catalog = Puppet::Resource::Catalog.new resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"}) catalog.add_resource resource manifest = tmpfile("manifest") File.open(manifest, "w") { |f| f.print catalog.to_pson } puppet = Puppet::Application[:apply] puppet.options[:catalog] = manifest puppet.apply expect(Puppet::FileSystem.exist?(file_to_create)).to be_true expect(File.read(file_to_create)).to eq("my stuff") end end it "applies a given file even when a directory environment is specified" do manifest = tmpfile("manifest.pp") File.open(manifest, "w") do |f| f.puts <<-EOF notice('it was applied') EOF end - special = Puppet::Node::Environment.create(:special, [], '') + special = Puppet::Node::Environment.create(:special, []) Puppet.override(:current_environment => special) do Puppet[:environment] = 'special' puppet = Puppet::Application[:apply] puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) expect { puppet.run_command }.to exit_with(0) end expect(@logs.map(&:to_s)).to include('it was applied') end context "with a module" do let(:modulepath) { tmpdir('modulepath') } let(:execute) { 'include amod' } let(:args) { ['-e', execute, '--modulepath', modulepath] } before(:each) do Puppet::FileSystem.mkpath("#{modulepath}/amod/manifests") File.open("#{modulepath}/amod/manifests/init.pp", "w") do |f| f.puts <<-EOF class amod{ notice('amod class included') } EOF end + environmentdir = Dir.mktmpdir('environments') + Puppet[:environmentpath] = environmentdir create_default_directory_environment end def create_default_directory_environment Puppet::FileSystem.mkpath("#{Puppet[:environmentpath]}/#{Puppet[:environment]}") end def init_cli_args_and_apply_app(args, execute) Puppet.initialize_settings(args) puppet = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => args)) puppet.options[:code] = execute return puppet end it "looks in --modulepath even when the default directory environment exists" do apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end it "looks in --modulepath even when given a specific directory --environment" do args << '--environment' << 'production' apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end it "looks in --modulepath when given multiple paths in --modulepath" do args = ['-e', execute, '--modulepath', [tmpdir('notmodulepath'), modulepath].join(File::PATH_SEPARATOR)] apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end end end diff --git a/spec/integration/directory_environments_spec.rb b/spec/integration/directory_environments_spec.rb index 71b82a987..c0de08776 100644 --- a/spec/integration/directory_environments_spec.rb +++ b/spec/integration/directory_environments_spec.rb @@ -1,50 +1,51 @@ require 'spec_helper' describe "directory environments" do let(:args) { ['--configprint', 'modulepath', '--environment', 'direnv'] } let(:puppet) do app = Puppet::Application[:apply] app.stubs(:command_line).returns(stub('command_line', :args => [])) app end context "with a single directory environmentpath" do before(:each) do - environmentdir = Puppet[:environmentpath].split(File::PATH_SEPARATOR).first + environmentdir = PuppetSpec::Files.tmpdir('envpath') + Puppet[:environmentpath] = environmentdir FileUtils.mkdir_p(environmentdir + "/direnv/modules") end it "config prints the environments modulepath" do Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed('direnv/modules') end it "config prints the cli --modulepath despite environment" do args << '--modulepath' << 'completely/different' Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed('completely/different') end end context "with an environmentpath having multiple directories" do let(:args) { ['--configprint', 'modulepath', '--environment', 'otherdirenv'] } before(:each) do envdir1 = File.join(Puppet[:confdir], 'env1') envdir2 = File.join(Puppet[:confdir], 'env2') Puppet[:environmentpath] = [envdir1, envdir2].join(File::PATH_SEPARATOR) FileUtils.mkdir_p(envdir2 + "/otherdirenv/modules") end it "config prints a directory environment modulepath" do Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed('otherdirenv/modules') end end end diff --git a/spec/integration/indirector/file_content/file_server_spec.rb b/spec/integration/indirector/file_content/file_server_spec.rb index d35d60b44..103a028a2 100755 --- a/spec/integration/indirector/file_content/file_server_spec.rb +++ b/spec/integration/indirector/file_content/file_server_spec.rb @@ -1,89 +1,89 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_content/file_server' require 'shared_behaviours/file_server_terminus' require 'puppet_spec/files' describe Puppet::Indirector::FileContent::FileServer, " when finding files" do it_should_behave_like "Puppet::Indirector::FileServerTerminus" include PuppetSpec::Files before do @terminus = Puppet::Indirector::FileContent::FileServer.new @test_class = Puppet::FileServing::Content Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) end it "should find plugin file content in the environment specified in the request" do path = tmpfile("file_content_with_env") Dir.mkdir(path) modpath = File.join(path, "mod") FileUtils.mkdir_p(File.join(modpath, "lib")) file = File.join(modpath, "lib", "file.rb") File.open(file, "wb") { |f| f.write "1\r\n" } Puppet.settings[:modulepath] = "/no/such/file" - env = Puppet::Node::Environment.create(:foo, [path], '') + env = Puppet::Node::Environment.create(:foo, [path]) result = Puppet::FileServing::Content.indirection.search("plugins", :environment => env, :recurse => true) result.should_not be_nil result.length.should == 2 result.map {|x| x.should be_instance_of(Puppet::FileServing::Content) } result.find {|x| x.relative_path == 'file.rb' }.content.should == "1\r\n" end it "should find file content in modules" do path = tmpfile("file_content") Dir.mkdir(path) modpath = File.join(path, "mymod") FileUtils.mkdir_p(File.join(modpath, "files")) file = File.join(modpath, "files", "myfile") File.open(file, "wb") { |f| f.write "1\r\n" } Puppet.settings[:modulepath] = path result = Puppet::FileServing::Content.indirection.find("modules/mymod/myfile") result.should_not be_nil result.should be_instance_of(Puppet::FileServing::Content) result.content.should == "1\r\n" end it "should find file content in files when node name expansions are used" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:exist?).with(Puppet[:fileserverconfig]).returns(true) @path = tmpfile("file_server_testing") Dir.mkdir(@path) subdir = File.join(@path, "mynode") Dir.mkdir(subdir) File.open(File.join(subdir, "myfile"), "wb") { |f| f.write "1\r\n" } # Use a real mount, so the integration is a bit deeper. @mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") @mount1.stubs(:allowed?).returns true @mount1.path = File.join(@path, "%h") @parser = stub 'parser', :changed? => false @parser.stubs(:parse).returns("one" => @mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) path = File.join(@path, "myfile") result = Puppet::FileServing::Content.indirection.find("one/myfile", :environment => "foo", :node => "mynode") result.should_not be_nil result.should be_instance_of(Puppet::FileServing::Content) result.content.should == "1\r\n" end end diff --git a/spec/integration/node/environment_spec.rb b/spec/integration/node/environment_spec.rb index d193367ee..1f428ac3f 100755 --- a/spec/integration/node/environment_spec.rb +++ b/spec/integration/node/environment_spec.rb @@ -1,109 +1,109 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/scope' require 'matchers/resource' describe Puppet::Node::Environment do include PuppetSpec::Files include Matchers::Resource def a_module_in(name, dir) Dir.mkdir(dir) moddir = File.join(dir, name) Dir.mkdir(moddir) moddir end it "should be able to return each module from its environment with the environment, name, and path set correctly" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] mods = {} %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir mods["mod#{num}"] = a_module_in("mod#{num}", dir) end - environment = Puppet::Node::Environment.create(:foo, dirs, '') + environment = Puppet::Node::Environment.create(:foo, dirs) environment.modules.each do |mod| mod.environment.should == environment mod.path.should == mods[mod.name] end end it "should not yield the same module from different module paths" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir a_module_in("mod", dir) end - environment = Puppet::Node::Environment.create(:foo, dirs, '') + environment = Puppet::Node::Environment.create(:foo, dirs) mods = environment.modules mods.length.should == 1 mods[0].path.should == File.join(base, "dir1", "mod") end shared_examples_for "the environment's initial import" do |settings| it "a manifest referring to a directory invokes parsing of all its files in sorted order" do settings.each do |name, value| Puppet[name] = value end # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file # depends on 'a' being evaluated first. The 'c' file is empty (to ensure # empty things do not break the directory import). # dirname = my_fixture('sitedir') # Set the manifest to the directory to make it parse and combine them when compiling node = Puppet::Node.new('testnode', :environment => Puppet::Node::Environment.create(:testing, [], dirname)) catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource('Class[a]') expect(catalog).to have_resource('Class[b]') expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10") end end describe 'using classic parser' do it_behaves_like "the environment's initial import", :parser => 'current', # fixture uses variables that are set in a particular order (this ensures # that files are parsed and combined in the right order or an error will # be raised if 'b' is evaluated before 'a'). :strict_variables => true end describe 'using future parser' do it_behaves_like "the environment's initial import", :parser => 'future', :evaluator => 'future', # Turned off because currently future parser turns on the binder which # causes lookup of facts that are uninitialized and it will fail with # errors for 'osfamily' etc. This can be turned back on when the binder # is taken out of the equation. :strict_variables => false context 'and evaluator current' do it_behaves_like "the environment's initial import", :parser => 'future', :evaluator => 'current', :strict_variables => false end end end diff --git a/spec/integration/resource/type_collection_spec.rb b/spec/integration/resource/type_collection_spec.rb index 6349460be..57dc04711 100755 --- a/spec/integration/resource/type_collection_spec.rb +++ b/spec/integration/resource/type_collection_spec.rb @@ -1,94 +1,94 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/resource/type_collection' describe Puppet::Resource::TypeCollection do describe "when autoloading from modules" do include PuppetSpec::Files before do @dir = tmpfile("autoload_testing") FileUtils.mkdir_p @dir - environment = Puppet::Node::Environment.create(:env, [@dir], '') + environment = Puppet::Node::Environment.create(:env, [@dir]) @code = environment.known_resource_types end # Setup a module. def mk_module(name, files = {}) mdir = File.join(@dir, name) mandir = File.join(mdir, "manifests") FileUtils.mkdir_p mandir defs = files.delete(:define) Dir.chdir(mandir) do files.each do |file, classes| File.open("#{file}.pp", "w") do |f| classes.each { |klass| if defs f.puts "define #{klass} {}" else f.puts "class #{klass} {}" end } end end end end it "should return nil when a class can't be found or loaded" do @code.find_hostclass('', 'nosuchclass').should be_nil end it "should load the module's init file first" do name = "simple" mk_module(name, :init => [name]) @code.find_hostclass("", name).name.should == name end it "should load the module's init file even when searching from a different namespace" do name = "simple" mk_module(name, :init => [name]) @code.find_hostclass("other::ns", name).name.should == name end it "should be able to load definitions from the module base file" do name = "simpdef" mk_module(name, :define => true, :init => [name]) @code.find_definition("", name).name.should == name end it "should be able to load qualified classes from the module base file" do modname = "both" name = "sub" mk_module(modname, :init => %w{both both::sub}) @code.find_hostclass("both", name).name.should == "both::sub" end it "should be able load classes from a separate file" do modname = "separate" name = "sub" mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub}) @code.find_hostclass("separate", name).name.should == "separate::sub" end it "should not fail when loading from a separate file if there is no module file" do modname = "alone" name = "sub" mk_module(modname, :sub => %w{alone::sub}) lambda { @code.find_hostclass("alone", name) }.should_not raise_error end it "should be able to load definitions from their own file" do name = "mymod" mk_module(name, :define => true, :mydefine => ["mymod::mydefine"]) @code.find_definition("", "mymod::mydefine").name.should == "mymod::mydefine" end end end diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index 69cd284b4..bd6af5322 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -1,455 +1,455 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/apply' require 'puppet/file_bucket/dipper' require 'puppet/configurer' require 'fileutils' describe Puppet::Application::Apply do before :each do @apply = Puppet::Application[:apply] Puppet::Util::Log.stubs(:newdestination) Puppet[:reports] = "none" end after :each do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.cache_class = nil Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end [:debug,:loadclasses,:test,:verbose,:use_nodes,:detailed_exitcodes,:catalog, :write_catalog_summary].each do |option| it "should declare handle_#{option} method" do @apply.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @apply.options.expects(:[]=).with(option, 'arg') @apply.send("handle_#{option}".to_sym, 'arg') end end it "should set the code to the provided code when :execute is used" do @apply.options.expects(:[]=).with(:code, 'arg') @apply.send("handle_execute".to_sym, 'arg') end describe "when applying options" do it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @apply.handle_logdest("console") end it "should set the setdest options to true" do @apply.options.expects(:[]=).with(:setdest,true) @apply.handle_logdest("console") end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet::FileBucket::Dipper.stubs(:new) STDIN.stubs(:read) Puppet::Transaction::Report.indirection.stubs(:cache_class=) end describe "with --test" do it "should call setup_test" do @apply.options[:test] = true @apply.expects(:setup_test) @apply.setup end it "should set options[:verbose] to true" do @apply.setup_test @apply.options[:verbose].should == true end it "should set options[:show_diff] to true" do Puppet.settings.override_default(:show_diff, false) @apply.setup_test Puppet[:show_diff].should == true end it "should set options[:detailed_exitcodes] to true" do @apply.setup_test @apply.options[:detailed_exitcodes].should == true end end it "should set console as the log destination if logdest option wasn't provided" do Puppet::Log.expects(:newdestination).with(:console) @apply.setup end it "should set INT trap" do Signal.expects(:trap).with(:INT) @apply.setup end it "should set log level to debug if --debug was passed" do @apply.options[:debug] = true @apply.setup Puppet::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @apply.options[:verbose] = true @apply.setup Puppet::Log.level.should == :info end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns true Puppet.settings.expects(:print_configs).returns true expect { @apply.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @apply.setup }.to exit_with 1 end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @apply.setup end it "configures a profiler when profiling is enabled" do Puppet[:profile] = true @apply.setup expect(Puppet::Util::Profiler.current).to be_a(Puppet::Util::Profiler::WallClock) end it "does not have a profiler if profiling is disabled" do Puppet[:profile] = false @apply.setup expect(Puppet::Util::Profiler.current).to eq(Puppet::Util::Profiler::NONE) end it "should set default_file_terminus to `file_server` to be local" do @apply.app_defaults[:default_file_terminus].should == :file_server end end describe "when executing" do it "should dispatch to 'apply' if it was called with 'apply'" do @apply.options[:catalog] = "foo" @apply.expects(:apply) @apply.run_command end it "should dispatch to main otherwise" do @apply.stubs(:options).returns({}) @apply.expects(:main) @apply.run_command end describe "the main command" do include PuppetSpec::Files before :each do Puppet[:prerun_command] = '' Puppet[:postrun_command] = '' Puppet::Node::Facts.indirection.terminus_class = :memory Puppet::Node::Facts.indirection.cache_class = :memory Puppet::Node.indirection.terminus_class = :memory Puppet::Node.indirection.cache_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) @node = Puppet::Node.new(Puppet[:node_name_value]) Puppet::Node.indirection.save(@node) @catalog = Puppet::Resource::Catalog.new @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.indirection.stubs(:find).returns(@catalog) STDIN.stubs(:read) @transaction = stub('transaction') @catalog.stubs(:apply).returns(@transaction) Puppet::Util::Storage.stubs(:load) Puppet::Configurer.any_instance.stubs(:save_last_run_summary) # to prevent it from trying to write files end after :each do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.cache_class = nil end around :each do |example| Puppet.override(:current_environment => - Puppet::Node::Environment.create(:production, [], '')) do + Puppet::Node::Environment.create(:production, [])) do example.run end end it "should set the code to run from --code" do @apply.options[:code] = "code to run" Puppet.expects(:[]=).with(:code,"code to run") expect { @apply.main }.to exit_with 0 end it "should set the code to run from STDIN if no arguments" do @apply.command_line.stubs(:args).returns([]) STDIN.stubs(:read).returns("code to run") Puppet.expects(:[]=).with(:code,"code to run") expect { @apply.main }.to exit_with 0 end it "should raise an error if a file is passed on command line and the file does not exist" do noexist = tmpfile('noexist.pp') @apply.command_line.stubs(:args).returns([noexist]) lambda { @apply.main }.should raise_error(RuntimeError, "Could not find file #{noexist}") end it "should set the manifest to the first file and warn other files will be skipped" do manifest = tmpfile('starwarsIV') FileUtils.touch(manifest) @apply.command_line.stubs(:args).returns([manifest, 'starwarsI', 'starwarsII']) expect { @apply.main }.to exit_with 0 msg = @logs.find {|m| m.message =~ /Only one file can be applied per run/ } msg.message.should == 'Only one file can be applied per run. Skipping starwarsI, starwarsII' msg.level.should == :warning end it "should raise an error if we can't find the node" do Puppet::Node.indirection.expects(:find).returns(nil) lambda { @apply.main }.should raise_error(RuntimeError, /Could not find node/) end it "should load custom classes if loadclasses" do @apply.options[:loadclasses] = true classfile = tmpfile('classfile') File.open(classfile, 'w') { |c| c.puts 'class' } Puppet[:classfile] = classfile @node.expects(:classes=).with(['class']) expect { @apply.main }.to exit_with 0 end it "should compile the catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns(@catalog) expect { @apply.main }.to exit_with 0 end it "should transform the catalog to ral" do @catalog.expects(:to_ral).returns(@catalog) expect { @apply.main }.to exit_with 0 end it "should finalize the catalog" do @catalog.expects(:finalize) expect { @apply.main }.to exit_with 0 end it "should not save the classes or resource file by default" do @catalog.expects(:write_class_file).never @catalog.expects(:write_resource_file).never expect { @apply.main }.to exit_with 0 end it "should save the classes and resources files when requested" do @apply.options[:write_catalog_summary] = true @catalog.expects(:write_class_file).once @catalog.expects(:write_resource_file).once expect { @apply.main }.to exit_with 0 end it "should call the prerun and postrun commands on a Configurer instance" do Puppet::Configurer.any_instance.expects(:execute_prerun_command).returns(true) Puppet::Configurer.any_instance.expects(:execute_postrun_command).returns(true) expect { @apply.main }.to exit_with 0 end it "should apply the catalog" do @catalog.expects(:apply).returns(stub_everything('transaction')) expect { @apply.main }.to exit_with 0 end it "should save the last run summary" do Puppet[:noop] = false report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.stubs(:new).returns(report) Puppet::Configurer.any_instance.expects(:save_last_run_summary).with(report) expect { @apply.main }.to exit_with 0 end describe "when using node_name_fact" do before :each do @facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(@facts) @node = Puppet::Node.new('other_node_name') Puppet::Node.indirection.save(@node) Puppet[:node_name_fact] = 'my_name_fact' end it "should set the facts name based on the node_name_fact" do expect { @apply.main }.to exit_with 0 @facts.name.should == 'other_node_name' end it "should set the node_name_value based on the node_name_fact" do expect { @apply.main }.to exit_with 0 Puppet[:node_name_value].should == 'other_node_name' end it "should merge in our node the loaded facts" do @facts.values.merge!('key' => 'value') expect { @apply.main }.to exit_with 0 @node.parameters['key'].should == 'value' end it "should raise an error if we can't find the facts" do Puppet::Node::Facts.indirection.expects(:find).returns(nil) lambda { @apply.main }.should raise_error end end describe "with detailed_exitcodes" do before :each do @apply.options[:detailed_exitcodes] = true end it "should exit with report's computed exit status" do Puppet[:noop] = false Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) expect { @apply.main }.to exit_with 666 end it "should exit with report's computed exit status, even if --noop is set" do Puppet[:noop] = true Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) expect { @apply.main }.to exit_with 666 end it "should always exit with 0 if option is disabled" do Puppet[:noop] = false report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) expect { @apply.main }.to exit_with 0 end it "should always exit with 0 if --noop" do Puppet[:noop] = true report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) expect { @apply.main }.to exit_with 0 end end end describe "the 'apply' command" do # We want this memoized, and to be able to adjust the content, so we # have to do it ourselves. def temporary_catalog(content = '"something"') @tempfile = Tempfile.new('catalog.pson') @tempfile.write(content) @tempfile.close @tempfile.path end it "should read the catalog in from disk if a file name is provided" do @apply.options[:catalog] = temporary_catalog Puppet::Resource::Catalog.stubs(:convert_from). with(:pson,'"something"').returns(Puppet::Resource::Catalog.new) @apply.apply end it "should read the catalog in from stdin if '-' is provided" do @apply.options[:catalog] = "-" $stdin.expects(:read).returns '"something"' Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'"something"').returns Puppet::Resource::Catalog.new @apply.apply end it "should deserialize the catalog from the default format" do @apply.options[:catalog] = temporary_catalog Puppet::Resource::Catalog.stubs(:default_format).returns :rot13_piglatin Puppet::Resource::Catalog.stubs(:convert_from).with(:rot13_piglatin,'"something"').returns Puppet::Resource::Catalog.new @apply.apply end it "should fail helpfully if deserializing fails" do @apply.options[:catalog] = temporary_catalog('something syntactically invalid') lambda { @apply.apply }.should raise_error(Puppet::Error) end it "should convert plain data structures into a catalog if deserialization does not do so" do @apply.options[:catalog] = temporary_catalog Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'"something"').returns({:foo => "bar"}) Puppet::Resource::Catalog.expects(:pson_create).with({:foo => "bar"}).returns(Puppet::Resource::Catalog.new) @apply.apply end it "should convert the catalog to a RAL catalog and use a Configurer instance to apply it" do @apply.options[:catalog] = temporary_catalog catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'"something"').returns catalog catalog.expects(:to_ral).returns "mycatalog" configurer = stub 'configurer' Puppet::Configurer.expects(:new).returns configurer configurer.expects(:run). with(:catalog => "mycatalog", :pluginsync => false) @apply.apply end end end describe "apply_catalog" do it "should call the configurer with the catalog" do catalog = "I am a catalog" Puppet::Configurer.any_instance.expects(:run). with(:catalog => catalog, :pluginsync => false) @apply.send(:apply_catalog, catalog) end end end diff --git a/spec/unit/environments_spec.rb b/spec/unit/environments_spec.rb index 3a1f81628..45aa64d34 100644 --- a/spec/unit/environments_spec.rb +++ b/spec/unit/environments_spec.rb @@ -1,126 +1,125 @@ require 'spec_helper' require 'puppet/environments' require 'puppet/file_system' require 'matchers/include' describe Puppet::Environments do include Matchers::Include include PuppetSpec::Files FS = Puppet::FileSystem describe "directories loader" do - - RSpec::Matchers.define :environment do |name| - match do |env| - env.name == name && - (!@manifest || @manifest == env.manifest) && - (!@modulepath || @modulepath == env.modulepath) - end - - chain :with_manifest do |manifest| - @manifest = manifest - end - - chain :with_modulepath do |modulepath| - @modulepath = modulepath - end - - description do - "environment #{expected}" + - (@manifest ? " with manifest #{@manifest}" : "") + - (@modulepath ? " with modulepath [#{@modulepath.join(', ')}]" : "") - end - - failure_message_for_should do |env| - "expected <#{env.name}: modulepath = [#{env.modulepath.join(', ')}], manifest = #{env.manifest}> to be #{description}" - end - end - - def loader_from(options, &block) - FS.overlay(*options[:filesystem]) do - yield Puppet::Environments::Directories.new( - options[:directory], - options[:modulepath] || [] - ) - end - end - it "lists environments" do global_path_1_location = File.expand_path("global_path_1") global_path_2_location = File.expand_path("global_path_2") global_path_1 = FS::MemoryFile.a_directory(global_path_1_location) global_path_2 = FS::MemoryFile.a_directory(global_path_2_location) envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_directory("modules"), FS::MemoryFile.a_directory("manifests"), ]), FS::MemoryFile.a_directory("env2") ]) loader_from(:filesystem => [envdir, global_path_1, global_path_2], :directory => envdir, :modulepath => [global_path_1_location, global_path_2_location]) do |loader| expect(loader.list).to include_in_any_order( environment(:env1). with_manifest("#{FS.path_string(envdir)}/env1/manifests"). with_modulepath(["#{FS.path_string(envdir)}/env1/modules", global_path_1_location, global_path_2_location]), environment(:env2)) end end it "does not list files" do envdir = FS::MemoryFile.a_directory("envdir", [ FS::MemoryFile.a_regular_file_containing("foo", ''), FS::MemoryFile.a_directory("env1"), FS::MemoryFile.a_directory("env2"), ]) loader_from(:filesystem => [envdir], :directory => envdir) do |loader| expect(loader.list).to include_in_any_order(environment(:env1), environment(:env2)) end end it "it ignores directories that are not valid env names (alphanumeric and _)" do envdir = FS::MemoryFile.a_directory("envdir", [ FS::MemoryFile.a_directory(".foo"), FS::MemoryFile.a_directory("bar-thing"), FS::MemoryFile.a_directory("with spaces"), FS::MemoryFile.a_directory("some.thing"), FS::MemoryFile.a_directory("env1"), FS::MemoryFile.a_directory("env2"), ]) loader_from(:filesystem => [envdir], :directory => envdir) do |loader| expect(loader.list).to include_in_any_order(environment(:env1), environment(:env2)) end end it "gets a particular environment" do directory_tree = FS::MemoryFile.a_directory("envdir", [ FS::MemoryFile.a_directory("env1"), FS::MemoryFile.a_directory("env2"), ]) loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(loader.get("env1")).to environment(:env1) end end it "returns nil if an environment can't be found" do directory_tree = FS::MemoryFile.a_directory("envdir", []) loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(loader.get("env_not_in_this_list")).to be_nil end end end + + RSpec::Matchers.define :environment do |name| + match do |env| + env.name == name && + (!@manifest || @manifest == env.manifest) && + (!@modulepath || @modulepath == env.modulepath) + end + + chain :with_manifest do |manifest| + @manifest = manifest + end + + chain :with_modulepath do |modulepath| + @modulepath = modulepath + end + + description do + "environment #{expected}" + + (@manifest ? " with manifest #{@manifest}" : "") + + (@modulepath ? " with modulepath [#{@modulepath.join(', ')}]" : "") + end + + failure_message_for_should do |env| + "expected <#{env.name}: modulepath = [#{env.modulepath.join(', ')}], manifest = #{env.manifest}> to be #{description}" + end + end + + def loader_from(options, &block) + FS.overlay(*options[:filesystem]) do + yield Puppet::Environments::Directories.new( + options[:directory], + options[:modulepath] || [] + ) + end + end end diff --git a/spec/unit/face/module/install_spec.rb b/spec/unit/face/module/install_spec.rb index 868b22511..3e3719786 100644 --- a/spec/unit/face/module/install_spec.rb +++ b/spec/unit/face/module/install_spec.rb @@ -1,113 +1,113 @@ require 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module install" do include PuppetSpec::Files subject { Puppet::Face[:module, :current] } describe "option validation" do let(:sep) { File::PATH_SEPARATOR } let(:fakefirstpath) { make_absolute("/my/fake/modpath") } let(:fakesecondpath) { make_absolute("/other/fake/path") } let(:fakemodpath) { "#{fakefirstpath}#{sep}#{fakesecondpath}" } let(:fakedirpath) { make_absolute("/my/fake/path") } let(:options) { {} } let(:environment) do - Puppet::Node::Environment.create(:env, [fakefirstpath, fakesecondpath], '') + Puppet::Node::Environment.create(:env, [fakefirstpath, fakesecondpath]) end let(:expected_options) do { :target_dir => fakefirstpath, :environment_instance => environment, } end around(:each) do |example| Puppet.override(:current_environment => environment) do example.run end end context "without any options" do it "requires a name" do pattern = /wrong number of arguments/ expect { subject.install }.to raise_error ArgumentError, pattern end it "does not require any options" do expects_installer_run_with("puppetlabs-apache", expected_options) subject.install("puppetlabs-apache") end end it "accepts the --force option" do options[:force] = true expected_options.merge!(options) expects_installer_run_with("puppetlabs-apache", expected_options) subject.install("puppetlabs-apache", options) end it "accepts the --target-dir option" do options[:target_dir] = make_absolute("/foo/puppet/modules") expected_options.merge!(options) expected_options[:environment_instance] = environment.override_with(:modulepath => [options[:target_dir], fakefirstpath, fakesecondpath]) expects_installer_run_with("puppetlabs-apache", expected_options) subject.install("puppetlabs-apache", options) end it "accepts the --version option" do options[:version] = "0.0.1" expected_options.merge!(options) expects_installer_run_with("puppetlabs-apache", expected_options) subject.install("puppetlabs-apache", options) end it "accepts the --ignore-dependencies option" do options[:ignore_dependencies] = true expected_options.merge!(options) expects_installer_run_with("puppetlabs-apache", expected_options) subject.install("puppetlabs-apache", options) end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :install } its(:summary) { should =~ /install.*module/im } its(:description) { should =~ /install.*module/im } its(:returns) { should =~ /pathname/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end def expects_installer_run_with(name, options) installer = mock("Installer") install_dir = mock("InstallDir") forge = mock("Forge") Puppet::Forge.expects(:new).with("PMT", subject.version).returns(forge) Puppet::ModuleTool::InstallDirectory.expects(:new). with(Pathname.new(expected_options[:target_dir])). returns(install_dir) Puppet::ModuleTool::Applications::Installer.expects(:new). with("puppetlabs-apache", forge, install_dir, expected_options). returns(installer) installer.expects(:run) end end diff --git a/spec/unit/face/module/list_spec.rb b/spec/unit/face/module/list_spec.rb index 9fc4e7121..314d4d762 100644 --- a/spec/unit/face/module/list_spec.rb +++ b/spec/unit/face/module/list_spec.rb @@ -1,195 +1,195 @@ # encoding: UTF-8 require 'spec_helper' require 'puppet/face' require 'puppet/module_tool' require 'puppet_spec/modules' describe "puppet module list" do include PuppetSpec::Files before do dir = tmpdir("deep_path") @modpath1 = File.join(dir, "modpath1") @modpath2 = File.join(dir, "modpath2") @modulepath = "#{@modpath1}#{File::PATH_SEPARATOR}#{@modpath2}" Puppet.settings[:modulepath] = @modulepath FileUtils.mkdir_p(@modpath1) FileUtils.mkdir_p(@modpath2) end around do |example| Puppet.override(:environments => Puppet::Environments::Legacy.new()) do example.run end end it "should return an empty list per dir in path if there are no modules" do Puppet.settings[:modulepath] = @modulepath Puppet::Face[:module, :current].list.should == { @modpath1 => [], @modpath2 => [] } end it "should include modules separated by the environment's modulepath" do foomod1 = PuppetSpec::Modules.create('foo', @modpath1) barmod1 = PuppetSpec::Modules.create('bar', @modpath1) foomod2 = PuppetSpec::Modules.create('foo', @modpath2) - usedenv = Puppet::Node::Environment.create(:useme, [@modpath1, @modpath2], '') + usedenv = Puppet::Node::Environment.create(:useme, [@modpath1, @modpath2]) Puppet.override(:environments => Puppet::Environments::Static.new(usedenv)) do Puppet::Face[:module, :current].list(:environment => 'useme').should == { @modpath1 => [ Puppet::Module.new('bar', barmod1.path, usedenv), Puppet::Module.new('foo', foomod1.path, usedenv) ], @modpath2 => [Puppet::Module.new('foo', foomod2.path, usedenv)] } end end it "should use the specified environment" do foomod = PuppetSpec::Modules.create('foo', @modpath1) barmod = PuppetSpec::Modules.create('bar', @modpath1) - usedenv = Puppet::Node::Environment.create(:useme, [@modpath1, @modpath2], '') + usedenv = Puppet::Node::Environment.create(:useme, [@modpath1, @modpath2]) Puppet.override(:environments => Puppet::Environments::Static.new(usedenv)) do Puppet::Face[:module, :current].list(:environment => 'useme').should == { @modpath1 => [ Puppet::Module.new('bar', barmod.path, usedenv), Puppet::Module.new('foo', foomod.path, usedenv) ], @modpath2 => [] } end end it "should use the specified modulepath" do foomod = PuppetSpec::Modules.create('foo', @modpath1) barmod = PuppetSpec::Modules.create('bar', @modpath2) modules = Puppet::Face[:module, :current].list(:modulepath => "#{@modpath1}#{File::PATH_SEPARATOR}#{@modpath2}") expect(modules[@modpath1].first.name).to eq('foo') expect(modules[@modpath1].first.path).to eq(foomod.path) expect(modules[@modpath1].first.environment.modulepath).to eq([@modpath1, @modpath2]) expect(modules[@modpath2].first.name).to eq('bar') expect(modules[@modpath2].first.path).to eq(barmod.path) expect(modules[@modpath2].first.environment.modulepath).to eq([@modpath1, @modpath2]) end it "prefers a given modulepath over the modulepath from the given environment" do foomod = PuppetSpec::Modules.create('foo', @modpath1) barmod = PuppetSpec::Modules.create('bar', @modpath2) - env = Puppet::Node::Environment.create(:myenv, ['/tmp/notused'], '') + env = Puppet::Node::Environment.create(:myenv, ['/tmp/notused']) Puppet[:modulepath] = "" modules = Puppet::Face[:module, :current].list(:environment => 'myenv', :modulepath => "#{@modpath1}#{File::PATH_SEPARATOR}#{@modpath2}") expect(modules[@modpath1].first.name).to eq('foo') expect(modules[@modpath1].first.path).to eq(foomod.path) expect(modules[@modpath1].first.environment.modulepath).to eq([@modpath1, @modpath2]) expect(modules[@modpath1].first.environment.name).to_not eq(:myenv) expect(modules[@modpath2].first.name).to eq('bar') expect(modules[@modpath2].first.path).to eq(barmod.path) expect(modules[@modpath2].first.environment.modulepath).to eq([@modpath1, @modpath2]) expect(modules[@modpath2].first.environment.name).to_not eq(:myenv) end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :list } its(:summary) { should =~ /list.*module/im } its(:description) { should =~ /list.*module/im } its(:returns) { should =~ /hash of paths to module objects/i } its(:examples) { should_not be_empty } end describe "when rendering" do it "should explicitly state when a modulepath is empty" do empty_modpath = tmpdir('empty') Puppet::Face[:module, :current].list_when_rendering_console( { empty_modpath => [] }, {:modulepath => empty_modpath} ).should == <<-HEREDOC.gsub(' ', '') #{empty_modpath} (no modules installed) HEREDOC end it "should print both modules with and without metadata" do modpath = tmpdir('modpath') Puppet.settings[:modulepath] = modpath PuppetSpec::Modules.create('nometadata', modpath) PuppetSpec::Modules.create('metadata', modpath, :metadata => {:author => 'metaman'}) dependency_tree = Puppet::Face[:module, :current].list output = Puppet::Face[:module, :current]. list_when_rendering_console(dependency_tree, {}) output.should == <<-HEREDOC.gsub(' ', '') #{modpath} ├── metaman-metadata (\e[0;36mv9.9.9\e[0m) └── nometadata (\e[0;36m???\e[0m) HEREDOC end it "should print the modulepaths in the order they are in the modulepath setting" do path1 = tmpdir('b') path2 = tmpdir('c') path3 = tmpdir('a') sep = File::PATH_SEPARATOR Puppet.settings[:modulepath] = "#{path1}#{sep}#{path2}#{sep}#{path3}" Puppet::Face[:module, :current].list_when_rendering_console( { path2 => [], path3 => [], path1 => [], }, {} ).should == <<-HEREDOC.gsub(' ', '') #{path1} (no modules installed) #{path2} (no modules installed) #{path3} (no modules installed) HEREDOC end it "should print dependencies as a tree" do PuppetSpec::Modules.create('dependable', @modpath1, :metadata => { :version => '0.0.5'}) PuppetSpec::Modules.create( 'other_mod', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 0.0.5", "name" => "puppetlabs/dependable" }] } ) dependency_tree = Puppet::Face[:module, :current].list output = Puppet::Face[:module, :current].list_when_rendering_console( dependency_tree, {:tree => true} ) output.should == <<-HEREDOC.gsub(' ', '') #{@modpath1} └─┬ puppetlabs-other_mod (\e[0;36mv1.0.0\e[0m) └── puppetlabs-dependable (\e[0;36mv0.0.5\e[0m) #{@modpath2} (no modules installed) HEREDOC end end end diff --git a/spec/unit/face/module/uninstall_spec.rb b/spec/unit/face/module/uninstall_spec.rb index 36209dc96..d046aa32c 100644 --- a/spec/unit/face/module/uninstall_spec.rb +++ b/spec/unit/face/module/uninstall_spec.rb @@ -1,70 +1,70 @@ require 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module uninstall" do subject { Puppet::Face[:module, :current] } let(:options) do {} end let(:modulepath) { File.expand_path('/module/path') } let(:environment) do - Puppet::Node::Environment.create(:env, [modulepath], '') + Puppet::Node::Environment.create(:env, [modulepath]) end let(:expected_options) do { :target_dir => modulepath, :environment_instance => environment, } end describe "option validation" do around(:each) do |example| Puppet.override(:current_environment => environment) do example.run end end context "without any options" do it "should require a name" do pattern = /wrong number of arguments/ expect { subject.uninstall }.to raise_error ArgumentError, pattern end it "should not require any options" do Puppet::ModuleTool::Applications::Uninstaller.expects(:run).once subject.uninstall("puppetlabs-apache") end end it "should accept the --version option" do options[:version] = "1.0.0" expected_options.merge!(:version => '1.0.0') Puppet::ModuleTool::Applications::Uninstaller.expects(:run).with("puppetlabs-apache", has_entries(expected_options)).once subject.uninstall("puppetlabs-apache", options) end it "should accept the --force flag" do options[:force] = true expected_options.merge!(:force => true) Puppet::ModuleTool::Applications::Uninstaller.expects(:run).with("puppetlabs-apache", has_entries(expected_options)).once subject.uninstall("puppetlabs-apache", options) end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :uninstall } its(:summary) { should =~ /uninstall.*module/im } its(:description) { should =~ /uninstall.*module/im } its(:returns) { should =~ /hash of module objects.*/im } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end diff --git a/spec/unit/face/parser_spec.rb b/spec/unit/face/parser_spec.rb index 3bf24bcb0..89e1b049a 100644 --- a/spec/unit/face/parser_spec.rb +++ b/spec/unit/face/parser_spec.rb @@ -1,70 +1,70 @@ require 'spec_helper' require 'puppet_spec/files' require 'puppet/face' describe Puppet::Face[:parser, :current] do include PuppetSpec::Files let(:parser) { Puppet::Face[:parser, :current] } context "from an interactive terminal" do before :each do from_an_interactive_terminal end it "validates the configured site manifest when no files are given" do Puppet[:manifest] = file_containing('site.pp', "{ invalid =>") expect { parser.validate() }.to exit_with(1) end it "validates the given file" do manifest = file_containing('site.pp', "{ invalid =>") expect { parser.validate(manifest) }.to exit_with(1) end it "runs error free when there are no validation errors" do manifest = file_containing('site.pp', "notify { valid: }") parser.validate(manifest) end it "reports missing files" do expect do parser.validate("missing.pp") end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m) end it "parses supplied manifest files in the context of a directory environment" do manifest = file_containing('test.pp', "{ invalid =>") env_loader = Puppet::Environments::Static.new( - Puppet::Node::Environment.create(:special, [], '') + Puppet::Node::Environment.create(:special, []) ) Puppet.override(:environments => env_loader) do Puppet[:environment] = 'special' expect { parser.validate(manifest) }.to exit_with(1) end expect(@logs.join).to match(/environment special.*Syntax error at '\{'/) end end it "validates the contents of STDIN when no files given and STDIN is not a tty" do from_a_piped_input_of("{ invalid =>") expect { parser.validate() }.to exit_with(1) end def from_an_interactive_terminal STDIN.stubs(:tty?).returns(true) end def from_a_piped_input_of(contents) STDIN.stubs(:tty?).returns(false) STDIN.stubs(:read).returns(contents) end end diff --git a/spec/unit/indirector/node/active_record_spec.rb b/spec/unit/indirector/node/active_record_spec.rb index ac3c98bc4..7d4ce5a9e 100755 --- a/spec/unit/indirector/node/active_record_spec.rb +++ b/spec/unit/indirector/node/active_record_spec.rb @@ -1,48 +1,48 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' describe "Puppet::Node::ActiveRecord", :if => Puppet.features.rails? && Puppet.features.sqlite? do include PuppetSpec::Files let(:nodename) { "mynode" } let(:fact_values) { {:afact => "a value"} } let(:facts) { Puppet::Node::Facts.new(nodename, fact_values) } - let(:environment) { Puppet::Node::Environment.create(:myenv, [], '') } + let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, :environment => environment) } let(:node_indirection) { Puppet::Node::ActiveRecord.new } before do require 'puppet/indirector/node/active_record' end it "should issue a deprecation warning" do Puppet.expects(:deprecation_warning).with() { |msg| msg =~ /ActiveRecord-based storeconfigs and inventory are deprecated/ } Puppet[:statedir] = tmpdir('active_record_tmp') Puppet[:railslog] = '$statedir/rails.log' node_indirection end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Node::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Node::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end it "should call fact_merge when a node is found" do db_instance = stub 'db_instance' Puppet::Node::ActiveRecord.ar_model.expects(:find_by_name).returns db_instance node = Puppet::Node.new(nodename) db_instance.expects(:to_puppet).returns node Puppet[:statedir] = tmpdir('active_record_tmp') Puppet[:railslog] = '$statedir/rails.log' Puppet::Node::Facts.indirection.expects(:find).with(nodename, :environment => environment).returns(facts) node_indirection.find(request).parameters.should include(fact_values) end end diff --git a/spec/unit/indirector/node/ldap_spec.rb b/spec/unit/indirector/node/ldap_spec.rb index c19a30150..76860e15d 100755 --- a/spec/unit/indirector/node/ldap_spec.rb +++ b/spec/unit/indirector/node/ldap_spec.rb @@ -1,441 +1,441 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/ldap' describe Puppet::Node::Ldap do let(:nodename) { "mynode.domain.com" } let(:node_indirection) { Puppet::Node::Ldap.new } - let(:environment) { Puppet::Node::Environment.create(:myenv, [], '') } + let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:fact_values) { {:afact => "a value", "one" => "boo"} } let(:facts) { Puppet::Node::Facts.new(nodename, fact_values) } before do Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => environment).returns(facts) end describe "when searching for a single node" do let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, :environment => environment) } it "should convert the hostname into a search filter" do entry = stub 'entry', :dn => 'cn=mynode.domain.com,ou=hosts,dc=madstop,dc=com', :vals => %w{}, :to_hash => {} node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(cn=#{nodename}))").yields entry node_indirection.name2hash(nodename) end it "should convert any found entry into a hash" do entry = stub 'entry', :dn => 'cn=mynode.domain.com,ou=hosts,dc=madstop,dc=com', :vals => %w{}, :to_hash => {} node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(cn=#{nodename}))").yields entry myhash = {"myhash" => true} node_indirection.expects(:entry2hash).with(entry).returns myhash node_indirection.name2hash(nodename).should == myhash end # This heavily tests our entry2hash method, so we don't have to stub out the stupid entry information any more. describe "when an ldap entry is found" do before do @entry = stub 'entry', :dn => 'cn=mynode,ou=hosts,dc=madstop,dc=com', :vals => %w{}, :to_hash => {} node_indirection.stubs(:ldapsearch).yields @entry end it "should convert the entry to a hash" do node_indirection.entry2hash(@entry).should be_instance_of(Hash) end it "should add the entry's common name to the hash if fqdn if false" do node_indirection.entry2hash(@entry,fqdn = false)[:name].should == "mynode" end it "should add the entry's fqdn name to the hash if fqdn if true" do node_indirection.entry2hash(@entry,fqdn = true)[:name].should == "mynode.madstop.com" end it "should add all of the entry's classes to the hash" do @entry.stubs(:vals).with("puppetclass").returns %w{one two} node_indirection.entry2hash(@entry)[:classes].should == %w{one two} end it "should deduplicate class values" do @entry.stubs(:to_hash).returns({}) node_indirection.stubs(:class_attributes).returns(%w{one two}) @entry.stubs(:vals).with("one").returns(%w{a b}) @entry.stubs(:vals).with("two").returns(%w{b c}) node_indirection.entry2hash(@entry)[:classes].should == %w{a b c} end it "should add the entry's environment to the hash" do @entry.stubs(:to_hash).returns("environment" => %w{production}) node_indirection.entry2hash(@entry)[:environment].should == "production" end it "should add all stacked parameters as parameters in the hash" do @entry.stubs(:vals).with("puppetvar").returns(%w{one=two three=four}) result = node_indirection.entry2hash(@entry) result[:parameters]["one"].should == "two" result[:parameters]["three"].should == "four" end it "should not add the stacked parameter as a normal parameter" do @entry.stubs(:vals).with("puppetvar").returns(%w{one=two three=four}) @entry.stubs(:to_hash).returns("puppetvar" => %w{one=two three=four}) node_indirection.entry2hash(@entry)[:parameters]["puppetvar"].should be_nil end it "should add all other attributes as parameters in the hash" do @entry.stubs(:to_hash).returns("foo" => %w{one two}) node_indirection.entry2hash(@entry)[:parameters]["foo"].should == %w{one two} end it "should return single-value parameters as strings, not arrays" do @entry.stubs(:to_hash).returns("foo" => %w{one}) node_indirection.entry2hash(@entry)[:parameters]["foo"].should == "one" end it "should convert 'true' values to the boolean 'true'" do @entry.stubs(:to_hash).returns({"one" => ["true"]}) node_indirection.entry2hash(@entry)[:parameters]["one"].should == true end it "should convert 'false' values to the boolean 'false'" do @entry.stubs(:to_hash).returns({"one" => ["false"]}) node_indirection.entry2hash(@entry)[:parameters]["one"].should == false end it "should convert 'true' values to the boolean 'true' inside an array" do @entry.stubs(:to_hash).returns({"one" => ["true", "other"]}) node_indirection.entry2hash(@entry)[:parameters]["one"].should == [true, "other"] end it "should convert 'false' values to the boolean 'false' inside an array" do @entry.stubs(:to_hash).returns({"one" => ["false", "other"]}) node_indirection.entry2hash(@entry)[:parameters]["one"].should == [false, "other"] end it "should add the parent's name if present" do @entry.stubs(:vals).with("parentnode").returns(%w{foo}) node_indirection.entry2hash(@entry)[:parent].should == "foo" end it "should fail if more than one parent is specified" do @entry.stubs(:vals).with("parentnode").returns(%w{foo}) node_indirection.entry2hash(@entry)[:parent].should == "foo" end end it "should search first for the provided key" do node_indirection.expects(:name2hash).with("mynode.domain.com").returns({}) node_indirection.find(request) end it "should search for the short version of the provided key if the key looks like a hostname and no results are found for the key itself" do node_indirection.expects(:name2hash).with("mynode.domain.com").returns(nil) node_indirection.expects(:name2hash).with("mynode").returns({}) node_indirection.find(request) end it "should search for default information if no information can be found for the key" do node_indirection.expects(:name2hash).with("mynode.domain.com").returns(nil) node_indirection.expects(:name2hash).with("mynode").returns(nil) node_indirection.expects(:name2hash).with("default").returns({}) node_indirection.find(request) end it "should return nil if no results are found in ldap" do node_indirection.stubs(:name2hash).returns nil node_indirection.find(request).should be_nil end it "should return a node object if results are found in ldap" do node_indirection.stubs(:name2hash).returns({}) node_indirection.find(request).should be end describe "and node information is found in LDAP" do before do @result = {} node_indirection.stubs(:name2hash).returns @result end it "should create the node with the correct name, even if it was found by a different name" do node_indirection.expects(:name2hash).with(nodename).returns nil node_indirection.expects(:name2hash).with("mynode").returns @result node_indirection.find(request).name.should == nodename end it "should add any classes from ldap" do classes = %w{a b c d} @result[:classes] = classes node_indirection.find(request).classes.should == classes end it "should add all entry attributes as node parameters" do params = {"one" => "two", "three" => "four"} @result[:parameters] = params node_indirection.find(request).parameters.should include(params) end it "should set the node's environment to the environment of the results" do - result_env = Puppet::Node::Environment.create(:local_test, [], '') + result_env = Puppet::Node::Environment.create(:local_test, []) Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => result_env).returns(facts) @result[:environment] = "local_test" Puppet.override(:environments => Puppet::Environments::Static.new(result_env)) do node_indirection.find(request).environment.should == result_env end end it "should retain false parameter values" do @result[:parameters] = {} @result[:parameters]["one"] = false node_indirection.find(request).parameters.should include({"one" => false}) end it "should merge the node's facts after the parameters from ldap are assigned" do # Make sure we've got data to start with, so the parameters are actually set. params = {"one" => "yay", "two" => "hooray"} @result[:parameters] = params # Node implements its own merge so that an existing param takes # precedence over facts. We get the same result here by merging params # into facts node_indirection.find(request).parameters.should == facts.values.merge(params) end describe "and a parent node is specified" do before do @entry = {:classes => [], :parameters => {}} @parent = {:classes => [], :parameters => {}} @parent_parent = {:classes => [], :parameters => {}} node_indirection.stubs(:name2hash).with(nodename).returns(@entry) node_indirection.stubs(:name2hash).with('parent').returns(@parent) node_indirection.stubs(:name2hash).with('parent_parent').returns(@parent_parent) node_indirection.stubs(:parent_attribute).returns(:parent) end it "should search for the parent node" do @entry[:parent] = "parent" node_indirection.expects(:name2hash).with(nodename).returns @entry node_indirection.expects(:name2hash).with('parent').returns @parent node_indirection.find(request) end it "should fail if the parent cannot be found" do @entry[:parent] = "parent" node_indirection.expects(:name2hash).with('parent').returns nil proc { node_indirection.find(request) }.should raise_error(Puppet::Error, /Could not find parent node/) end it "should add any parent classes to the node's classes" do @entry[:parent] = "parent" @entry[:classes] = %w{a b} @parent[:classes] = %w{c d} node_indirection.find(request).classes.should == %w{a b c d} end it "should add any parent parameters to the node's parameters" do @entry[:parent] = "parent" @entry[:parameters]["one"] = "two" @parent[:parameters]["three"] = "four" node_indirection.find(request).parameters.should include({"one" => "two", "three" => "four"}) end it "should prefer node parameters over parent parameters" do @entry[:parent] = "parent" @entry[:parameters]["one"] = "two" @parent[:parameters]["one"] = "three" node_indirection.find(request).parameters.should include({"one" => "two"}) end it "should use the parent's environment if the node has none" do - env = Puppet::Node::Environment.create(:parent, [], '') + env = Puppet::Node::Environment.create(:parent, []) @entry[:parent] = "parent" @parent[:environment] = "parent" Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => env).returns(facts) Puppet.override(:environments => Puppet::Environments::Static.new(env)) do node_indirection.find(request).environment.should == env end end it "should prefer the node's environment to the parent's" do - child_env = Puppet::Node::Environment.create(:child, [], '') + child_env = Puppet::Node::Environment.create(:child, []) @entry[:parent] = "parent" @entry[:environment] = "child" @parent[:environment] = "parent" Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => child_env).returns(facts) Puppet.override(:environments => Puppet::Environments::Static.new(child_env)) do node_indirection.find(request).environment.should == child_env end end it "should recursively look up parent information" do @entry[:parent] = "parent" @entry[:parameters]["one"] = "two" @parent[:parent] = "parent_parent" @parent[:parameters]["three"] = "four" @parent_parent[:parameters]["five"] = "six" node_indirection.find(request).parameters.should include("one" => "two", "three" => "four", "five" => "six") end it "should not allow loops in parent declarations" do @entry[:parent] = "parent" @parent[:parent] = nodename proc { node_indirection.find(request) }.should raise_error(ArgumentError) end end end end describe "when searching for multiple nodes" do let(:options) { {:environment => environment} } let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, options) } before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml end it "should find all nodes if no arguments are provided" do node_indirection.expects(:ldapsearch).with("(objectclass=puppetClient)") # LAK:NOTE The search method requires an essentially bogus key. It's # an API problem that I don't really know how to fix. node_indirection.search request end describe "and a class is specified" do it "should find all nodes that are members of that class" do node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(puppetclass=one))") options[:class] = "one" node_indirection.search request end end describe "multiple classes are specified" do it "should find all nodes that are members of all classes" do node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(puppetclass=one)(puppetclass=two))") options[:class] = %w{one two} node_indirection.search request end end it "should process each found entry" do # .yields can't be used to yield multiple values :/ node_indirection.expects(:ldapsearch).yields("one") node_indirection.expects(:entry2hash).with("one",nil).returns(:name => nodename) node_indirection.search request end it "should return a node for each processed entry with the name from the entry" do node_indirection.expects(:ldapsearch).yields("whatever") node_indirection.expects(:entry2hash).with("whatever",nil).returns(:name => nodename) result = node_indirection.search(request) result[0].should be_instance_of(Puppet::Node) result[0].name.should == nodename end it "should merge each node's facts" do node_indirection.stubs(:ldapsearch).yields("one") node_indirection.stubs(:entry2hash).with("one",nil).returns(:name => nodename) node_indirection.search(request)[0].parameters.should include(fact_values) end it "should pass the request's fqdn option to entry2hash" do options[:fqdn] = :hello node_indirection.stubs(:ldapsearch).yields("one") node_indirection.expects(:entry2hash).with("one",:hello).returns(:name => nodename) node_indirection.search(request) end end describe Puppet::Node::Ldap, " when developing the search query" do it "should return the value of the :ldapclassattrs split on commas as the class attributes" do Puppet[:ldapclassattrs] = "one,two" node_indirection.class_attributes.should == %w{one two} end it "should return nil as the parent attribute if the :ldapparentattr is set to an empty string" do Puppet[:ldapparentattr] = "" node_indirection.parent_attribute.should be_nil end it "should return the value of the :ldapparentattr as the parent attribute" do Puppet[:ldapparentattr] = "pere" node_indirection.parent_attribute.should == "pere" end it "should use the value of the :ldapstring as the search filter" do Puppet[:ldapstring] = "mystring" node_indirection.search_filter("testing").should == "mystring" end it "should replace '%s' with the node name in the search filter if it is present" do Puppet[:ldapstring] = "my%sstring" node_indirection.search_filter("testing").should == "mytestingstring" end it "should not modify the global :ldapstring when replacing '%s' in the search filter" do filter = mock 'filter' filter.expects(:include?).with("%s").returns(true) filter.expects(:gsub).with("%s", "testing").returns("mynewstring") Puppet[:ldapstring] = filter node_indirection.search_filter("testing").should == "mynewstring" end end describe Puppet::Node::Ldap, " when deciding attributes to search for" do it "should use 'nil' if the :ldapattrs setting is 'all'" do Puppet[:ldapattrs] = "all" node_indirection.search_attributes.should be_nil end it "should split the value of :ldapattrs on commas and use the result as the attribute list" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns([]) node_indirection.stubs(:parent_attribute).returns(nil) node_indirection.search_attributes.should == %w{one two} end it "should add the class attributes to the search attributes if not returning all attributes" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns(%w{three four}) node_indirection.stubs(:parent_attribute).returns(nil) # Sort them so i don't have to care about return order node_indirection.search_attributes.sort.should == %w{one two three four}.sort end it "should add the parent attribute to the search attributes if not returning all attributes" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns([]) node_indirection.stubs(:parent_attribute).returns("parent") node_indirection.search_attributes.sort.should == %w{one two parent}.sort end it "should not add nil parent attributes to the search attributes" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns([]) node_indirection.stubs(:parent_attribute).returns(nil) node_indirection.search_attributes.should == %w{one two} end end end diff --git a/spec/unit/indirector/node/plain_spec.rb b/spec/unit/indirector/node/plain_spec.rb index 8e1d0decf..607fdefdf 100755 --- a/spec/unit/indirector/node/plain_spec.rb +++ b/spec/unit/indirector/node/plain_spec.rb @@ -1,26 +1,26 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/plain' describe Puppet::Node::Plain do let(:nodename) { "mynode" } let(:fact_values) { {:afact => "a value"} } let(:facts) { Puppet::Node::Facts.new(nodename, fact_values) } - let(:environment) { Puppet::Node::Environment.create(:myenv, [], '') } + let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, :environment => environment) } let(:node_indirection) { Puppet::Node::Plain.new } before do Puppet::Node::Facts.indirection.expects(:find).with(nodename, :environment => environment).returns(facts) end it "merges facts into the node" do node_indirection.find(request).parameters.should include(fact_values) end it "should set the node environment from the request" do node_indirection.find(request).environment.should == environment end end diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb index e0d9df339..3c11664d3 100755 --- a/spec/unit/indirector/request_spec.rb +++ b/spec/unit/indirector/request_spec.rb @@ -1,605 +1,605 @@ #! /usr/bin/env ruby require 'spec_helper' require 'matchers/json' require 'puppet/indirector/request' require 'puppet/util/pson' describe Puppet::Indirector::Request do include JSONMatchers describe "when registering the document type" do it "should register its document type with JSON" do PSON.registered_document_types["IndirectorRequest"].should equal(Puppet::Indirector::Request) end end describe "when initializing" do it "should always convert the indirection name to a symbol" do Puppet::Indirector::Request.new("ind", :method, "mykey", nil).indirection_name.should == :ind end it "should use provided value as the key if it is a string" do Puppet::Indirector::Request.new(:ind, :method, "mykey", nil).key.should == "mykey" end it "should use provided value as the key if it is a symbol" do Puppet::Indirector::Request.new(:ind, :method, :mykey, nil).key.should == :mykey end it "should use the name of the provided instance as its key if an instance is provided as the key instead of a string" do instance = mock 'instance', :name => "mykey" request = Puppet::Indirector::Request.new(:ind, :method, nil, instance) request.key.should == "mykey" request.instance.should equal(instance) end it "should support options specified as a hash" do expect { Puppet::Indirector::Request.new(:ind, :method, :key, nil, :one => :two) }.to_not raise_error end it "should support nil options" do expect { Puppet::Indirector::Request.new(:ind, :method, :key, nil, nil) }.to_not raise_error end it "should support unspecified options" do expect { Puppet::Indirector::Request.new(:ind, :method, :key, nil) }.to_not raise_error end it "should use an empty options hash if nil was provided" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, nil).options.should == {} end it "should default to a nil node" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).node.should be_nil end it "should set its node attribute if provided in the options" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :node => "foo.com").node.should == "foo.com" end it "should default to a nil ip" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).ip.should be_nil end it "should set its ip attribute if provided in the options" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ip => "192.168.0.1").ip.should == "192.168.0.1" end it "should default to being unauthenticated" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).should_not be_authenticated end it "should set be marked authenticated if configured in the options" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :authenticated => "eh").should be_authenticated end it "should keep its options as a hash even if a node is specified" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :node => "eh").options.should be_instance_of(Hash) end it "should keep its options as a hash even if another option is specified" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :foo => "bar").options.should be_instance_of(Hash) end it "should treat options other than :ip, :node, and :authenticated as options rather than attributes" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :server => "bar").options[:server].should == "bar" end it "should normalize options to use symbols as keys" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, "foo" => "bar").options[:foo].should == "bar" end describe "and the request key is a URI" do let(:file) { File.expand_path("/my/file with spaces") } describe "and the URI is a 'file' URI" do before do @request = Puppet::Indirector::Request.new(:ind, :method, "#{URI.unescape(Puppet::Util.path_to_uri(file).to_s)}", nil) end it "should set the request key to the unescaped full file path" do @request.key.should == file end it "should not set the protocol" do @request.protocol.should be_nil end it "should not set the port" do @request.port.should be_nil end it "should not set the server" do @request.server.should be_nil end end it "should set the protocol to the URI scheme" do Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff", nil).protocol.should == "http" end it "should set the server if a server is provided" do Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff", nil).server.should == "host" end it "should set the server and port if both are provided" do Puppet::Indirector::Request.new(:ind, :method, "http://host:543/stuff", nil).port.should == 543 end it "should default to the masterport if the URI scheme is 'puppet'" do Puppet[:masterport] = "321" Puppet::Indirector::Request.new(:ind, :method, "puppet://host/stuff", nil).port.should == 321 end it "should use the provided port if the URI scheme is not 'puppet'" do Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff", nil).port.should == 80 end it "should set the request key to the unescaped key part path from the URI" do Puppet::Indirector::Request.new(:ind, :method, "http://host/environment/terminus/stuff with spaces", nil).key.should == "stuff with spaces" end it "should set the :uri attribute to the full URI" do Puppet::Indirector::Request.new(:ind, :method, "http:///stu ff", nil).uri.should == 'http:///stu ff' end it "should not parse relative URI" do Puppet::Indirector::Request.new(:ind, :method, "foo/bar", nil).uri.should be_nil end it "should not parse opaque URI" do Puppet::Indirector::Request.new(:ind, :method, "mailto:joe", nil).uri.should be_nil end end it "should allow indication that it should not read a cached instance" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ignore_cache => true).should be_ignore_cache end it "should default to not ignoring the cache" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).should_not be_ignore_cache end it "should allow indication that it should not not read an instance from the terminus" do Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ignore_terminus => true).should be_ignore_terminus end it "should default to not ignoring the terminus" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).should_not be_ignore_terminus end end it "should look use the Indirection class to return the appropriate indirection" do ind = mock 'indirection' Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns ind request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) request.indirection.should equal(ind) end it "should use its indirection to look up the appropriate model" do ind = mock 'indirection' Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns ind request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) ind.expects(:model).returns "mymodel" request.model.should == "mymodel" end it "should fail intelligently when asked to find a model but the indirection cannot be found" do Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns nil request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) expect { request.model }.to raise_error(ArgumentError) end it "should have a method for determining if the request is plural or singular" do Puppet::Indirector::Request.new(:myind, :method, :key, nil).should respond_to(:plural?) end it "should be considered plural if the method is 'search'" do Puppet::Indirector::Request.new(:myind, :search, :key, nil).should be_plural end it "should not be considered plural if the method is not 'search'" do Puppet::Indirector::Request.new(:myind, :find, :key, nil).should_not be_plural end it "should use its uri, if it has one, as its string representation" do Puppet::Indirector::Request.new(:myind, :find, "foo://bar/baz", nil).to_s.should == "foo://bar/baz" end it "should use its indirection name and key, if it has no uri, as its string representation" do Puppet::Indirector::Request.new(:myind, :find, "key", nil) == "/myind/key" end it "should be able to return the URI-escaped key" do Puppet::Indirector::Request.new(:myind, :find, "my key", nil).escaped_key.should == URI.escape("my key") end it "should set its environment to an environment instance when a string is specified as its environment" do - env = Puppet::Node::Environment.create(:foo, [], '') + env = Puppet::Node::Environment.create(:foo, []) Puppet.override(:environments => Puppet::Environments::Static.new(env)) do Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => "foo").environment.should == env end end it "should use any passed in environment instances as its environment" do - env = Puppet::Node::Environment.create(:foo, [], '') + env = Puppet::Node::Environment.create(:foo, []) Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => env).environment.should equal(env) end it "should use the configured environment when none is provided" do - configured = Puppet::Node::Environment.create(:foo, [], '') + configured = Puppet::Node::Environment.create(:foo, []) Puppet[:environment] = "foo" Puppet.override(:environments => Puppet::Environments::Static.new(configured)) do Puppet::Indirector::Request.new(:myind, :find, "my key", nil).environment.should == configured end end it "should support converting its options to a hash" do Puppet::Indirector::Request.new(:myind, :find, "my key", nil ).should respond_to(:to_hash) end it "should include all of its attributes when its options are converted to a hash" do Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :node => 'foo').to_hash[:node].should == 'foo' end describe "when building a query string from its options" do def a_request_with_options(options) Puppet::Indirector::Request.new(:myind, :find, "my key", nil, options) end def the_parsed_query_string_from(request) CGI.parse(request.query_string.sub(/^\?/, '')) end it "should return an empty query string if there are no options" do request = a_request_with_options(nil) request.query_string.should == "" end it "should return an empty query string if the options are empty" do request = a_request_with_options({}) request.query_string.should == "" end it "should prefix the query string with '?'" do request = a_request_with_options(:one => "two") request.query_string.should =~ /^\?/ end it "should include all options in the query string, separated by '&'" do request = a_request_with_options(:one => "two", :three => "four") the_parsed_query_string_from(request).should == { "one" => ["two"], "three" => ["four"] } end it "should ignore nil options" do request = a_request_with_options(:one => "two", :three => nil) the_parsed_query_string_from(request).should == { "one" => ["two"] } end it "should convert 'true' option values into strings" do request = a_request_with_options(:one => true) the_parsed_query_string_from(request).should == { "one" => ["true"] } end it "should convert 'false' option values into strings" do request = a_request_with_options(:one => false) the_parsed_query_string_from(request).should == { "one" => ["false"] } end it "should convert to a string all option values that are integers" do request = a_request_with_options(:one => 50) the_parsed_query_string_from(request).should == { "one" => ["50"] } end it "should convert to a string all option values that are floating point numbers" do request = a_request_with_options(:one => 1.2) the_parsed_query_string_from(request).should == { "one" => ["1.2"] } end it "should CGI-escape all option values that are strings" do request = a_request_with_options(:one => "one two") the_parsed_query_string_from(request).should == { "one" => ["one two"] } end it "should convert an array of values into multiple entries for the same key" do request = a_request_with_options(:one => %w{one two}) the_parsed_query_string_from(request).should == { "one" => ["one", "two"] } end it "should convert an array of values into a single yaml entry when in legacy mode" do Puppet[:legacy_query_parameter_serialization] = true request = a_request_with_options(:one => %w{one two}) the_parsed_query_string_from(request).should == { "one" => ["--- \n - one\n - two"] } end it "should stringify simple data types inside an array" do request = a_request_with_options(:one => ['one', nil]) the_parsed_query_string_from(request).should == { "one" => ["one"] } end it "should error if an array contains another array" do request = a_request_with_options(:one => ['one', ["not allowed"]]) expect { request.query_string }.to raise_error(ArgumentError) end it "should error if an array contains illegal data" do request = a_request_with_options(:one => ['one', { :not => "allowed" }]) expect { request.query_string }.to raise_error(ArgumentError) end it "should convert to a string and CGI-escape all option values that are symbols" do request = a_request_with_options(:one => :"sym bol") the_parsed_query_string_from(request).should == { "one" => ["sym bol"] } end it "should fail if options other than booleans or strings are provided" do request = a_request_with_options(:one => { :one => :two }) expect { request.query_string }.to raise_error(ArgumentError) end end describe "when converting to json" do before do @request = Puppet::Indirector::Request.new(:facts, :find, "foo", nil) end it "should produce a hash with the document_type set to 'request'" do @request.should set_json_document_type_to("IndirectorRequest") end it "should set the 'key'" do @request.should set_json_attribute("key").to("foo") end it "should include an attribute for its indirection name" do @request.should set_json_attribute("type").to("facts") end it "should include a 'method' attribute set to its method" do @request.should set_json_attribute("method").to("find") end it "should add all attributes under the 'attributes' attribute" do @request.ip = "127.0.0.1" @request.should set_json_attribute("attributes", "ip").to("127.0.0.1") end it "should add all options under the 'attributes' attribute" do @request.options["opt"] = "value" PSON.parse(@request.to_pson)["data"]['attributes']['opt'].should == "value" end it "should include the instance if provided" do facts = Puppet::Node::Facts.new("foo") @request.instance = facts PSON.parse(@request.to_pson)["data"]['instance'].should be_instance_of(Hash) end end describe "when converting from json" do before do @request = Puppet::Indirector::Request.new(:facts, :find, "foo", nil) @klass = Puppet::Indirector::Request @format = Puppet::Network::FormatHandler.format('pson') end def from_json(json) @format.intern(Puppet::Indirector::Request, json) end it "should set the 'key'" do from_json(@request.to_pson).key.should == "foo" end it "should fail if no key is provided" do json = PSON.parse(@request.to_pson) json['data'].delete("key") expect { from_json(json.to_pson) }.to raise_error(ArgumentError) end it "should set its indirector name" do from_json(@request.to_pson).indirection_name.should == :facts end it "should fail if no type is provided" do json = PSON.parse(@request.to_pson) json['data'].delete("type") expect { from_json(json.to_pson) }.to raise_error(ArgumentError) end it "should set its method" do from_json(@request.to_pson).method.should == "find" end it "should fail if no method is provided" do json = PSON.parse(@request.to_pson) json['data'].delete("method") expect { from_json(json.to_pson) }.to raise_error(ArgumentError) end it "should initialize with all attributes and options" do @request.ip = "127.0.0.1" @request.options["opt"] = "value" result = from_json(@request.to_pson) result.options[:opt].should == "value" result.ip.should == "127.0.0.1" end it "should set its instance as an instance if one is provided" do facts = Puppet::Node::Facts.new("foo") @request.instance = facts result = from_json(@request.to_pson) result.instance.should be_instance_of(Puppet::Node::Facts) end end context '#do_request' do before :each do @request = Puppet::Indirector::Request.new(:myind, :find, "my key", nil) end context 'when not using SRV records' do before :each do Puppet.settings[:use_srv_records] = false end it "yields the request with the default server and port when no server or port were specified on the original request" do count = 0 rval = @request.do_request(:puppet, 'puppet.example.com', '90210') do |got| count += 1 got.server.should == 'puppet.example.com' got.port.should == '90210' 'Block return value' end count.should == 1 rval.should == 'Block return value' end end context 'when using SRV records' do before :each do Puppet.settings[:use_srv_records] = true Puppet.settings[:srv_domain] = 'example.com' end it "yields the request with the original server and port unmodified" do @request.server = 'puppet.example.com' @request.port = '90210' count = 0 rval = @request.do_request do |got| count += 1 got.server.should == 'puppet.example.com' got.port.should == '90210' 'Block return value' end count.should == 1 rval.should == 'Block return value' end context "when SRV returns servers" do before :each do @dns_mock = mock('dns') Resolv::DNS.expects(:new).returns(@dns_mock) @port = 7205 @host = '_x-puppet._tcp.example.com' @srv_records = [Resolv::DNS::Resource::IN::SRV.new(0, 0, @port, @host)] @dns_mock.expects(:getresources). with("_x-puppet._tcp.#{Puppet.settings[:srv_domain]}", Resolv::DNS::Resource::IN::SRV). returns(@srv_records) end it "yields a request using the server and port from the SRV record" do count = 0 rval = @request.do_request do |got| count += 1 got.server.should == '_x-puppet._tcp.example.com' got.port.should == 7205 @block_return end count.should == 1 rval.should == @block_return end it "should fall back to the default server when the block raises a SystemCallError" do count = 0 second_pass = nil rval = @request.do_request(:puppet, 'puppet', 8140) do |got| count += 1 if got.server == '_x-puppet._tcp.example.com' then raise SystemCallError, "example failure" else second_pass = got end @block_return end second_pass.server.should == 'puppet' second_pass.port.should == 8140 count.should == 2 rval.should == @block_return end end end end describe "#remote?" do def request(options = {}) Puppet::Indirector::Request.new('node', 'find', 'localhost', nil, options) end it "should not be unless node or ip is set" do request.should_not be_remote end it "should be remote if node is set" do request(:node => 'example.com').should be_remote end it "should be remote if ip is set" do request(:ip => '127.0.0.1').should be_remote end it "should be remote if node and ip are set" do request(:node => 'example.com', :ip => '127.0.0.1').should be_remote end end end diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index 8e3c46989..b8c3fdeea 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -1,710 +1,710 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/modules' require 'puppet/module_tool/checksums' describe Puppet::Module do include PuppetSpec::Files let(:env) { mock("environment") } let(:path) { "/path" } let(:name) { "mymod" } let(:mod) { Puppet::Module.new(name, path, env) } before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory Puppet::FileSystem.stubs(:exist?).returns false end it "should have a class method that returns a named module from a given environment" do env = mock 'module' env.expects(:module).with(name).returns "yep" Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find(name, "myenv").should == "yep" end it "should return nil if asked for a named module that doesn't exist" do env = mock 'module' env.expects(:module).with(name).returns nil Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find(name, "myenv").should be_nil end describe "attributes" do it "should support a 'version' attribute" do mod.version = 1.09 mod.version.should == 1.09 end it "should support a 'source' attribute" do mod.source = "http://foo/bar" mod.source.should == "http://foo/bar" end it "should support a 'project_page' attribute" do mod.project_page = "http://foo/bar" mod.project_page.should == "http://foo/bar" end it "should support an 'author' attribute" do mod.author = "Luke Kanies " mod.author.should == "Luke Kanies " end it "should support a 'license' attribute" do mod.license = "GPL2" mod.license.should == "GPL2" end it "should support a 'summary' attribute" do mod.summary = "GPL2" mod.summary.should == "GPL2" end it "should support a 'description' attribute" do mod.description = "GPL2" mod.description.should == "GPL2" end it "should support specifying a compatible puppet version" do mod.puppetversion = "0.25" mod.puppetversion.should == "0.25" end end it "should validate that the puppet version is compatible" do mod.puppetversion = "0.25" Puppet.expects(:version).returns "0.25" mod.validate_puppet_version end it "should fail if the specified puppet version is not compatible" do mod.puppetversion = "0.25" Puppet.stubs(:version).returns "0.24" lambda { mod.validate_puppet_version }.should raise_error(Puppet::Module::IncompatibleModule) end describe "when finding unmet dependencies" do before do Puppet::FileSystem.unstub(:exist?) @modpath = tmpdir('modpath') Puppet.settings[:modulepath] = @modpath end it "should list modules that are missing" do metadata_file = "#{@modpath}/needy/metadata.json" mod = PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar" }] } ) mod.unmet_dependencies.should == [{ :reason => :missing, :name => "baz/foobar", :version_constraint => ">= 2.2.0", :parent => { :name => 'puppetlabs/needy', :version => 'v9.9.9' }, :mod_details => { :installed_version => nil } }] end it "should list modules that are missing and have invalid names" do metadata_file = "#{@modpath}/needy/metadata.json" mod = PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar=bar" }] } ) mod.unmet_dependencies.should == [{ :reason => :missing, :name => "baz/foobar=bar", :version_constraint => ">= 2.2.0", :parent => { :name => 'puppetlabs/needy', :version => 'v9.9.9' }, :mod_details => { :installed_version => nil } }] end it "should list modules with unmet version requirement" do - env = Puppet::Node::Environment.create(:testing, [@modpath], '') + env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'test_gte_req', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar" }] }, :environment => env ) mod2 = PuppetSpec::Modules.create( 'test_specific_req', @modpath, :metadata => { :dependencies => [{ "version_requirement" => "1.0.0", "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '2.0.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should == [{ :reason => :version_mismatch, :name => "baz/foobar", :version_constraint => ">= 2.2.0", :parent => { :version => "v9.9.9", :name => "puppetlabs/test_gte_req" }, :mod_details => { :installed_version => "2.0.0" } }] mod2.unmet_dependencies.should == [{ :reason => :version_mismatch, :name => "baz/foobar", :version_constraint => "v1.0.0", :parent => { :version => "v9.9.9", :name => "puppetlabs/test_specific_req" }, :mod_details => { :installed_version => "2.0.0" } }] end it "should consider a dependency without a version requirement to be satisfied" do - env = Puppet::Node::Environment.create(:testing, [@modpath], '') + env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [{ "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '2.0.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should be_empty end it "should consider a dependency without a semantic version to be unmet" do - env = Puppet::Node::Environment.create(:testing, [@modpath], '') + env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [{ "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '5.1', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should == [{ :reason => :non_semantic_version, :parent => { :version => "v9.9.9", :name => "puppetlabs/foobar" }, :mod_details => { :installed_version => "5.1" }, :name => "baz/foobar", :version_constraint => ">= 0.0.0" }] end it "should have valid dependencies when no dependencies have been specified" do mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [] } ) mod.unmet_dependencies.should == [] end it "should only list unmet dependencies" do - env = Puppet::Node::Environment.create(:testing, [@modpath], '') + env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( name, @modpath, :metadata => { :dependencies => [ { "version_requirement" => ">= 2.2.0", "name" => "baz/satisfied" }, { "version_requirement" => ">= 2.2.0", "name" => "baz/notsatisfied" } ] }, :environment => env ) PuppetSpec::Modules.create( 'satisfied', @modpath, :metadata => { :version => '3.3.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should == [{ :reason => :missing, :mod_details => { :installed_version => nil }, :parent => { :version => "v9.9.9", :name => "puppetlabs/#{name}" }, :name => "baz/notsatisfied", :version_constraint => ">= 2.2.0" }] end it "should be empty when all dependencies are met" do - env = Puppet::Node::Environment.create(:testing, [@modpath], '') + env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'mymod2', @modpath, :metadata => { :dependencies => [ { "version_requirement" => ">= 2.2.0", "name" => "baz/satisfied" }, { "version_requirement" => "< 2.2.0", "name" => "baz/alsosatisfied" } ] }, :environment => env ) PuppetSpec::Modules.create( 'satisfied', @modpath, :metadata => { :version => '3.3.0', :author => 'baz' }, :environment => env ) PuppetSpec::Modules.create( 'alsosatisfied', @modpath, :metadata => { :version => '2.1.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should be_empty end end describe "when managing supported platforms" do it "should support specifying a supported platform" do mod.supports "solaris" end it "should support specifying a supported platform and version" do mod.supports "solaris", 1.0 end end it "should return nil if asked for a module whose name is 'nil'" do Puppet::Module.find(nil, "myenv").should be_nil end it "should provide support for logging" do Puppet::Module.ancestors.should be_include(Puppet::Util::Logging) end it "should be able to be converted to a string" do mod.to_s.should == "Module #{name}(#{path})" end it "should fail if its name is not alphanumeric" do lambda { Puppet::Module.new(".something", "/path", env) }.should raise_error(Puppet::Module::InvalidName) end it "should require a name at initialization" do lambda { Puppet::Module.new }.should raise_error(ArgumentError) end it "should accept an environment at initialization" do Puppet::Module.new("foo", "/path", env).environment.should == env end describe '#modulepath' do it "should return the directory the module is installed in, if a path exists" do mod = Puppet::Module.new("foo", "/a/foo", env) mod.modulepath.should == '/a' end end [:plugins, :pluginfacts, :templates, :files, :manifests].each do |filetype| case filetype when :plugins dirname = "lib" when :pluginfacts dirname = "facts.d" else dirname = filetype.to_s end it "should be able to return individual #{filetype}" do module_file = File.join(path, dirname, "my/file") Puppet::FileSystem.expects(:exist?).with(module_file).returns true mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should == module_file end it "should consider #{filetype} to be present if their base directory exists" do module_file = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(module_file).returns true mod.send(filetype.to_s + "?").should be_true end it "should consider #{filetype} to be absent if their base directory does not exist" do module_file = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(module_file).returns false mod.send(filetype.to_s + "?").should be_false end it "should return nil if asked to return individual #{filetype} that don't exist" do module_file = File.join(path, dirname, "my/file") Puppet::FileSystem.expects(:exist?).with(module_file).returns false mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end it "should return the base directory if asked for a nil path" do base = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(base).returns true mod.send(filetype.to_s.sub(/s$/, ''), nil).should == base end end it "should return the path to the plugin directory" do mod.plugin_directory.should == File.join(path, "lib") end end describe Puppet::Module, "when finding matching manifests" do before do @mod = Puppet::Module.new("mymod", "/a", mock("environment")) @pq_glob_with_extension = "yay/*.xx" @fq_glob_with_extension = "/a/manifests/#{@pq_glob_with_extension}" end it "should return all manifests matching the glob pattern" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.stubs(:directory?).returns false @mod.match_manifests(@pq_glob_with_extension).should == %w{foo bar} end it "should not return directories" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.expects(:directory?).with("foo").returns false FileTest.expects(:directory?).with("bar").returns true @mod.match_manifests(@pq_glob_with_extension).should == %w{foo} end it "should default to the 'init' file if no glob pattern is specified" do Puppet::FileSystem.expects(:exist?).with("/a/manifests/init.pp").returns(true) Puppet::FileSystem.expects(:exist?).with("/a/manifests/init.rb").returns(false) @mod.match_manifests(nil).should == %w{/a/manifests/init.pp} end it "should return all manifests matching the glob pattern in all existing paths" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{a b}) @mod.match_manifests(@pq_glob_with_extension).should == %w{a b} end it "should match the glob pattern plus '.{pp,rb}' if no extention is specified" do Dir.expects(:glob).with("/a/manifests/yay/foo.{pp,rb}").returns(%w{yay}) @mod.match_manifests("yay/foo").should == %w{yay} end it "should return an empty array if no manifests matched" do Dir.expects(:glob).with(@fq_glob_with_extension).returns([]) @mod.match_manifests(@pq_glob_with_extension).should == [] end it "should raise an error if the pattern tries to leave the manifest directory" do expect do @mod.match_manifests("something/../../*") end.to raise_error(Puppet::Module::InvalidFilePattern, 'The pattern "something/../../*" to find manifests in the module "mymod" is invalid and potentially unsafe.') end end describe Puppet::Module do include PuppetSpec::Files before do @modpath = tmpdir('modpath') @module = PuppetSpec::Modules.create('mymod', @modpath) end it "should use 'License' in its current path as its metadata file" do @module.license_file.should == "#{@modpath}/mymod/License" end it "should cache the license file" do @module.expects(:path).once.returns nil @module.license_file @module.license_file end it "should use 'metadata.json' in its current path as its metadata file" do @module.metadata_file.should == "#{@modpath}/mymod/metadata.json" end it "should have metadata if it has a metadata file and its data is not empty" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should have metadata if it has a metadata file and its data is not empty" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should not have metadata if has a metadata file and its data is empty" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "/* +-----------------------------------------------------------------------+ | | | ==> DO NOT EDIT THIS FILE! <== | | | | You should edit the `Modulefile` and run `puppet-module build` | | to generate the `metadata.json` file for your releases. | | | +-----------------------------------------------------------------------+ */ {}" @module.should_not be_has_metadata end it "should know if it is missing a metadata file" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns false @module.should_not be_has_metadata end it "should be able to parse its metadata file" do @module.should respond_to(:load_metadata) end it "should parse its metadata file on initialization if it is present" do Puppet::Module.any_instance.expects(:has_metadata?).returns true Puppet::Module.any_instance.expects(:load_metadata) Puppet::Module.new("yay", "/path", mock("env")) end it "should tolerate failure to parse" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns(my_fixture('trailing-comma.json')) @module.has_metadata?.should be_false end def a_module_with_metadata(data) text = data.to_pson mod = Puppet::Module.new("foo", "/path", mock("env")) mod.stubs(:metadata_file).returns "/my/file" File.stubs(:read).with("/my/file").returns text mod end describe "when loading the metadata file" do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25", :dependencies => [] } @module = a_module_with_metadata(@data) end %w{source author version license}.each do |attr| it "should set #{attr} if present in the metadata file" do @module.load_metadata @module.send(attr).should == @data[attr.to_sym] end it "should fail if #{attr} is not present in the metadata file" do @data.delete(attr.to_sym) @text = @data.to_pson File.stubs(:read).with("/my/file").returns @text lambda { @module.load_metadata }.should raise_error( Puppet::Module::MissingMetadata, "No #{attr} module metadata provided for foo" ) end end it "should set puppetversion if present in the metadata file" do @module.load_metadata @module.puppetversion.should == @data[:puppetversion] end context "when versionRequirement is used for dependency version info" do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25", :dependencies => [ { "versionRequirement" => "0.0.1", "name" => "pmtacceptance/stdlib" }, { "versionRequirement" => "0.1.0", "name" => "pmtacceptance/apache" } ] } @module = a_module_with_metadata(@data) end it "should set the dependency version_requirement key" do @module.load_metadata @module.dependencies[0]['version_requirement'].should == "0.0.1" end it "should set the version_requirement key for all dependencies" do @module.load_metadata @module.dependencies[0]['version_requirement'].should == "0.0.1" @module.dependencies[1]['version_requirement'].should == "0.1.0" end end end it "should be able to tell if there are local changes" do modpath = tmpdir('modpath') foo_checksum = 'acbd18db4cc2f85cedef654fccc4a4d8' checksummed_module = PuppetSpec::Modules.create( 'changed', modpath, :metadata => { :checksums => { "foo" => foo_checksum, } } ) foo_path = Pathname.new(File.join(checksummed_module.path, 'foo')) IO.binwrite(foo_path, 'notfoo') Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should_not == foo_checksum checksummed_module.has_local_changes?.should be_true IO.binwrite(foo_path, 'foo') Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should == foo_checksum checksummed_module.has_local_changes?.should be_false end it "should know what other modules require it" do - env = Puppet::Node::Environment.create(:testing, [@modpath], '') + env = Puppet::Node::Environment.create(:testing, [@modpath]) dependable = PuppetSpec::Modules.create( 'dependable', @modpath, :metadata => {:author => 'puppetlabs'}, :environment => env ) PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :author => 'beggar', :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "puppetlabs/dependable" }] }, :environment => env ) PuppetSpec::Modules.create( 'wantit', @modpath, :metadata => { :author => 'spoiled', :dependencies => [{ "version_requirement" => "< 5.0.0", "name" => "puppetlabs/dependable" }] }, :environment => env ) dependable.required_by.should =~ [ { "name" => "beggar/needy", "version" => "9.9.9", "version_requirement" => ">= 2.2.0" }, { "name" => "spoiled/wantit", "version" => "9.9.9", "version_requirement" => "< 5.0.0" } ] end end diff --git a/spec/unit/module_tool/applications/installer_spec.rb b/spec/unit/module_tool/applications/installer_spec.rb index dc6d7b009..63c8cf48e 100644 --- a/spec/unit/module_tool/applications/installer_spec.rb +++ b/spec/unit/module_tool/applications/installer_spec.rb @@ -1,331 +1,331 @@ require 'spec_helper' require 'puppet/module_tool/applications' require 'puppet_spec/modules' require 'semver' describe Puppet::ModuleTool::Applications::Installer do include PuppetSpec::Files let(:unpacker) { stub(:run) } let(:installer_class) { Puppet::ModuleTool::Applications::Installer } let(:modpath1) do path = File.join(tmpdir("installer"), "modpath1") FileUtils.mkdir_p(path) path end let(:stdlib_pkg) do mod = File.join(modpath1, "pmtacceptance-stdlib-0.0.1.tar.gz") FileUtils.touch(mod) mod end - let(:env) { Puppet::Node::Environment.create(:env, [modpath1], '') } + let(:env) { Puppet::Node::Environment.create(:env, [modpath1]) } let(:options) do { :target_dir => modpath1, :environment_instance => env, } end let(:forge) do forge = mock("Puppet::Forge") forge.stubs(:remote_dependency_info).returns(remote_dependency_info) forge.stubs(:uri).returns('forge-dev.puppetlabs.com') remote_dependency_info.each_key do |mod| remote_dependency_info[mod].each do |release| forge.stubs(:retrieve).with(release['file']).returns("/fake_cache#{release['file']}") end end forge end let(:install_dir) do install_dir = mock("Puppet::ModuleTool::InstallDirectory") install_dir.stubs(:prepare) install_dir end let(:remote_dependency_info) do { "pmtacceptance/apache" => [ { "dependencies" => [], "version" => "1.0.0-alpha", "file" => "/pmtacceptance-apache-1.0.0-alpha.tar.gz" }, { "dependencies" => [], "version" => "1.0.0-beta", "file" => "/pmtacceptance-apache-1.0.0-beta.tar.gz" }, { "dependencies" => [], "version" => "1.0.0-rc1", "file" => "/pmtacceptance-apache-1.0.0-rc1.tar.gz" }, ], "pmtacceptance/stdlib" => [ { "dependencies" => [], "version" => "0.0.1", "file" => "/pmtacceptance-stdlib-0.0.1.tar.gz" }, { "dependencies" => [], "version" => "0.0.2", "file" => "/pmtacceptance-stdlib-0.0.2.tar.gz" }, { "dependencies" => [], "version" => "1.0.0-pre", "file" => "/pmtacceptance-stdlib-1.0.0-pre.tar.gz" }, { "dependencies" => [], "version" => "1.0.0", "file" => "/pmtacceptance-stdlib-1.0.0.tar.gz" }, { "dependencies" => [], "version" => "1.5.0-pre", "file" => "/pmtacceptance-stdlib-1.5.0-pre.tar.gz" }, ], "pmtacceptance/java" => [ { "dependencies" => [["pmtacceptance/stdlib", ">= 0.0.1"]], "version" => "1.7.0", "file" => "/pmtacceptance-java-1.7.0.tar.gz" }, { "dependencies" => [["pmtacceptance/stdlib", "1.0.0"]], "version" => "1.7.1", "file" => "/pmtacceptance-java-1.7.1.tar.gz" } ], "pmtacceptance/apollo" => [ { "dependencies" => [ ["pmtacceptance/java", "1.7.1"], ["pmtacceptance/stdlib", "0.0.1"] ], "version" => "0.0.1", "file" => "/pmtacceptance-apollo-0.0.1.tar.gz" }, { "dependencies" => [ ["pmtacceptance/java", ">= 1.7.0"], ["pmtacceptance/stdlib", ">= 1.0.0"] ], "version" => "0.0.2", "file" => "/pmtacceptance-apollo-0.0.2.tar.gz" } ] } end describe "the behavior of .is_module_package?" do it "should return true when file is a module package" do installer = installer_class.new("foo", forge, install_dir, options) installer.send(:is_module_package?, stdlib_pkg).should be_true end it "should return false when file is not a module package" do installer = installer_class.new("foo", forge, install_dir, options) installer.send(:is_module_package?, "pmtacceptance-apollo-0.0.2.tar"). should be_false end end context "when the source is a repository" do it "should require a valid name" do lambda { installer_class.run('puppet', install_dir, params) }.should raise_error(ArgumentError, "Could not install module with invalid name: puppet") end it "should install the current stable version of the requested module" do Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). returns(unpacker) results = installer_class.run('pmtacceptance-stdlib', forge, install_dir, options) results[:installed_modules].length == 1 results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" results[:installed_modules][0][:version][:vstring].should == "1.0.0" end it "should install the most recent version of requested module in the absence of a stable version" do Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-apache-1.0.0-rc1.tar.gz', options). returns(unpacker) results = installer_class.run('pmtacceptance-apache', forge, install_dir, options) results[:installed_modules].length == 1 results[:installed_modules][0][:module].should == "pmtacceptance-apache" results[:installed_modules][0][:version][:vstring].should == "1.0.0-rc1" end it "should install the most recent stable version of requested module for the requested version range" do Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options.merge(:version => '1.x')). returns(unpacker) results = installer_class.run('pmtacceptance-stdlib', forge, install_dir, options.merge(:version => '1.x')) results[:installed_modules].length == 1 results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" results[:installed_modules][0][:version][:vstring].should == "1.0.0" end it "should install the most recent version of requested module for the requested version range in the absence of a stable version" do Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-stdlib-1.5.0-pre.tar.gz', options.merge(:version => '1.5.0-pre')). returns(unpacker) results = installer_class.run('pmtacceptance-stdlib', forge, install_dir, options.merge(:version => '1.5.0-pre')) results[:installed_modules].length == 1 results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" results[:installed_modules][0][:version][:vstring].should == "1.5.0-pre" end context "should check the target directory" do let(:installer) do installer_class.new('pmtacceptance-stdlib', forge, install_dir, options) end def expect_normal_unpacker Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). returns(unpacker) end def expect_normal_results results end it "(#15202) prepares the install directory" do expect_normal_unpacker install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest") results = installer.run results[:installed_modules].length.should eq 1 results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" results[:installed_modules][0][:version][:vstring].should == "1.0.0" end it "(#15202) reports an error when the install directory cannot be prepared" do install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest"). raises(Puppet::ModuleTool::Errors::PermissionDeniedCreateInstallDirectoryError.new("original", :module => "pmtacceptance-stdlib")) results = installer.run results[:result].should == :failure results[:error][:oneline].should =~ /Permission is denied/ end end context "when the requested module has dependencies" do it "should install dependencies" do Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). returns(unpacker) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). returns(unpacker) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-java-1.7.1.tar.gz', options). returns(unpacker) results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) installed_dependencies = results[:installed_modules][0][:dependencies] dependencies = installed_dependencies.inject({}) do |result, dep| result[dep[:module]] = dep[:version][:vstring] result end dependencies.length.should == 2 dependencies['pmtacceptance-java'].should == '1.7.1' dependencies['pmtacceptance-stdlib'].should == '1.0.0' end it "should install requested module if the '--force' flag is used" do options.merge!(:force => true) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). returns(unpacker) results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) results[:installed_modules][0][:module].should == "pmtacceptance-apollo" end it "should not install dependencies if the '--force' flag is used" do options.merge!(:force => true) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). returns(unpacker) results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) dependencies = results[:installed_modules][0][:dependencies] dependencies.should == [] end it "should not install dependencies if the '--ignore-dependencies' flag is used" do options.merge!(:ignore_dependencies => true) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). returns(unpacker) results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) dependencies = results[:installed_modules][0][:dependencies] dependencies.should == [] end it "should set an error if dependencies can't be resolved" do options.merge!(:version => '0.0.1') oneline = "'pmtacceptance-apollo' (v0.0.1) requested; Invalid dependency cycle" multiline = <<-MSG.strip Could not install module 'pmtacceptance-apollo' (v0.0.1) No version of 'pmtacceptance-stdlib' will satisfy dependencies You specified 'pmtacceptance-apollo' (v0.0.1), which depends on 'pmtacceptance-java' (v1.7.1), which depends on 'pmtacceptance-stdlib' (v1.0.0) Use `puppet module install --force` to install this module anyway MSG results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) results[:result].should == :failure results[:error][:oneline].should == oneline results[:error][:multiline].should == multiline end it "resolves conflicts for each dependency only once" do Puppet::Log.level = :debug installed_modules = [ Puppet::Module.new('ntp', File.join(modpath1, 'ntp'), env.name), Puppet::Module.new('mysql', File.join(modpath1, 'mysql'), env.name), Puppet::Module.new('apache', File.join(modpath1, 'apache'), env.name) ] env.stubs(:modules_by_path).returns({modpath1 => installed_modules}) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). returns(unpacker) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-java-1.7.1.tar.gz', options). returns(unpacker) Puppet::ModuleTool::Applications::Unpacker.expects(:new). with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). returns(unpacker) installer_class.run('pmtacceptance-apollo', forge, install_dir, options) modules = @logs.map do |log| data = log.message.match(/Resolving conflicts for (.*)/) data ? data[1] : nil end.compact expect(modules).to eq(["pmtacceptance-apollo", "pmtacceptance-java,pmtacceptance-stdlib"]) end end context "when there are modules installed" do it "should use local version when already exists and satisfies constraints" it "should reinstall the local version if force is used" it "should upgrade local version when necessary to satisfy constraints" it "should error when a local version can't be upgraded to satisfy constraints" end context "when a local module needs upgrading to satisfy constraints but has changes" do it "should error" it "should warn and continue if force is used" end it "should error when a local version of a dependency has no version metadata" it "should error when a local version of a dependency has a non-semver version" it "should error when a local version of a dependency has a different forge name" it "should error when a local version of a dependency has no metadata" end context "when the source is a filesystem" do before do @sourcedir = tmpdir('sourcedir') end it "should error if it can't parse the name" it "should try to get_release_package_from_filesystem if it has a valid name" end end diff --git a/spec/unit/module_tool/applications/uninstaller_spec.rb b/spec/unit/module_tool/applications/uninstaller_spec.rb index f7b1d9bba..2ec65c6e4 100644 --- a/spec/unit/module_tool/applications/uninstaller_spec.rb +++ b/spec/unit/module_tool/applications/uninstaller_spec.rb @@ -1,207 +1,207 @@ require 'spec_helper' require 'puppet/module_tool' require 'tmpdir' require 'puppet_spec/modules' describe Puppet::ModuleTool::Applications::Uninstaller do include PuppetSpec::Files def mkmod(name, path, metadata=nil) modpath = File.join(path, name) FileUtils.mkdir_p(modpath) if metadata File.open(File.join(modpath, 'metadata.json'), 'w') do |f| f.write(metadata.to_pson) end end modpath end describe "the behavior of the instances" do let(:working_dir) { tmpdir("uninstaller") } let(:modpath1) { create_temp_dir("modpath1") } let(:modpath2) { create_temp_dir("modpath2") } - let(:env) { Puppet::Node::Environment.create(:env, [modpath1, modpath2], '') } + let(:env) { Puppet::Node::Environment.create(:env, [modpath1, modpath2]) } let(:options) { { :environment_instance => env } } let(:uninstaller) { Puppet::ModuleTool::Applications::Uninstaller.new("puppetlabs-foo", options) } def create_temp_dir(name) path = File.join(working_dir, name) FileUtils.mkdir_p(path) path end let(:foo_metadata) do { :author => "puppetlabs", :name => "puppetlabs/foo", :version => "1.0.0", :source => "http://dummyurl/foo", :license => "Apache2", :dependencies => [], } end let(:bar_metadata) do { :author => "puppetlabs", :name => "puppetlabs/bar", :version => "1.0.0", :source => "http://dummyurl/bar", :license => "Apache2", :dependencies => [], } end context "when the module is not installed" do it "should fail" do Puppet::ModuleTool::Applications::Uninstaller.new('fakemod_not_installed', options).run[:result].should == :failure end end context "when the module is installed" do it "should uninstall the module" do PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) results = uninstaller.run results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end it "should only uninstall the requested module" do PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) PuppetSpec::Modules.create('bar', modpath1, :metadata => bar_metadata) results = uninstaller.run results[:affected_modules].length == 1 results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end it "should uninstall fail if a module exists twice in the modpath" do PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) PuppetSpec::Modules.create('foo', modpath2, :metadata => foo_metadata) uninstaller.run[:result].should == :failure end context "when options[:version] is specified" do it "should uninstall the module if the version matches" do PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) options[:version] = "1.0.0" results = uninstaller.run results[:affected_modules].length.should == 1 results[:affected_modules].first.forge_name.should == "puppetlabs/foo" results[:affected_modules].first.version.should == "1.0.0" end it "should not uninstall the module if the version does not match" do PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) options[:version] = "2.0.0" uninstaller.run[:result].should == :failure end end context "when the module metadata is missing" do it "should not uninstall the module" do PuppetSpec::Modules.create('foo', modpath1) uninstaller.run[:result].should == :failure end end context "when the module has local changes" do it "should not uninstall the module" do mod = PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) Puppet::ModuleTool::Applications::Checksummer.expects(:run).with(mod.path).returns(['change']) uninstaller.run[:result].should == :failure end end context "when the module does not have local changes" do it "should uninstall the module" do PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) results = uninstaller.run results[:affected_modules].length.should == 1 results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end end context "when uninstalling the module will cause broken dependencies" do it "should not uninstall the module" do Puppet.settings[:modulepath] = modpath1 PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) PuppetSpec::Modules.create( 'needy', modpath1, :metadata => { :author => 'beggar', :dependencies => [{ "version_requirement" => ">= 1.0.0", "name" => "puppetlabs/foo" }] } ) uninstaller.run[:result].should == :failure end end context "when using the --force flag" do let(:fakemod) do stub( :forge_name => 'puppetlabs/fakemod', :version => '0.0.1', :has_local_changes? => true ) end it "should ignore local changes" do foo = mkmod("foo", modpath1, foo_metadata) options[:force] = true results = uninstaller.run results[:affected_modules].length.should == 1 results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end it "should ignore broken dependencies" do Puppet.settings[:modulepath] = modpath1 PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) PuppetSpec::Modules.create( 'needy', modpath1, :metadata => { :author => 'beggar', :dependencies => [{ "version_requirement" => ">= 1.0.0", "name" => "puppetlabs/foo" }] } ) options[:force] = true results = uninstaller.run results[:affected_modules].length.should == 1 results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end end end end end diff --git a/spec/unit/module_tool_spec.rb b/spec/unit/module_tool_spec.rb index 63bd5e295..083cf74ce 100755 --- a/spec/unit/module_tool_spec.rb +++ b/spec/unit/module_tool_spec.rb @@ -1,204 +1,204 @@ #! /usr/bin/env ruby # encoding: UTF-8 require 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool do describe '.is_module_root?' do it 'should return true if directory has a module file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/Modulefile')). returns(true) subject.is_module_root?(Pathname.new('/a/b/c')).should be_true end it 'should return false if directory does not have a module file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/Modulefile')). returns(false) subject.is_module_root?(Pathname.new('/a/b/c')).should be_false end end describe '.find_module_root' do let(:sample_path) { Pathname.new('/a/b/c').expand_path } it 'should return the first path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?).with(sample_path). returns(true) subject.find_module_root(sample_path).should == sample_path end it 'should return a parent path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b/c'))).returns(false) Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b'))).returns(true) subject.find_module_root(sample_path).should == Pathname.new('/a/b').expand_path end it 'should return nil when no module root can be found' do Puppet::ModuleTool.expects(:is_module_root?).at_least_once.returns(false) subject.find_module_root(sample_path).should be_nil end end describe '.format_tree' do it 'should return an empty tree when given an empty list' do subject.format_tree([]).should == '' end it 'should return a shallow when given a list without dependencies' do list = [ { :text => 'first' }, { :text => 'second' }, { :text => 'third' } ] subject.format_tree(list).should == <<-TREE ├── first ├── second └── third TREE end it 'should return a deeply nested tree when given a list with deep dependencies' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, ] subject.format_tree(list).should == <<-TREE └─┬ first └─┬ second └── third TREE end it 'should show connectors when deep dependencies are not on the last node of the top level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, { :text => 'fourth' } ] subject.format_tree(list).should == <<-TREE ├─┬ first │ └─┬ second │ └── third └── fourth TREE end it 'should show connectors when deep dependencies are not on the last node of any level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] } ] subject.format_tree(list).should == <<-TREE └─┬ first ├─┬ second │ └── third └── fourth TREE end it 'should show connectors in every case when deep dependencies are not on the last node' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] }, { :text => 'fifth' } ] subject.format_tree(list).should == <<-TREE ├─┬ first │ ├─┬ second │ │ └── third │ └── fourth └── fifth TREE end end describe '.set_option_defaults' do let(:options) { {} } let(:modulepath) { [File.expand_path('/env/module/path'), File.expand_path('/global/module/path')] } - let(:environment) { Puppet::Node::Environment.create('current', modulepath, '') } + let(:environment) { Puppet::Node::Environment.create('current', modulepath) } around(:each) do |example| Puppet.override(:current_environment => environment) do example.run end end describe 'option :environment_instance' do it 'adds an environment_instance to the options hash' do subject.set_option_defaults(options) expect(options[:environment_instance].name).to eq(environment.name) expect(options[:environment_instance].modulepath).to eq(environment.modulepath) end end describe 'option :target_dir' do let (:target_dir) { 'boo' } context 'passed:' do let (:options) { {:target_dir => target_dir} } it 'prepends the target_dir into the environment_instance modulepath' do subject.set_option_defaults options expect(options[:environment_instance].full_modulepath). to eq([File.expand_path(target_dir)] + modulepath) end it 'expands the target dir' do subject.set_option_defaults options options[:target_dir].should == File.expand_path(target_dir) end end context 'NOT passed:' do it 'the option should be set to the first component of Puppet[:modulepath]' do options = Hash.new subject.set_option_defaults options expect(options[:target_dir]).to eq(environment.full_modulepath.first) end end end end end diff --git a/spec/unit/network/http/api/v1_spec.rb b/spec/unit/network/http/api/v1_spec.rb index a80838150..a00555eb1 100755 --- a/spec/unit/network/http/api/v1_spec.rb +++ b/spec/unit/network/http/api/v1_spec.rb @@ -1,519 +1,529 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/api/v1' require 'puppet/indirector_testing' describe Puppet::Network::HTTP::API::V1 do let(:not_found_code) { Puppet::Network::HTTP::Error::HTTPNotFoundError::CODE } let(:not_acceptable_code) { Puppet::Network::HTTP::Error::HTTPNotAcceptableError::CODE } let(:not_authorized_code) { Puppet::Network::HTTP::Error::HTTPNotAuthorizedError::CODE } let(:indirection) { Puppet::IndirectorTesting.indirection } let(:handler) { Puppet::Network::HTTP::API::V1.new } let(:response) { Puppet::Network::HTTP::MemoryResponse.new } def a_request_that_heads(data, request = {}) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "text/yaml", }, :method => "HEAD", :path => "/production/#{indirection.name}/#{data.value}", :params => {}, }) end def a_request_that_submits(data, request = {}) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => request[:content_type_header] || "text/yaml", }, :method => "PUT", :path => "/production/#{indirection.name}/#{data.value}", :params => {}, :body => request[:body] || data.render("text/yaml") }) end def a_request_that_destroys(data, request = {}) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "text/yaml", }, :method => "DELETE", :path => "/production/#{indirection.name}/#{data.value}", :params => {}, :body => '' }) end def a_request_that_finds(data, request = {}) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "text/yaml", }, :method => "GET", :path => "/production/#{indirection.name}/#{data.value}", :params => {}, :body => '' }) end def a_request_that_searches(key, request = {}) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "text/yaml", }, :method => "GET", :path => "/production/#{indirection.name}s/#{key}", :params => {}, :body => '' }) end before do Puppet::IndirectorTesting.indirection.terminus_class = :memory Puppet::IndirectorTesting.indirection.terminus.clear handler.stubs(:check_authorization) handler.stubs(:warn_if_near_expiration) end describe "when converting a URI into a request" do before do handler.stubs(:handler).returns "foo" end it "should require the http method, the URI, and the query parameters" do # Not a terribly useful test, but an important statement for the spec lambda { handler.uri2indirection("/foo") }.should raise_error(ArgumentError) end it "should use the first field of the URI as the environment" do handler.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].to_s.should == "env" end it "should fail if the environment is not alphanumeric" do lambda { handler.uri2indirection("GET", "/env ness/foo/bar", {}) }.should raise_error(ArgumentError) end it "should use the environment from the URI even if one is specified in the parameters" do handler.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"})[3][:environment].to_s.should == "env" end it "should not pass a buck_path parameter through (See Bugs #13553, #13518, #13511)" do handler.uri2indirection("GET", "/env/foo/bar", { :bucket_path => "/malicious/path" })[3].should_not include({ :bucket_path => "/malicious/path" }) end it "should pass allowed parameters through" do handler.uri2indirection("GET", "/env/foo/bar", { :allowed_param => "value" })[3].should include({ :allowed_param => "value" }) end it "should return the environment as a Puppet::Node::Environment" do handler.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].should be_a Puppet::Node::Environment end it "should not pass a buck_path parameter through (See Bugs #13553, #13518, #13511)" do handler.uri2indirection("GET", "/env/foo/bar", { :bucket_path => "/malicious/path" })[3].should_not include({ :bucket_path => "/malicious/path" }) end it "should pass allowed parameters through" do handler.uri2indirection("GET", "/env/foo/bar", { :allowed_param => "value" })[3].should include({ :allowed_param => "value" }) end it "should use the second field of the URI as the indirection name" do handler.uri2indirection("GET", "/env/foo/bar", {})[0].should == "foo" end it "should fail if the indirection name is not alphanumeric" do lambda { handler.uri2indirection("GET", "/env/foo ness/bar", {}) }.should raise_error(ArgumentError) end it "should use the remainder of the URI as the indirection key" do handler.uri2indirection("GET", "/env/foo/bar", {})[2].should == "bar" end it "should support the indirection key being a /-separated file path" do handler.uri2indirection("GET", "/env/foo/bee/baz/bomb", {})[2].should == "bee/baz/bomb" end it "should fail if no indirection key is specified" do lambda { handler.uri2indirection("GET", "/env/foo/", {}) }.should raise_error(ArgumentError) lambda { handler.uri2indirection("GET", "/env/foo", {}) }.should raise_error(ArgumentError) end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is singular" do handler.uri2indirection("GET", "/env/foo/bar", {})[1].should == :find end it "should choose 'find' as the indirection method if the http method is a POST and the indirection name is singular" do handler.uri2indirection("POST", "/env/foo/bar", {})[1].should == :find end it "should choose 'head' as the indirection method if the http method is a HEAD and the indirection name is singular" do handler.uri2indirection("HEAD", "/env/foo/bar", {})[1].should == :head end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is plural" do handler.uri2indirection("GET", "/env/foos/bar", {})[1].should == :search end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is facts" do handler.uri2indirection("GET", "/env/facts/bar", {})[1].should == :find end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is facts" do handler.uri2indirection("PUT", "/env/facts/bar", {})[1].should == :save end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is inventory" do handler.uri2indirection("GET", "/env/inventory/search", {})[1].should == :search end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is facts" do handler.uri2indirection("GET", "/env/facts/bar", {})[1].should == :find end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is facts" do handler.uri2indirection("PUT", "/env/facts/bar", {})[1].should == :save end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is inventory" do handler.uri2indirection("GET", "/env/inventory/search", {})[1].should == :search end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is facts_search" do handler.uri2indirection("GET", "/env/facts_search/bar", {})[1].should == :search end it "should change indirection name to 'facts' if the http method is a GET and the indirection name is facts_search" do handler.uri2indirection("GET", "/env/facts_search/bar", {})[0].should == 'facts' end it "should not change indirection name from 'facts' if the http method is a GET and the indirection name is facts" do handler.uri2indirection("GET", "/env/facts/bar", {})[0].should == 'facts' end it "should change indirection name to 'status' if the http method is a GET and the indirection name is statuses" do handler.uri2indirection("GET", "/env/statuses/bar", {})[0].should == 'status' end it "should change indirection name to 'probe' if the http method is a GET and the indirection name is probes" do handler.uri2indirection("GET", "/env/probes/bar", {})[0].should == 'probe' end it "should choose 'delete' as the indirection method if the http method is a DELETE and the indirection name is singular" do handler.uri2indirection("DELETE", "/env/foo/bar", {})[1].should == :destroy end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is singular" do handler.uri2indirection("PUT", "/env/foo/bar", {})[1].should == :save end it "should fail if an indirection method cannot be picked" do lambda { handler.uri2indirection("UPDATE", "/env/foo/bar", {}) }.should raise_error(ArgumentError) end it "should URI unescape the indirection key" do escaped = URI.escape("foo bar") indirection_name, method, key, params = handler.uri2indirection("GET", "/env/foo/#{escaped}", {}) key.should == "foo bar" end end describe "when converting a request into a URI" do let(:request) { Puppet::Indirector::Request.new(:foo, :find, "with spaces", nil, :foo => :bar, :environment => "myenv") } it "should use the environment as the first field of the URI" do handler.class.indirection2uri(request).split("/")[1].should == "myenv" end it "should use the indirection as the second field of the URI" do handler.class.indirection2uri(request).split("/")[2].should == "foo" end it "should pluralize the indirection name if the method is 'search'" do request.stubs(:method).returns :search handler.class.indirection2uri(request).split("/")[2].should == "foos" end it "should use the escaped key as the remainder of the URI" do escaped = URI.escape("with spaces") handler.class.indirection2uri(request).split("/")[3].sub(/\?.+/, '').should == escaped end it "should add the query string to the URI" do request.expects(:query_string).returns "?query" handler.class.indirection2uri(request).should =~ /\?query$/ end end describe "when converting a request into a URI with body" do let(:request) { Puppet::Indirector::Request.new(:foo, :find, "with spaces", nil, :foo => :bar, :environment => "myenv") } it "should use the environment as the first field of the URI" do handler.class.request_to_uri_and_body(request).first.split("/")[1].should == "myenv" end it "should use the indirection as the second field of the URI" do handler.class.request_to_uri_and_body(request).first.split("/")[2].should == "foo" end it "should use the escaped key as the remainder of the URI" do escaped = URI.escape("with spaces") handler.class.request_to_uri_and_body(request).first.split("/")[3].sub(/\?.+/, '').should == escaped end it "should return the URI and body separately" do handler.class.request_to_uri_and_body(request).should == ["/myenv/foo/with%20spaces", "foo=bar"] end end describe "when processing a request" do it "should return not_authorized_code if the request is not authorized" do request = a_request_that_heads(Puppet::IndirectorTesting.new("my data")) handler.expects(:check_authorization).raises(Puppet::Network::AuthorizationError.new("forbidden")) handler.call(request, response) expect(response.code).to eq(not_authorized_code) end - it "should return an error code if the indirection does not support remote requests" do + it "should return 'not found' if the indirection does not support remote requests" do request = a_request_that_heads(Puppet::IndirectorTesting.new("my data")) indirection.expects(:allow_remote_requests?).returns(false) handler.call(request, response) expect(response.code).to eq(not_found_code) end + it "should return 'not found' if the environment does not exist" do + Puppet.override(:environments => Puppet::Environments::Static.new()) do + request = a_request_that_heads(Puppet::IndirectorTesting.new("my data")) + + handler.call(request, response) + + expect(response.code).to eq(not_found_code) + end + end + it "should serialize a controller exception when an exception is thrown while finding the model instance" do request = a_request_that_finds(Puppet::IndirectorTesting.new("key")) handler.expects(:do_find).raises(ArgumentError, "The exception") handler.call(request, response) expect(response.code).to eq(400) expect(response.body).to eq("The exception") expect(response.type).to eq("text/plain") end end describe "when finding a model instance" do it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_finds(data, :accept_header => "unknown, pson, yaml") handler.call(request, response) expect(response.body).to eq(data.render(:pson)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) end it "responds with a not_acceptable_code error when no accept header is provided" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_finds(data, :accept_header => nil) handler.call(request, response) expect(response.code).to eq(not_acceptable_code) end it "raises an error when no accepted formats are known" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_finds(data, :accept_header => "unknown, also/unknown") handler.call(request, response) expect(response.code).to eq(not_acceptable_code) end it "should pass the result through without rendering it if the result is a string" do data = Puppet::IndirectorTesting.new("my data") data_string = "my data string" request = a_request_that_finds(data, :accept_header => "pson") indirection.expects(:find).returns(data_string) handler.call(request, response) expect(response.body).to eq(data_string) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) end it "should return a not_found_code when no model instance can be found" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_finds(data, :accept_header => "unknown, pson, yaml") handler.call(request, response) expect(response.code).to eq(not_found_code) end end describe "when searching for model instances" do it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_searches("my", :accept_header => "unknown, pson, yaml") handler.call(request, response) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) expect(response.body).to eq(Puppet::IndirectorTesting.render_multiple(:pson, [data])) end it "should return [] when searching returns an empty array" do request = a_request_that_searches("nothing", :accept_header => "unknown, pson, yaml") handler.call(request, response) expect(response.body).to eq("[]") expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) end it "should return a not_found_code when searching returns nil" do request = a_request_that_searches("nothing", :accept_header => "unknown, pson, yaml") indirection.expects(:search).returns(nil) handler.call(request, response) expect(response.code).to eq(not_found_code) end end describe "when destroying a model instance" do it "destroys the data indicated in the request" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_destroys(data) handler.call(request, response) Puppet::IndirectorTesting.indirection.find("my data").should be_nil end it "responds with yaml when no Accept header is given" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_destroys(data, :accept_header => nil) handler.call(request, response) expect(response.body).to eq(data.render(:yaml)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:yaml)) end it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_destroys(data, :accept_header => "unknown, pson, yaml") handler.call(request, response) expect(response.body).to eq(data.render(:pson)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) end it "raises an error and does not destroy when no accepted formats are known" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_submits(data, :accept_header => "unknown, also/unknown") handler.call(request, response) expect(response.code).to eq(not_acceptable_code) Puppet::IndirectorTesting.indirection.find("my data").should_not be_nil end end describe "when saving a model instance" do it "allows an empty body when the format supports it" do class Puppet::IndirectorTesting::Nonvalidatingmemory < Puppet::IndirectorTesting::Memory def validate_key(_) # nothing end end indirection.terminus_class = :nonvalidatingmemory data = Puppet::IndirectorTesting.new("test") request = a_request_that_submits(data, :content_type_header => "application/x-raw", :body => '') handler.call(request, response) Puppet::IndirectorTesting.indirection.find("test").name.should == '' end it "saves the data sent in the request" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data) handler.call(request, response) saved = Puppet::IndirectorTesting.indirection.find("my data") expect(saved.name).to eq(data.name) end it "responds with yaml when no Accept header is given" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :accept_header => nil) handler.call(request, response) expect(response.body).to eq(data.render(:yaml)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:yaml)) end it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :accept_header => "unknown, pson, yaml") handler.call(request, response) expect(response.body).to eq(data.render(:pson)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) end it "raises an error and does not save when no accepted formats are known" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :accept_header => "unknown, also/unknown") handler.call(request, response) expect(Puppet::IndirectorTesting.indirection.find("my data")).to be_nil expect(response.code).to eq(not_acceptable_code) end end describe "when performing head operation" do it "should not generate a response when a model head call succeeds" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_heads(data) handler.call(request, response) expect(response.code).to eq(nil) end it "should return a not_found_code when the model head call returns false" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_heads(data) handler.call(request, response) expect(response.code).to eq(not_found_code) expect(response.type).to eq("text/plain") expect(response.body).to eq("Not Found: Could not find indirector_testing my data") end end end diff --git a/spec/unit/network/http/api/v2/environments_spec.rb b/spec/unit/network/http/api/v2/environments_spec.rb index 6e97f905d..6c6d7a581 100644 --- a/spec/unit/network/http/api/v2/environments_spec.rb +++ b/spec/unit/network/http/api/v2/environments_spec.rb @@ -1,42 +1,42 @@ require 'spec_helper' require 'puppet/node/environment' require 'puppet/network/http' require 'matchers/json' describe Puppet::Network::HTTP::API::V2::Environments do include JSONMatchers it "responds with all of the available environments" do environment = Puppet::Node::Environment.create(:production, ["/first", "/second"], '/manifests') loader = Puppet::Environments::Static.new(environment) handler = Puppet::Network::HTTP::API::V2::Environments.new(loader) response = Puppet::Network::HTTP::MemoryResponse.new handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) expect(response.code).to eq(200) expect(response.type).to eq("application/json") expect(JSON.parse(response.body)).to eq({ "search_paths" => loader.search_paths, "environments" => { "production" => { "settings" => { "modulepath" => [File.expand_path("/first"), File.expand_path("/second")], "manifest" => File.expand_path("/manifests") } } } }) end it "the response conforms to the environments schema" do - environment = Puppet::Node::Environment.create(:production, [], '') + environment = Puppet::Node::Environment.create(:production, []) handler = Puppet::Network::HTTP::API::V2::Environments.new(Puppet::Environments::Static.new(environment)) response = Puppet::Network::HTTP::MemoryResponse.new handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) expect(response.body).to validate_against('api/schemas/environments.json') end end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 5d265e00b..b5728ac5a 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -1,466 +1,466 @@ #! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' require 'puppet/parser/parser_factory' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.new("testing") } include PuppetSpec::Files after do Puppet::Node::Environment.clear end shared_examples_for 'the environment' do it "should use the filetimeout for the ttl for the module list" do Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) end it "should use the default environment if no name is provided while initializing an environment" do Puppet[:environment] = "one" Puppet::Node::Environment.new.name.should == :one end it "should treat environment instances as singletons" do Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end it "should treat an environment specified as names or strings as equivalent" do Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end it "should return its name when converted to a string" do Puppet::Node::Environment.new(:one).to_s.should == "one" end it "should just return any provided environment if an environment is provided as the name" do one = Puppet::Node::Environment.new(:one) Puppet::Node::Environment.new(one).should equal(one) end describe "overriding an existing environment" do let(:original_path) { [tmpdir('original')] } let(:new_path) { [tmpdir('new')] } let(:environment) { Puppet::Node::Environment.create(:overridden, original_path, 'orig.pp') } it "overrides modulepath" do overridden = environment.override_with(:modulepath => new_path) expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(new_path) end it "overrides manifest" do overridden = environment.override_with(:manifest => 'new.pp') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('new.pp')) expect(overridden.modulepath).to eq(original_path) end end describe "watching a file" do let(:filename) { "filename" } it "accepts a File" do file = tmpfile(filename) env.known_resource_types.expects(:watch_file).with(file.to_s) env.watch_file(file) end it "accepts a String" do env.known_resource_types.expects(:watch_file).with(filename) env.watch_file(filename) end end describe "when managing known resource types" do before do @collection = Puppet::Resource::TypeCollection.new(env) env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) end it "should create a resource type collection if none exists" do Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection env.known_resource_types.should equal(@collection) end it "should reuse any existing resource type collection" do env.known_resource_types.should equal(env.known_resource_types) end it "should perform the initial import when creating a new collection" do env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) env.known_resource_types end it "should return the same collection even if stale if it's the same thread" do Puppet::Resource::TypeCollection.stubs(:new).returns @collection env.known_resource_types.stubs(:stale?).returns true env.known_resource_types.should equal(@collection) end it "should generate a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:require_reparse?).returns true env.check_for_reparse new_type_collection = env.known_resource_types new_type_collection.should be_a Puppet::Resource::TypeCollection new_type_collection.should_not equal(old_type_collection) end end it "should validate the modulepath directories" do real_file = tmpdir('moduledir') path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) Puppet[:modulepath] = path env.modulepath.should == [real_file] end it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do first_puppetlib = tmpdir('puppetlib1') second_puppetlib = tmpdir('puppetlib2') first_moduledir = tmpdir('moduledir1') second_moduledir = tmpdir('moduledir2') Puppet::Util.withenv("PUPPETLIB" => [first_puppetlib, second_puppetlib].join(File::PATH_SEPARATOR)) do Puppet[:modulepath] = [first_moduledir, second_moduledir].join(File::PATH_SEPARATOR) env.modulepath.should == [first_puppetlib, second_puppetlib, first_moduledir, second_moduledir] end end describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing end it "should provide an array-like accessor method for returning any environment-specific setting" do env.should respond_to(:[]) end it "should ask the Puppet settings instance for the setting qualified with the environment name" do Puppet.settings.parse_config(<<-CONF) [testing] server = myval CONF env[:server].should == "myval" end it "should be able to return an individual module that exists in its module path" do env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] mod = env.module('one') mod.should be_a(Puppet::Module) mod.name.should == 'one' end it "should not return a module if the module doesn't exist" do env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] env.module('two').should be_nil end it "should return nil if asked for a module that does not exist in its path" do modpath = tmpdir('modpath') - env = Puppet::Node::Environment.create(:testing, [modpath], '') + env = Puppet::Node::Environment.create(:testing, [modpath]) env.module("one").should be_nil end describe "module data" do before do dir = tmpdir("deep_path") @first = File.join(dir, "first") @second = File.join(dir, "second") Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}" FileUtils.mkdir_p(@first) FileUtils.mkdir_p(@second) end describe "#modules_by_path" do it "should return an empty list if there are no modules" do env.modules_by_path.should == { @first => [], @second => [] } end it "should include modules even if they exist in multiple dirs in the modulepath" do modpath1 = File.join(@first, "foo") FileUtils.mkdir_p(modpath1) modpath2 = File.join(@second, "foo") FileUtils.mkdir_p(modpath2) env.modules_by_path.should == { @first => [Puppet::Module.new('foo', modpath1, env)], @second => [Puppet::Module.new('foo', modpath2, env)] } end it "should ignore modules with invalid names" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@first, 'foo2')) FileUtils.mkdir_p(File.join(@first, 'foo-bar')) FileUtils.mkdir_p(File.join(@first, 'foo_bar')) FileUtils.mkdir_p(File.join(@first, 'foo=bar')) FileUtils.mkdir_p(File.join(@first, 'foo bar')) FileUtils.mkdir_p(File.join(@first, 'foo.bar')) FileUtils.mkdir_p(File.join(@first, '-foo')) FileUtils.mkdir_p(File.join(@first, 'foo-')) FileUtils.mkdir_p(File.join(@first, 'foo--bar')) env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end end describe "#module_requirements" do it "should return a list of what modules depend on other modules" do PuppetSpec::Modules.create( 'foo', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] } ) PuppetSpec::Modules.create( 'bar', @second, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] } ) PuppetSpec::Modules.create( 'baz', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }] } ) PuppetSpec::Modules.create( 'alpha', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) env.module_requirements.should == { 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ { "name" => "puppetlabs/bar", "version" => "9.9.9", "version_requirement" => "<= 2.0.0" } ], 'puppetlabs/bar' => [ { "name" => "puppetlabs/alpha", "version" => "9.9.9", "version_requirement" => "~3.0.0" }, { "name" => "puppetlabs/baz", "version" => "9.9.9", "version_requirement" => "3.0.0" }, { "name" => "puppetlabs/foo", "version" => "9.9.9", "version_requirement" => ">= 1.0.0" } ], 'puppetlabs/baz' => [] } end end describe ".module_by_forge_name" do it "should find modules by forge_name" do mod = PuppetSpec::Modules.create( 'baz', @first, :metadata => {:author => 'puppetlabs'}, :environment => env ) env.module_by_forge_name('puppetlabs/baz').should == mod end it "should not find modules with same name by the wrong author" do mod = PuppetSpec::Modules.create( 'baz', @first, :metadata => {:author => 'sneakylabs'}, :environment => env ) env.module_by_forge_name('puppetlabs/baz').should == nil end it "should return nil when the module can't be found" do env.module_by_forge_name('ima/nothere').should be_nil end end describe ".modules" do it "should return an empty list if there are no modules" do env.modules.should == [] end it "should return a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| FileUtils.mkdir_p(File.join(@first, mod_name)) end %w{bee baz}.each do |mod_name| FileUtils.mkdir_p(File.join(@second, mod_name)) end env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort end it "should remove duplicates" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@second, 'foo')) env.modules.collect{|mod| mod.name}.sort.should == %w{foo} end it "should ignore modules with invalid names" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@first, 'foo2')) FileUtils.mkdir_p(File.join(@first, 'foo-bar')) FileUtils.mkdir_p(File.join(@first, 'foo_bar')) FileUtils.mkdir_p(File.join(@first, 'foo=bar')) FileUtils.mkdir_p(File.join(@first, 'foo bar')) env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end it "should create modules with the correct environment" do FileUtils.mkdir_p(File.join(@first, 'foo')) env.modules.each {|mod| mod.environment.should == env } end end end end describe "when performing initial import" do def parser_and_environment(name) env = Puppet::Node::Environment.new(name) parser = Puppet::Parser::ParserFactory.parser(env) Puppet::Parser::ParserFactory.stubs(:parser).returns(parser) [parser, env] end it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet[:code] = "my code" parser, env = parser_and_environment('testing') parser.expects(:string=).with "my code" parser.expects(:parse) env.instance_eval { perform_initial_import } end it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do filename = tmpfile('myfile') Puppet[:manifest] = filename parser, env = parser_and_environment('testing') parser.expects(:file=).with filename parser.expects(:parse) env.instance_eval { perform_initial_import } end it "should pass the manifest file to the parser even if it does not exist on disk" do filename = tmpfile('myfile') Puppet[:code] = "" Puppet[:manifest] = filename parser, env = parser_and_environment('testing') parser.expects(:file=).with(filename).once parser.expects(:parse).once env.instance_eval { perform_initial_import } end it "should fail helpfully if there is an error importing" do Puppet::FileSystem.stubs(:exist?).returns true parser, env = parser_and_environment('testing') parser.expects(:file=).once parser.expects(:parse).raises ArgumentError expect do env.known_resource_types end.to raise_error(Puppet::Error) end it "should not do anything if the ignore_import settings is set" do Puppet[:ignoreimport] = true parser, env = parser_and_environment('testing') parser.expects(:string=).never parser.expects(:file=).never parser.expects(:parse).never env.instance_eval { perform_initial_import } end it "should mark the type collection as needing a reparse when there is an error parsing" do parser, env = parser_and_environment('testing') parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") expect do env.known_resource_types end.to raise_error(Puppet::Error, /Syntax error at .../) env.known_resource_types.require_reparse?.should be_true end end end describe 'with classic parser' do before :each do Puppet[:parser] = 'current' end it_behaves_like 'the environment' end describe 'with future parser' do before :each do Puppet[:parser] = 'future' end it_behaves_like 'the environment' end describe '#current' do it 'should return the current context' do env = Puppet::Node::Environment.new(:test) Puppet::Context.any_instance.expects(:lookup).with(:current_environment).returns(env) Puppet.expects(:deprecation_warning).once Puppet::Node::Environment.current.should equal(env) end end end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 11f24729e..85ec6e127 100755 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -1,323 +1,323 @@ #! /usr/bin/env ruby require 'spec_helper' require 'matchers/json' describe Puppet::Node do include JSONMatchers - let(:environment) { Puppet::Node::Environment.create(:bar, [], '') } + let(:environment) { Puppet::Node::Environment.create(:bar, []) } let(:env_loader) { Puppet::Environments::Static.new(environment) } it "should register its document type as Node" do PSON.registered_document_types["Node"].should equal(Puppet::Node) end describe "when managing its environment" do it "should use any set environment" do Puppet.override(:environments => env_loader) do Puppet::Node.new("foo", :environment => "bar").environment.should == environment end end it "should support providing an actual environment instance" do Puppet::Node.new("foo", :environment => environment).environment.name.should == :bar end it "should determine its environment from its parameters if no environment is set" do Puppet.override(:environments => env_loader) do Puppet::Node.new("foo", :parameters => {"environment" => :bar}).environment.should == environment end end it "should use the configured environment if no environment is provided" do Puppet[:environment] = environment.name.to_s Puppet.override(:environments => env_loader) do Puppet::Node.new("foo").environment.should == environment end end it "should allow the environment to be set after initialization" do node = Puppet::Node.new("foo") node.environment = :bar node.environment.name.should == :bar end it "should allow its environment to be set by parameters after initialization" do node = Puppet::Node.new("foo") node.parameters["environment"] = :bar node.environment.name.should == :bar end end it "can survive a round-trip through YAML" do facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'kjhgrg', :classes => ['erth', 'aiu'], :parameters => {"hostname"=>"food"} ) new_node = Puppet::Node.convert_from('yaml', node.render('yaml')) new_node.environment.should == node.environment new_node.parameters.should == node.parameters new_node.classes.should == node.classes new_node.name.should == node.name end it "can round-trip through pson" do facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'kjhgrg', :classes => ['erth', 'aiu'], :parameters => {"hostname"=>"food"} ) new_node = Puppet::Node.convert_from('pson', node.render('pson')) new_node.environment.should == node.environment new_node.parameters.should == node.parameters new_node.classes.should == node.classes new_node.name.should == node.name end it "validates against the node json schema", :unless => Puppet.features.microsoft_windows? do facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'kjhgrg', :classes => ['erth', 'aiu'], :parameters => {"hostname"=>"food"} ) expect(node.to_pson).to validate_against('api/schemas/node.json') end it "when missing optional parameters validates against the node json schema", :unless => Puppet.features.microsoft_windows? do facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'kjhgrg' ) expect(node.to_pson).to validate_against('api/schemas/node.json') end describe "when converting to json" do before do @node = Puppet::Node.new("mynode") end it "should provide its name" do @node.should set_json_attribute('name').to("mynode") end it "should produce a hash with the document_type set to 'Node'" do @node.should set_json_document_type_to("Node") end it "should include the classes if set" do @node.classes = %w{a b c} @node.should set_json_attribute("classes").to(%w{a b c}) end it "should not include the classes if there are none" do @node.should_not set_json_attribute('classes') end it "should include parameters if set" do @node.parameters = {"a" => "b", "c" => "d"} @node.should set_json_attribute('parameters').to({"a" => "b", "c" => "d"}) end it "should not include the parameters if there are none" do @node.should_not set_json_attribute('parameters') end it "should include the environment" do @node.environment = "production" @node.should set_json_attribute('environment').to('production') end end describe "when converting from json" do before do @node = Puppet::Node.new("mynode") @format = Puppet::Network::FormatHandler.format('pson') end def from_json(json) @format.intern(Puppet::Node, json) end it "should set its name" do Puppet::Node.should read_json_attribute('name').from(@node.to_pson).as("mynode") end it "should include the classes if set" do @node.classes = %w{a b c} Puppet::Node.should read_json_attribute('classes').from(@node.to_pson).as(%w{a b c}) end it "should include parameters if set" do @node.parameters = {"a" => "b", "c" => "d"} Puppet::Node.should read_json_attribute('parameters').from(@node.to_pson).as({"a" => "b", "c" => "d"}) end it "should include the environment" do Puppet.override(:environments => env_loader) do @node.environment = environment Puppet::Node.should read_json_attribute('environment').from(@node.to_pson).as(environment) end end end end describe Puppet::Node, "when initializing" do before do @node = Puppet::Node.new("testnode") end it "should set the node name" do @node.name.should == "testnode" end it "should not allow nil node names" do proc { Puppet::Node.new(nil) }.should raise_error(ArgumentError) end it "should default to an empty parameter hash" do @node.parameters.should == {} end it "should default to an empty class array" do @node.classes.should == [] end it "should note its creation time" do @node.time.should be_instance_of(Time) end it "should accept parameters passed in during initialization" do params = {"a" => "b"} @node = Puppet::Node.new("testing", :parameters => params) @node.parameters.should == params end it "should accept classes passed in during initialization" do classes = %w{one two} @node = Puppet::Node.new("testing", :classes => classes) @node.classes.should == classes end it "should always return classes as an array" do @node = Puppet::Node.new("testing", :classes => "myclass") @node.classes.should == ["myclass"] end end describe Puppet::Node, "when merging facts" do before do @node = Puppet::Node.new("testnode") Puppet::Node::Facts.indirection.stubs(:find).with(@node.name, instance_of(Hash)).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should fail intelligently if it cannot find facts" do Puppet::Node::Facts.indirection.expects(:find).with(@node.name, instance_of(Hash)).raises "foo" lambda { @node.fact_merge }.should raise_error(Puppet::Error) end it "should prefer parameters already set on the node over facts from the node" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.fact_merge @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.fact_merge @node.parameters["two"].should == "b" end it "should accept arbitrary parameters to merge into its parameters" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.merge "two" => "three" @node.parameters["two"].should == "three" end it "should add the environment to the list of parameters" do Puppet[:environment] = "one" @node = Puppet::Node.new("testnode", :environment => "one") @node.merge "two" => "three" @node.parameters["environment"].should == "one" end it "should not set the environment if it is already set in the parameters" do Puppet[:environment] = "one" @node = Puppet::Node.new("testnode", :environment => "one") @node.merge "environment" => "two" @node.parameters["environment"].should == "two" end end describe Puppet::Node, "when indirecting" do it "should default to the 'plain' node terminus" do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.terminus_class.should == :plain end end describe Puppet::Node, "when generating the list of names to search through" do before do @node = Puppet::Node.new("foo.domain.com", :parameters => {"hostname" => "yay", "domain" => "domain.com"}) end it "should return an array of names" do @node.names.should be_instance_of(Array) end describe "and the node name is fully qualified" do it "should contain an entry for each part of the node name" do @node.names.should be_include("foo.domain.com") @node.names.should be_include("foo.domain") @node.names.should be_include("foo") end end it "should include the node's fqdn" do @node.names.should be_include("yay.domain.com") end it "should combine and include the node's hostname and domain if no fqdn is available" do @node.names.should be_include("yay.domain.com") end it "should contain an entry for each name available by stripping a segment of the fqdn" do @node.parameters["fqdn"] = "foo.deep.sub.domain.com" @node.names.should be_include("foo.deep.sub.domain") @node.names.should be_include("foo.deep.sub") end describe "and :node_name is set to 'cert'" do before do Puppet[:strict_hostname_checking] = false Puppet[:node_name] = "cert" end it "should use the passed-in key as the first value" do @node.names[0].should == "foo.domain.com" end describe "and strict hostname checking is enabled" do it "should only use the passed-in key" do Puppet[:strict_hostname_checking] = true @node.names.should == ["foo.domain.com"] end end end describe "and :node_name is set to 'facter'" do before do Puppet[:strict_hostname_checking] = false Puppet[:node_name] = "facter" end it "should use the node's 'hostname' fact as the first value" do @node.names[0].should == "yay" end end end diff --git a/spec/unit/parser/ast/collection_spec.rb b/spec/unit/parser/ast/collection_spec.rb index a5e40b2c3..5b2f7c14d 100755 --- a/spec/unit/parser/ast/collection_spec.rb +++ b/spec/unit/parser/ast/collection_spec.rb @@ -1,70 +1,70 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::AST::Collection do before :each do @mytype = Puppet::Resource::Type.new(:definition, "mytype") - @environment = Puppet::Node::Environment.create(:testing, [], '') + @environment = Puppet::Node::Environment.create(:testing, []) @environment.known_resource_types.add @mytype @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foonode", :environment => @environment)) @scope = Puppet::Parser::Scope.new(@compiler) @overrides = stub_everything 'overrides' @overrides.stubs(:is_a?).with(Puppet::Parser::AST).returns(true) end it "should evaluate its query" do query = mock 'query' collection = Puppet::Parser::AST::Collection.new :query => query, :form => :virtual collection.type = 'mytype' query.expects(:safeevaluate).with(@scope) collection.evaluate(@scope) end it "should instantiate a Collector for this type" do collection = Puppet::Parser::AST::Collection.new :form => :virtual, :type => "test" @test_type = Puppet::Resource::Type.new(:definition, "test") @environment.known_resource_types.add @test_type Puppet::Parser::Collector.expects(:new).with(@scope, "test", nil, nil, :virtual) collection.evaluate(@scope) end it "should tell the compiler about this collector" do collection = Puppet::Parser::AST::Collection.new :form => :virtual, :type => "mytype" Puppet::Parser::Collector.stubs(:new).returns("whatever") @compiler.expects(:add_collection).with("whatever") collection.evaluate(@scope) end it "should evaluate overriden paramaters" do collector = stub_everything 'collector' collection = Puppet::Parser::AST::Collection.new :form => :virtual, :type => "mytype", :override => @overrides Puppet::Parser::Collector.stubs(:new).returns(collector) @overrides.expects(:safeevaluate).with(@scope) collection.evaluate(@scope) end it "should tell the collector about overrides" do collector = mock 'collector' collection = Puppet::Parser::AST::Collection.new :form => :virtual, :type => "mytype", :override => @overrides Puppet::Parser::Collector.stubs(:new).returns(collector) collector.expects(:add_override) collection.evaluate(@scope) end it "should fail when evaluating undefined resource types" do collection = Puppet::Parser::AST::Collection.new :form => :virtual, :type => "bogus" lambda { collection.evaluate(@scope) }.should raise_error "Resource type bogus doesn't exist" end end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 8a6621520..ab1e40cf9 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -1,903 +1,903 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title def initialize(type, title) @type = type @title = title end def [](attr) return nil if attr == :stage :main end def ref "#{type.to_s.capitalize}[#{title}]" end def evaluated? @evaluated end def builtin_type? @builtin end def virtual? @virtual end def class? false end def stage? false end def evaluate end def file "/fake/file/goes/here" end def line "42" end end describe Puppet::Parser::Compiler do include PuppetSpec::Files def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) end before :each do # Push me faster, I wanna go back in time! (Specifically, freeze time # across the test since we have a bunch of version == timestamp code # hidden away in the implementation and we keep losing the race.) # --daniel 2011-04-21 now = Time.now Time.stubs(:now).returns(now) - environment = Puppet::Node::Environment.create(:testing, [], '') + environment = Puppet::Node::Environment.create(:testing, []) @node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler, :source => stub('source')) @scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope) @scope.resource = @scope_resource end it "should have a class method that compiles, converts, and returns a catalog" do compiler = stub 'compiler' Puppet::Parser::Compiler.expects(:new).with(@node).returns compiler catalog = stub 'catalog' compiler.expects(:compile).returns catalog converted_catalog = stub 'converted_catalog' catalog.expects(:to_resource).returns converted_catalog Puppet::Parser::Compiler.compile(@node).should equal(converted_catalog) end it "should fail intelligently when a class-level compile fails" do Puppet::Parser::Compiler.expects(:new).raises ArgumentError lambda { Puppet::Parser::Compiler.compile(@node) }.should raise_error(Puppet::Error) end it "should use the node's environment as its environment" do @compiler.environment.should equal(@node.environment) end it "should include the resource type collection helper" do Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end it "should be able to return a class list containing all added classes" do @compiler.add_class "" @compiler.add_class "one" @compiler.add_class "two" @compiler.classlist.sort.should == %w{one two}.sort end it "should clear the global caches before compile" do compiler = stub 'compiler' Puppet::Parser::Compiler.expects(:new).with(@node).returns compiler catalog = stub 'catalog' compiler.expects(:compile).returns catalog catalog.expects(:to_resource) $known_resource_types = "rspec" $env_module_directories = "rspec" Puppet::Parser::Compiler.compile(@node) $known_resource_types = nil $env_module_directories = nil end describe "when initializing" do it "should set its node attribute" do @compiler.node.should equal(@node) end it "should detect when ast nodes are absent" do @compiler.ast_nodes?.should be_false end it "should detect when ast nodes are present" do @known_resource_types.expects(:nodes?).returns true @compiler.ast_nodes?.should be_true end it "should copy the known_resource_types version to the catalog" do @compiler.catalog.version.should == @known_resource_types.version end it "should copy any node classes into the class list" do node = Puppet::Node.new("mynode") node.classes = %w{foo bar} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should transform node class hashes into a class list" do node = Puppet::Node.new("mynode") node.classes = {'foo'=>{'one'=>'p1'}, 'bar'=>{'two'=>'p2'}} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should add a 'main' stage to the catalog" do @compiler.catalog.resource(:stage, :main).should be_instance_of(Puppet::Parser::Resource) end end describe "when managing scopes" do it "should create a top scope" do @compiler.topscope.should be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do @compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope) end it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' newscope = @compiler.newscope(scope) newscope.parent.should equal(scope) end it "should set the parent scope of the new scope to its topscope if the parent passed in is nil" do scope = mock 'scope' newscope = @compiler.newscope(nil) newscope.parent.should equal(@compiler.topscope) end end describe "when compiling" do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract, :evaluate_relationships] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compiler.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.topscope['a'].should == "b" @compiler.topscope['c'].should == "d" end it "should set the client and server versions on the catalog" do params = {"clientversion" => "2", "serverversion" => "3"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.catalog.client_version.should == "2" @compiler.catalog.server_version.should == "3" end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "") main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } @compiler.topscope.expects(:source=).with(main_class) @compiler.compile end it "should create a new, empty 'main' if no main class exists" do compile_stub(:evaluate_main) @compiler.compile @known_resource_types.find_hostclass([""], "").should be_instance_of(Puppet::Resource::Type) end it "should add an edge between the main stage and main class" do @compiler.compile (stage = @compiler.catalog.resource(:stage, "main")).should be_instance_of(Puppet::Parser::Resource) (klass = @compiler.catalog.resource(:class, "")).should be_instance_of(Puppet::Parser::Resource) @compiler.catalog.edge?(stage, klass).should be_true end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } @compiler.add_collection(colls[0]) @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) @compiler.compile end it "should ignore builtin resources" do resource = resource(:file, "testing") @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true } @compiler.compile end it "should not evaluate already-evaluated resources" do resource = resource(:file, "testing") resource.stubs(:evaluated?).returns true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) resource2 = CompilerTestResource.new(:file, "other") # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true; @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.evaluated = true } @compiler.compile end describe "when finishing" do before do @compiler.send(:evaluate_main) @catalog = @compiler.catalog end def add_resource(name, parent = nil) resource = Puppet::Parser::Resource.new "file", name, :scope => @scope @compiler.add_resource(@scope, resource) @catalog.add_edge(parent, resource) if parent resource end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope resource.expects(:finish) @compiler.add_resource(@scope, resource) # And one that does not dnf_resource = stub_everything "dnf", :ref => "File[dnf]", :type => "file" @compiler.add_resource(@scope, dnf_resource) @compiler.send(:finish) end it "should call finish() in add_resource order" do resources = sequence('resources') resource1 = add_resource("finish1") resource1.expects(:finish).in_sequence(resources) resource2 = add_resource("finish2") resource2.expects(:finish).in_sequence(resources) @compiler.send(:finish) end it "should add each container's metaparams to its contained resources" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) @compiler.send(:finish) resource1[:noop].should be_true end it "should add metaparams recursively" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2[:noop].should be_true end it "should prefer metaparams from immediate parents" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) resource1[:noop] = false @compiler.send(:finish) resource2[:noop].should be_false end it "should merge tags downward" do main = @catalog.resource(:class, :main) main.tag("one") resource1 = add_resource("meh", main) resource1.tag "two" resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2.tags.should be_include("one") resource2.tags.should be_include("two") end it "should work if only middle resources have metaparams set" do main = @catalog.resource(:class, :main) resource1 = add_resource("meh", main) resource1[:noop] = true resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2[:noop].should be_true end end it "should return added resources in add order" do resource1 = resource(:file, "yay") @compiler.add_resource(@scope, resource1) resource2 = resource(:file, "youpi") @compiler.add_resource(@scope, resource2) @compiler.resources.should == [resource1, resource2] end it "should add resources that do not conflict with existing resources" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do path = make_absolute("/foo") file1 = resource(:file, path) file2 = resource(:file, path) @compiler.add_resource(@scope, file1) lambda { @compiler.add_resource(@scope, file2) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should add an edge from the scope resource to the added resource" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_edge(@scope.resource, resource) end it "should not add non-class resources that don't specify a stage to the 'main' stage" do main = @compiler.catalog.resource(:stage, :main) resource = resource(:file, "foo") @compiler.add_resource(@scope, resource) @compiler.catalog.should_not be_edge(main, resource) end it "should not add any parent-edges to stages" do stage = resource(:stage, "other") @compiler.add_resource(@scope, stage) @scope.resource = resource(:class, "foo") @compiler.catalog.edge?(@scope.resource, stage).should be_false end it "should not attempt to add stages to other stages" do other_stage = resource(:stage, "other") second_stage = resource(:stage, "second") @compiler.add_resource(@scope, other_stage) @compiler.add_resource(@scope, second_stage) second_stage[:stage] = "other" @compiler.catalog.edge?(other_stage, second_stage).should be_false end it "should have a method for looking up resources" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay[foo]").should equal(resource) end it "should be able to look resources up by type and title" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay", "foo").should equal(resource) end it "should not evaluate virtual defined resources" do resource = resource(:file, "testing") resource.virtual = true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end end describe "when evaluating collections" do it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do @compiler.delete_collection(coll) end } @compiler.compile end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(nil) @compiler.add_collection(coll) lambda { @compiler.compile }.should_not raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(:something) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Failed to realize virtual resources something' end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns([:one, :two]) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Failed to realize virtual resources one, two' end end describe "when evaluating relationships" do it "should evaluate each relationship with its catalog" do dep = stub 'dep' dep.expects(:evaluate).with(@compiler.catalog) @compiler.add_relationship dep @compiler.evaluate_relationships end end describe "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end it "should raise an error if a class is not found" do @scope.expects(:find_hostclass).with("notfound", {:assume_fqname => false}).returns(nil) lambda{ @compiler.evaluate_classes(%w{notfound}, @scope) }.should raise_error(Puppet::Error, /Could not find class/) end it "should raise an error when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses @compiler.topscope.expects(:find_hostclass).with('foo', {:assume_fqname => false}).returns(nil) lambda{ @compiler.compile }.should raise_error(Puppet::Error, /Could not find class foo for testnode/) end end describe "when evaluating found classes" do before do Puppet.settings[:data_binding_terminus] = "none" @class = stub 'class', :name => "my::class" @scope.stubs(:find_hostclass).with("myclass", {:assume_fqname => false}).returns(@class) @resource = stub 'resource', :ref => "Class[myclass]", :type => "file" end it "should evaluate each class" do @compiler.catalog.stubs(:tag) @class.expects(:ensure_in_catalog).with(@scope) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end describe "and the classes are specified as a hash with parameters" do before do @node.classes = {} @ast_obj = Puppet::Parser::AST::String.new(:value => 'foo') end # Define the given class with default parameters def define_class(name, parameters) @node.classes[name] = parameters klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => @ast_obj, 'p2' => @ast_obj}) @compiler.topscope.known_resource_types.add klass end def compile @catalog = @compiler.compile end it "should record which classes are evaluated" do classes = {'foo'=>{}, 'bar::foo'=>{}, 'bar'=>{}} classes.each { |c, params| define_class(c, params) } compile() classes.each { |name, p| @catalog.classes.should include(name) } end it "should provide default values for parameters that have no values specified" do define_class('foo', {}) compile() @catalog.resource(:class, 'foo')['p1'].should == "foo" end it "should use any provided values" do define_class('foo', {'p1' => 'real_value'}) compile() @catalog.resource(:class, 'foo')['p1'].should == "real_value" end it "should support providing some but not all values" do define_class('foo', {'p1' => 'real_value'}) compile() @catalog.resource(:class, 'Foo')['p1'].should == "real_value" @catalog.resource(:class, 'Foo')['p2'].should == "foo" end it "should ensure each node class is in catalog and has appropriate tags" do klasses = ['bar::foo'] @node.classes = klasses ast_obj = Puppet::Parser::AST::String.new(:value => 'foo') klasses.each do |name| klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => ast_obj, 'p2' => ast_obj}) @compiler.topscope.known_resource_types.add klass end catalog = @compiler.compile r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } r2.tags.should == Puppet::Util::TagSet.new(['bar::foo', 'class', 'bar', 'foo']) end end it "should fail if required parameters are missing" do klass = {'foo'=>{'a'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'a' => nil, 'b' => nil}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass b to Class[Foo]") end it "should fail if invalid parameters are passed" do klass = {'foo'=>{'3'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Invalid parameter 3") end it "should ensure class is in catalog without params" do @node.classes = klasses = {'foo'=>nil} foo = Puppet::Resource::Type.new(:hostclass, 'foo') @compiler.topscope.known_resource_types.add foo catalog = @compiler.compile catalog.classes.should include 'foo' end it "should not evaluate the resources created for found classes unless asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) @scope.stubs(:class_scope).with(@class).returns("something") @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes previously evaluated with different capitalization" do @compiler.catalog.stubs(:tag) @scope.stubs(:find_hostclass).with("MyClass",{:assume_fqname => false}).returns(@class) @scope.stubs(:class_scope).with(@class).returns("something") @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{MyClass}, @scope, false) end end describe "when evaluating AST nodes with no AST nodes present" do it "should do nothing" do @compiler.expects(:ast_nodes?).returns(false) @compiler.known_resource_types.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never @compiler.send(:evaluate_ast_node) end end describe "when evaluating AST nodes with AST nodes present" do before do @compiler.known_resource_types.stubs(:nodes?).returns true # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @compiler.known_resource_types.stubs(:node).with("a").returns(nil) @compiler.known_resource_types.stubs(:node).with("b").returns(nil) @compiler.known_resource_types.stubs(:node).with("c").returns(nil) # It should check this last, of course. @compiler.known_resource_types.stubs(:node).with("default").returns(nil) end it "should fail if the named node cannot be found" do proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :name => "c", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :name => "default", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :name => "c" @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) node_resource.expects(:evaluate) @compiler.send(:evaluate_ast_node) end end describe "when evaluating node classes" do include PuppetSpec::Compiler describe "when provided classes in array format" do let(:node) { Puppet::Node.new('someone', :classes => ['something']) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end it "should evaluate the class without parameters if it's not already included" do manifest = "class something {}" catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "when provided classes in hash format" do describe "for classes without parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {}}) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something {} MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "for classes with parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {'configuron' => 'defrabulated'}}) } describe "when the class exists" do it "should fail if the class is already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} include something MANIFEST expect { compile_to_catalog(manifest, node) }.to raise_error(Puppet::Error, /Class\[Something\] is already declared/) end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} MANIFEST catalog = compile_to_catalog(manifest, node) resource = catalog.resource('Class', 'Something') resource['configuron'].should == 'defrabulated' end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end end end describe "when managing resource overrides" do before do @override = stub 'override', :ref => "File[/foo]", :type => "my" @resource = resource(:file, "/foo") end it "should be able to store overrides" do lambda { @compiler.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) @compiler.add_override(@override) @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override @compiler.add_override(@override) # Then the resource @compiler.add_resource(@scope, @resource) # And compile, so they get resolved @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do @compiler.add_override(@override) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Could not find resource(s) File[/foo] for overriding' end end end diff --git a/spec/unit/parser/files_spec.rb b/spec/unit/parser/files_spec.rb index 02ef3d5cf..3eb43b276 100755 --- a/spec/unit/parser/files_spec.rb +++ b/spec/unit/parser/files_spec.rb @@ -1,149 +1,149 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/files' describe Puppet::Parser::Files do include PuppetSpec::Files - let(:environment) { Puppet::Node::Environment.create(:testing, [], '') } + let(:environment) { Puppet::Node::Environment.create(:testing, []) } before do @basepath = make_absolute("/somepath") end describe "when searching for templates" do it "should return fully-qualified templates directly" do Puppet::Parser::Files.expects(:modulepath).never Puppet::Parser::Files.find_template(@basepath + "/my/template", environment).should == @basepath + "/my/template" end it "should return the template from the first found module" do mod = mock 'module' mod.expects(:template).returns("/one/mymod/templates/mytemplate") environment.expects(:module).with("mymod").returns mod Puppet::Parser::Files.find_template("mymod/mytemplate", environment).should == "/one/mymod/templates/mytemplate" end it "should return the file in the templatedir if it exists" do Puppet[:templatedir] = "/my/templates" Puppet[:modulepath] = "/one:/two" File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(true) Puppet::Parser::Files.find_template("mymod/mytemplate", environment).should == File.join(Puppet[:templatedir], "mymod/mytemplate") end it "should not raise an error if no valid templatedir exists and the template exists in a module" do mod = mock 'module' mod.expects(:template).returns("/one/mymod/templates/mytemplate") environment.expects(:module).with("mymod").returns mod Puppet::Parser::Files.stubs(:templatepath).with(environment).returns(nil) Puppet::Parser::Files.find_template("mymod/mytemplate", environment).should == "/one/mymod/templates/mytemplate" end it "should return unqualified templates if they exist in the template dir" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(environment).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate", environment).should == "/my/templates/mytemplate" end it "should only return templates if they actually exist" do Puppet::FileSystem.expects(:exist?).with("/my/templates/mytemplate").returns true Puppet::Parser::Files.stubs(:templatepath).with(environment).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate", environment).should == "/my/templates/mytemplate" end it "should return nil when asked for a template that doesn't exist" do Puppet::FileSystem.expects(:exist?).with("/my/templates/mytemplate").returns false Puppet::Parser::Files.stubs(:templatepath).with(environment).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate", environment).should be_nil end it "should accept relative templatedirs" do Puppet::FileSystem.stubs(:exist?).returns true Puppet[:templatedir] = "my/templates" File.expects(:directory?).with(File.expand_path("my/templates")).returns(true) Puppet::Parser::Files.find_template("mytemplate", environment).should == File.expand_path("my/templates/mytemplate") end it "should use the environment templatedir if no module is found and an environment is specified" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(environment).returns(["/myenv/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", environment).should == "/myenv/templates/mymod/mytemplate" end it "should use first dir from environment templatedir if no module is found and an environment is specified" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(environment).returns(["/myenv/templates", "/two/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", environment).should == "/myenv/templates/mymod/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and the first dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) Puppet::FileSystem.expects(:exist?).with("/one/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate", environment).should == "/one/templates/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and only second dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) Puppet::FileSystem.expects(:exist?).with("/one/templates/mytemplate").returns(false) Puppet::FileSystem.expects(:exist?).with("/two/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate", environment).should == "/two/templates/mytemplate" end it "should use the node environment if specified" do mod = mock 'module' environment.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/my/modules/mymod/templates/envtemplate") Puppet::Parser::Files.find_template("mymod/envtemplate", environment).should == "/my/modules/mymod/templates/envtemplate" end it "should return nil if no template can be found" do Puppet::Parser::Files.find_template("foomod/envtemplate", environment).should be_nil end end describe "when searching for manifests" do it "should ignore invalid modules" do mod = mock 'module' environment.expects(:module).with("mymod").raises(Puppet::Module::InvalidName, "name is invalid") Puppet.expects(:value).with(:modulepath).never Dir.stubs(:glob).returns %w{foo} Puppet::Parser::Files.find_manifests_in_modules("mymod/init.pp", environment) end end describe "when searching for manifests in a module" do def a_module_in_environment(env, name) mod = Puppet::Module.new(name, "/one/#{name}", env) env.stubs(:module).with(name).returns mod mod.stubs(:match_manifests).with("init.pp").returns(["/one/#{name}/manifests/init.pp"]) end it "returns no files when no module is found" do module_name, files = Puppet::Parser::Files.find_manifests_in_modules("not_here_module/foo", environment) expect(files).to be_empty expect(module_name).to be_nil end it "should return the name of the module and the manifests from the first found module" do a_module_in_environment(environment, "mymod") Puppet::Parser::Files.find_manifests_in_modules("mymod/init.pp", environment).should == ["mymod", ["/one/mymod/manifests/init.pp"]] end it "does not find the module when it is a different environment" do - different_env = Puppet::Node::Environment.create(:different, [], '') + different_env = Puppet::Node::Environment.create(:different, []) a_module_in_environment(environment, "mymod") Puppet::Parser::Files.find_manifests_in_modules("mymod/init.pp", different_env).should_not include("mymod") end end end diff --git a/spec/unit/parser/functions_spec.rb b/spec/unit/parser/functions_spec.rb index 91013586c..b3c04d1af 100755 --- a/spec/unit/parser/functions_spec.rb +++ b/spec/unit/parser/functions_spec.rb @@ -1,135 +1,135 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::Functions do def callable_functions_from(mod) Class.new { include mod }.new end let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) } - let(:environment) { Puppet::Node::Environment.create(:myenv, [], '') } + let(:environment) { Puppet::Node::Environment.create(:myenv, []) } before do Puppet::Parser::Functions.reset end it "should have a method for returning an environment-specific module" do Puppet::Parser::Functions.environment_module(environment).should be_instance_of(Module) end describe "when calling newfunction" do it "should create the function in the environment module" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } function_module.should be_method_defined :function_name end it "should warn if the function already exists" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } Puppet.expects(:warning) Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } end it "should raise an error if the function type is not correct" do expect { Puppet::Parser::Functions.newfunction("name", :type => :unknown) { |args| } }.to raise_error Puppet::DevError, "Invalid statement type :unknown" end it "instruments the function to profile the execution" do messages = [] Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id") Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } callable_functions_from(function_module).function_name([]) messages.first.should =~ /Called name/ end end describe "when calling function to test function existence" do it "should return false if the function doesn't exist" do Puppet::Parser::Functions.autoloader.stubs(:load) Puppet::Parser::Functions.function("name").should be_false end it "should return its name if the function exists" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } Puppet::Parser::Functions.function("name").should == "function_name" end it "should try to autoload the function if it doesn't exist yet" do Puppet::Parser::Functions.autoloader.expects(:load) Puppet::Parser::Functions.function("name") end it "combines functions from the root with those from the current environment" do Puppet.override(:current_environment => Puppet.lookup(:root_environment)) do Puppet::Parser::Functions.newfunction("onlyroot", :type => :rvalue) do |args| end end - Puppet.override(:current_environment => Puppet::Node::Environment.create(:other, [], '')) do + Puppet.override(:current_environment => Puppet::Node::Environment.create(:other, [])) do Puppet::Parser::Functions.newfunction("other_env", :type => :rvalue) do |args| end expect(Puppet::Parser::Functions.function("onlyroot")).to eq("function_onlyroot") expect(Puppet::Parser::Functions.function("other_env")).to eq("function_other_env") end expect(Puppet::Parser::Functions.function("other_env")).to be_false end end describe "when calling function to test arity" do let(:function_module) { Module.new } before do Puppet::Parser::Functions.stubs(:environment_module).returns(function_module) end it "should raise an error if the function is called with too many arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1,2,3]) }.to raise_error ArgumentError end it "should raise an error if the function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1]) }.to raise_error ArgumentError end it "should not raise an error if the function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1,2]) }.to_not raise_error end it "should raise an error if the variable arg function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1]) }.to raise_error ArgumentError end it "should not raise an error if the variable arg function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1,2]) }.to_not raise_error end it "should not raise an error if the variable arg function is called with more number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1,2,3]) }.to_not raise_error end end describe "::arity" do it "returns the given arity of a function" do Puppet::Parser::Functions.newfunction("name", :arity => 4) { |args| } Puppet::Parser::Functions.arity(:name).should == 4 end it "returns -1 if no arity is given" do Puppet::Parser::Functions.newfunction("name") { |args| } Puppet::Parser::Functions.arity(:name).should == -1 end end end diff --git a/spec/unit/parser/parser_spec.rb b/spec/unit/parser/parser_spec.rb index 56062d47e..eb9370292 100755 --- a/spec/unit/parser/parser_spec.rb +++ b/spec/unit/parser/parser_spec.rb @@ -1,535 +1,535 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser do Puppet::Parser::AST before :each do @known_resource_types = Puppet::Resource::TypeCollection.new("development") @parser = Puppet::Parser::Parser.new "development" @parser.stubs(:known_resource_types).returns @known_resource_types @true_ast = Puppet::Parser::AST::Boolean.new :value => true end it "should require an environment at initialization" do expect { Puppet::Parser::Parser.new }.to raise_error(ArgumentError, /wrong number of arguments/) end it "should set the environment" do - env = Puppet::Node::Environment.create(:testing, [], '') + env = Puppet::Node::Environment.create(:testing, []) Puppet::Parser::Parser.new(env).environment.should == env end it "should be able to look up the environment-specific resource type collection" do - env = Puppet::Node::Environment.create(:development, [], '') + env = Puppet::Node::Environment.create(:development, []) rtc = env.known_resource_types parser = Puppet::Parser::Parser.new env parser.known_resource_types.should equal(rtc) end context "when importing" do it "uses the directory of the currently parsed file" do @parser.lexer.stubs(:file).returns "/tmp/current_file" @parser.known_resource_types.loader.expects(:import).with("newfile", "/tmp") @parser.import("newfile") end it "uses the current working directory, when there is no file being parsed" do @parser.known_resource_types.loader.expects(:import).with('one', Dir.pwd) @parser.known_resource_types.loader.expects(:import).with('two', Dir.pwd) @parser.parse("import 'one', 'two'") end end describe "when parsing files" do before do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:read).returns "" @parser.stubs(:watch_file) end it "should treat files ending in 'rb' as ruby files" do @parser.expects(:parse_ruby_file) @parser.file = "/my/file.rb" @parser.parse end end describe "when parsing append operator" do it "should not raise syntax errors" do expect { @parser.parse("$var += something") }.to_not raise_error end it "should raise syntax error on incomplete syntax " do expect { @parser.parse("$var += ") }.to raise_error(Puppet::ParseError, /Syntax error at end of file/) end it "should create ast::VarDef with append=true" do vardef = @parser.parse("$var += 2").code[0] vardef.should be_a(Puppet::Parser::AST::VarDef) vardef.append.should == true end it "should work with arrays too" do vardef = @parser.parse("$var += ['test']").code[0] vardef.should be_a(Puppet::Parser::AST::VarDef) vardef.append.should == true end end describe "when parsing selector" do it "should support hash access on the left hand side" do expect { @parser.parse("$h = { 'a' => 'b' } $a = $h['a'] ? { 'b' => 'd', default => undef }") }.to_not raise_error end end describe "parsing 'unless'" do it "should create the correct ast objects" do Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } @parser.parse("unless false { $var = 1 }") end it "should not raise an error with empty statements" do expect { @parser.parse("unless false { }") }.to_not raise_error end #test for bug #13296 it "should not override 'unless' as a parameter inside resources" do lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error end end describe "when parsing parameter names" do Puppet::Parser::Lexer::KEYWORDS.sort_tokens.each do |keyword| it "should allow #{keyword} as a keyword" do lambda { @parser.parse("exec {'/bin/echo foo': #{keyword} => '/usr/bin/false',}") }.should_not raise_error end end end describe "when parsing 'if'" do it "not, it should create the correct ast objects" do Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } @parser.parse("if ! true { $var = 1 }") end it "boolean operation, it should create the correct ast objects" do Puppet::Parser::AST::BooleanOperator.expects(:new).with { |h| h[:rval].is_a?(Puppet::Parser::AST::Boolean) and h[:lval].is_a?(Puppet::Parser::AST::Boolean) and h[:operator]=="or" } @parser.parse("if true or true { $var = 1 }") end it "comparison operation, it should create the correct ast objects" do Puppet::Parser::AST::ComparisonOperator.expects(:new).with { |h| h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="<" } @parser.parse("if 1 < 2 { $var = 1 }") end end describe "when parsing if complex expressions" do it "should create a correct ast tree" do aststub = stub_everything 'ast' Puppet::Parser::AST::ComparisonOperator.expects(:new).with { |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]==">" }.returns(aststub) Puppet::Parser::AST::ComparisonOperator.expects(:new).with { |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="==" }.returns(aststub) Puppet::Parser::AST::BooleanOperator.expects(:new).with { |h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and" } @parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }") end it "should raise an error on incorrect expression" do expect { @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }") }.to raise_error(Puppet::ParseError, /Syntax error at '\)'/) end end describe "when parsing resource references" do it "should not raise syntax errors" do expect { @parser.parse('exec { test: param => File["a"] }') }.to_not raise_error end it "should not raise syntax errors with multiple references" do expect { @parser.parse('exec { test: param => File["a","b"] }') }.to_not raise_error end it "should create an ast::ResourceReference" do Puppet::Parser::AST::ResourceReference.expects(:new).with { |arg| arg[:line]==1 and arg[:type]=="File" and arg[:title].is_a?(Puppet::Parser::AST::ASTArray) } @parser.parse('exec { test: command => File["a","b"] }') end end describe "when parsing resource overrides" do it "should not raise syntax errors" do expect { @parser.parse('Resource["title"] { param => value }') }.to_not raise_error end it "should not raise syntax errors with multiple overrides" do expect { @parser.parse('Resource["title1","title2"] { param => value }') }.to_not raise_error end it "should create an ast::ResourceOverride" do #Puppet::Parser::AST::ResourceOverride.expects(:new).with { |arg| # arg[:line]==1 and arg[:object].is_a?(Puppet::Parser::AST::ResourceReference) and arg[:parameters].is_a?(Puppet::Parser::AST::ResourceParam) #} ro = @parser.parse('Resource["title1","title2"] { param => value }').code[0] ro.should be_a(Puppet::Parser::AST::ResourceOverride) ro.line.should == 1 ro.object.should be_a(Puppet::Parser::AST::ResourceReference) ro.parameters[0].should be_a(Puppet::Parser::AST::ResourceParam) end end describe "when parsing if statements" do it "should not raise errors with empty if" do expect { @parser.parse("if true { }") }.to_not raise_error end it "should not raise errors with empty else" do expect { @parser.parse("if false { notice('if') } else { }") }.to_not raise_error end it "should not raise errors with empty if and else" do expect { @parser.parse("if false { } else { }") }.to_not raise_error end it "should create a nop node for empty branch" do Puppet::Parser::AST::Nop.expects(:new) @parser.parse("if true { }") end it "should create a nop node for empty else branch" do Puppet::Parser::AST::Nop.expects(:new) @parser.parse("if true { notice('test') } else { }") end it "should build a chain of 'ifs' if there's an 'elsif'" do expect { @parser.parse(<<-PP) }.to_not raise_error if true { notice('test') } elsif true {} else { } PP end end describe "when parsing function calls" do it "should not raise errors with no arguments" do expect { @parser.parse("tag()") }.to_not raise_error end it "should not raise errors with rvalue function with no args" do expect { @parser.parse("$a = template()") }.to_not raise_error end it "should not raise errors with arguments" do expect { @parser.parse("notice(1)") }.to_not raise_error end it "should not raise errors with multiple arguments" do expect { @parser.parse("notice(1,2)") }.to_not raise_error end it "should not raise errors with multiple arguments and a trailing comma" do expect { @parser.parse("notice(1,2,)") }.to_not raise_error end end describe "when parsing arrays" do it "should parse an array" do expect { @parser.parse("$a = [1,2]") }.to_not raise_error end it "should not raise errors with a trailing comma" do expect { @parser.parse("$a = [1,2,]") }.to_not raise_error end it "should accept an empty array" do expect { @parser.parse("$var = []\n") }.to_not raise_error end end describe "when providing AST context" do before do @lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev" @parser.stubs(:lexer).returns @lexer end it "should include the lexer's line" do @parser.ast_context[:line].should == 50 end it "should include the lexer's file" do @parser.ast_context[:file].should == "/foo/bar" end it "should include the docs if directed to do so" do @parser.ast_context(true)[:doc].should == "whev" end it "should not include the docs when told not to" do @parser.ast_context(false)[:doc].should be_nil end it "should not include the docs by default" do @parser.ast_context[:doc].should be_nil end end describe "when building ast nodes" do before do @lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev" @parser.stubs(:lexer).returns @lexer @class = Puppet::Resource::Type.new(:hostclass, "myclass", :use_docs => false) end it "should return a new instance of the provided class created with the provided options" do @class.expects(:new).with { |opts| opts[:foo] == "bar" } @parser.ast(@class, :foo => "bar") end it "should merge the ast context into the provided options" do @class.expects(:new).with { |opts| opts[:file] == "/foo" } @parser.expects(:ast_context).returns :file => "/foo" @parser.ast(@class, :foo => "bar") end it "should prefer provided options over AST context" do @class.expects(:new).with { |opts| opts[:file] == "/bar" } @lexer.expects(:file).returns "/foo" @parser.ast(@class, :file => "/bar") end it "should include docs when the AST class uses them" do @class.expects(:use_docs).returns true @class.stubs(:new) @parser.expects(:ast_context).with{ |docs, line| docs == true }.returns({}) @parser.ast(@class, :file => "/bar") end it "should get docs from lexer using the correct AST line number" do @class.expects(:use_docs).returns true @class.stubs(:new).with{ |a| a[:doc] == "doc" } @lexer.expects(:getcomment).with(12).returns "doc" @parser.ast(@class, :file => "/bar", :line => 12) end end describe "when retrieving a specific node" do it "should delegate to the known_resource_types node" do @known_resource_types.expects(:node).with("node") @parser.node("node") end end describe "when retrieving a specific class" do it "should delegate to the loaded code" do @known_resource_types.expects(:hostclass).with("class") @parser.hostclass("class") end end describe "when retrieving a specific definitions" do it "should delegate to the loaded code" do @known_resource_types.expects(:definition).with("define") @parser.definition("define") end end describe "when determining the configuration version" do it "should determine it from the resource type collection" do @parser.known_resource_types.expects(:version).returns "foo" @parser.version.should == "foo" end end describe "when looking up definitions" do it "should use the known resource types to check for them by name" do @parser.known_resource_types.stubs(:find_or_load).with("namespace","name",:definition).returns(:this_value) @parser.find_definition("namespace","name").should == :this_value end end describe "when looking up hostclasses" do it "should use the known resource types to check for them by name" do @parser.known_resource_types.stubs(:find_or_load).with("namespace","name",:hostclass,{}).returns(:this_value) @parser.find_hostclass("namespace","name").should == :this_value end end describe "when parsing classes" do before :each do @krt = Puppet::Resource::TypeCollection.new("development") @parser = Puppet::Parser::Parser.new "development" @parser.stubs(:known_resource_types).returns @krt end it "should not create new classes" do @parser.parse("class foobar {}").code[0].should be_a(Puppet::Parser::AST::Hostclass) @krt.hostclass("foobar").should be_nil end it "should correctly set the parent class when one is provided" do @parser.parse("class foobar inherits yayness {}").code[0].instantiate('')[0].parent.should == "yayness" end it "should correctly set the parent class for multiple classes at a time" do statements = @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}").code statements[0].instantiate('')[0].parent.should == "yayness" statements[1].instantiate('')[0].parent.should == "bar" end it "should define the code when some is provided" do @parser.parse("class foobar { $var = val }").code[0].code.should_not be_nil end it "should accept parameters with trailing comma" do @parser.parse("file { '/example': ensure => file, }").should be end it "should accept parametrized classes with trailing comma" do @parser.parse("class foobar ($var1 = 0,) { $var = val }").code[0].code.should_not be_nil end it "should define parameters when provided" do foobar = @parser.parse("class foobar($biz,$baz) {}").code[0].instantiate('')[0] foobar.arguments.should == {"biz" => nil, "baz" => nil} end end describe "when parsing resources" do before :each do @krt = Puppet::Resource::TypeCollection.new("development") @parser = Puppet::Parser::Parser.new "development" @parser.stubs(:known_resource_types).returns @krt end it "should be able to parse class resources" do @krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil})) expect { @parser.parse("class { foobar: biz => stuff }") }.to_not raise_error end it "should correctly mark exported resources as exported" do @parser.parse("@@file { '/file': }").code[0].exported.should be_true end it "should correctly mark virtual resources as virtual" do @parser.parse("@file { '/file': }").code[0].virtual.should be_true end end describe "when parsing nodes" do it "should be able to parse a node with a single name" do node = @parser.parse("node foo { }").code[0] node.should be_a Puppet::Parser::AST::Node node.names.length.should == 1 node.names[0].value.should == "foo" end it "should be able to parse a node with two names" do node = @parser.parse("node foo, bar { }").code[0] node.should be_a Puppet::Parser::AST::Node node.names.length.should == 2 node.names[0].value.should == "foo" node.names[1].value.should == "bar" end it "should be able to parse a node with three names" do node = @parser.parse("node foo, bar, baz { }").code[0] node.should be_a Puppet::Parser::AST::Node node.names.length.should == 3 node.names[0].value.should == "foo" node.names[1].value.should == "bar" node.names[2].value.should == "baz" end end it "should fail if trying to collect defaults" do expect { @parser.parse("@Port { protocols => tcp }") }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/) end context "when parsing collections" do it "should parse basic collections" do @parser.parse("Port <| |>").code. should be_all {|x| x.is_a? Puppet::Parser::AST::Collection } end it "should parse fully qualified collections" do @parser.parse("Port::Range <| |>").code. should be_all {|x| x.is_a? Puppet::Parser::AST::Collection } end end it "should not assign to a fully qualified variable" do expect { @parser.parse("$one::two = yay") }.to raise_error(Puppet::ParseError, /Cannot assign to variables in other namespaces/) end it "should parse assignment of undef" do tree = @parser.parse("$var = undef") tree.code.children[0].should be_an_instance_of Puppet::Parser::AST::VarDef tree.code.children[0].value.should be_an_instance_of Puppet::Parser::AST::Undef end context "#namesplit" do { "base::sub" => %w{base sub}, "main" => ["", "main"], "one::two::three::four" => ["one::two::three", "four"], }.each do |input, output| it "should split #{input.inspect} to #{output.inspect}" do @parser.namesplit(input).should == output end end end it "should treat classes as case insensitive" do @parser.known_resource_types.import_ast(@parser.parse("class yayness {}"), '') @parser.known_resource_types.hostclass('yayness'). should == @parser.find_hostclass("", "YayNess") end it "should treat defines as case insensitive" do @parser.known_resource_types.import_ast(@parser.parse("define funtest {}"), '') @parser.known_resource_types.hostclass('funtest'). should == @parser.find_hostclass("", "fUntEst") end context "deprecations" do it "should flag use of import as deprecated" do Puppet.expects(:deprecation_warning).once @parser.known_resource_types.loader.expects(:import).with('foo', Dir.pwd) @parser.parse("import 'foo'") end end end diff --git a/spec/unit/parser/resource_spec.rb b/spec/unit/parser/resource_spec.rb index f78f83982..d0ec5f49c 100755 --- a/spec/unit/parser/resource_spec.rb +++ b/spec/unit/parser/resource_spec.rb @@ -1,586 +1,586 @@ require 'spec_helper' describe Puppet::Parser::Resource do before do - environment = Puppet::Node::Environment.create(:testing, [], '') + environment = Puppet::Node::Environment.create(:testing, []) @node = Puppet::Node.new("yaynode", :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @source = newclass "" @scope = @compiler.topscope end def mkresource(args = {}) args[:source] ||= @source args[:scope] ||= @scope params = args[:parameters] || {:one => "yay", :three => "rah"} if args[:parameters] == :none args.delete(:parameters) elsif not args[:parameters].is_a? Array args[:parameters] = paramify(args[:source], params) end Puppet::Parser::Resource.new("resource", "testing", args) end def param(name, value, source) Puppet::Parser::Resource::Param.new(:name => name, :value => value, :source => source) end def paramify(source, hash) hash.collect do |name, value| Puppet::Parser::Resource::Param.new( :name => name, :value => value, :source => source ) end end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def newdefine(name) @known_resource_types.add Puppet::Resource::Type.new(:definition, name) end def newnode(name) @known_resource_types.add Puppet::Resource::Type.new(:node, name) end it "should get its environment from its scope" do scope = stub 'scope', :source => stub("source"), :namespaces => nil scope.expects(:environment).returns("foo").at_least_once Puppet::Parser::Resource.new("file", "whatever", :scope => scope).environment.should == "foo" end it "should use the resource type collection helper module" do Puppet::Parser::Resource.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end it "should use the scope's environment as its environment" do @scope.expects(:environment).returns("myenv").at_least_once Puppet::Parser::Resource.new("file", "whatever", :scope => @scope).environment.should == "myenv" end it "should be isomorphic if it is builtin and models an isomorphic type" do Puppet::Type.type(:file).expects(:isomorphic?).returns(true) @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true end it "should not be isomorphic if it is builtin and models a non-isomorphic type" do Puppet::Type.type(:file).expects(:isomorphic?).returns(false) @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_false end it "should be isomorphic if it is not builtin" do newdefine "whatever" @resource = Puppet::Parser::Resource.new("whatever", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true end it "should have an array-indexing method for retrieving parameter values" do @resource = mkresource @resource[:one].should == "yay" end it "should use a Puppet::Resource for converting to a ral resource" do trans = mock 'resource', :to_ral => "yay" @resource = mkresource @resource.expects(:copy_as_resource).returns trans @resource.to_ral.should == "yay" end it "should be able to use the indexing operator to access parameters" do resource = Puppet::Parser::Resource.new("resource", "testing", :source => "source", :scope => @scope) resource["foo"] = "bar" resource["foo"].should == "bar" end it "should return the title when asked for a parameter named 'title'" do Puppet::Parser::Resource.new("resource", "testing", :source => @source, :scope => @scope)[:title].should == "testing" end describe "when initializing" do before do @arguments = {:scope => @scope} end it "should fail unless #{name.to_s} is specified" do expect { Puppet::Parser::Resource.new('file', '/my/file') }.to raise_error(ArgumentError, /Resources require a hash as last argument/) end it "should set the reference correctly" do res = Puppet::Parser::Resource.new("resource", "testing", @arguments) res.ref.should == "Resource[testing]" end it "should be tagged with user tags" do tags = [ "tag1", "tag2" ] @arguments[:parameters] = [ param(:tag, tags , :source) ] res = Puppet::Parser::Resource.new("resource", "testing", @arguments) res.should be_tagged("tag1") res.should be_tagged("tag2") end end describe "when evaluating" do before do @catalog = Puppet::Resource::Catalog.new source = stub('source') source.stubs(:module_name) @scope = Puppet::Parser::Scope.new(@compiler, :source => source) @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @scope)) end it "should evaluate the associated AST definition" do definition = newdefine "mydefine" res = Puppet::Parser::Resource.new("mydefine", "whatever", :scope => @scope, :source => @source, :catalog => @catalog) definition.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST class" do @class = newclass "myclass" res = Puppet::Parser::Resource.new("class", "myclass", :scope => @scope, :source => @source, :catalog => @catalog) @class.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST node" do nodedef = newnode("mynode") res = Puppet::Parser::Resource.new("node", "mynode", :scope => @scope, :source => @source, :catalog => @catalog) nodedef.expects(:evaluate_code).with(res) res.evaluate end it "should add an edge to any specified stage for class resources" do @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) other_stage = Puppet::Parser::Resource.new(:stage, "other", :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, other_stage) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'other' @compiler.add_resource(@scope, resource) resource.evaluate @compiler.catalog.edge?(other_stage, resource).should be_true end it "should fail if an unknown stage is specified" do @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'other' expect { resource.evaluate }.to raise_error(ArgumentError, /Could not find stage other specified by/) end it "should add edges from the class resources to the parent's stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) foo_stage = Puppet::Parser::Resource.new(:stage, :foo_stage, :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, foo_stage) @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'foo_stage' @compiler.add_resource(@scope, resource) resource.evaluate @compiler.catalog.should be_edge(foo_stage, resource) end it "should allow edges to propagate multiple levels down the scope hierarchy" do Puppet[:code] = <<-MANIFEST stage { before: before => Stage[main] } class alpha { include beta } class beta { include gamma } class gamma { } class { alpha: stage => before } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') # Stringify them to make for easier lookup edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} edges.should include(["Stage[before]", "Class[Alpha]"]) edges.should include(["Stage[before]", "Class[Beta]"]) edges.should include(["Stage[before]", "Class[Gamma]"]) end it "should use the specified stage even if the parent scope specifies one" do Puppet[:code] = <<-MANIFEST stage { before: before => Stage[main], } stage { after: require => Stage[main], } class alpha { class { beta: stage => after } } class beta { } class { alpha: stage => before } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} edges.should include(["Stage[before]", "Class[Alpha]"]) edges.should include(["Stage[after]", "Class[Beta]"]) end it "should add edges from top-level class resources to the main stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, resource) resource.evaluate @compiler.catalog.should be_edge(main, resource) end end describe "when finishing" do before do @class = newclass "myclass" @nodedef = newnode("mynode") @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source) end it "should do nothing if it has already been finished" do @resource.finish @resource.expects(:add_defaults).never @resource.finish end it "should add all defaults available from the scope" do @resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => param(:owner, "default", @resource.source)) @resource.finish @resource[:owner].should == "default" end it "should not replace existing parameters with defaults" do @resource.set_parameter :owner, "oldvalue" @resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => :replaced) @resource.finish @resource[:owner].should == "oldvalue" end it "should add a copy of each default, rather than the actual default parameter instance" do newparam = param(:owner, "default", @resource.source) other = newparam.dup other.value = "other" newparam.expects(:dup).returns(other) @resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => newparam) @resource.finish @resource[:owner].should == "other" end end describe "when being tagged" do before do @scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} @scope.stubs(:resource).returns @scope_resource @resource = Puppet::Parser::Resource.new("file", "yay", :scope => @scope, :source => mock('source')) end it "should get tagged with the resource type" do @resource.tags.should be_include("file") end it "should get tagged with the title" do @resource.tags.should be_include("yay") end it "should get tagged with each name in the title if the title is a qualified class name" do resource = Puppet::Parser::Resource.new("file", "one::two", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should get tagged with each name in the type if the type is a qualified class name" do resource = Puppet::Parser::Resource.new("one::two", "whatever", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should not get tagged with non-alphanumeric titles" do resource = Puppet::Parser::Resource.new("file", "this is a test", :scope => @scope, :source => mock('source')) resource.tags.should_not be_include("this is a test") end it "should fail on tags containing '*' characters" do expect { @resource.tag("bad*tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do expect { @resource.tag("-badtag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do expect { @resource.tag("bad tag") }.to raise_error(Puppet::ParseError) end it "should allow alpha tags" do expect { @resource.tag("good_tag") }.to_not raise_error end end describe "when merging overrides" do before do @source = "source1" @resource = mkresource :source => @source @override = mkresource :source => @source end it "should fail when the override was not created by a parent class" do @override.source = "source2" @override.source.expects(:child_of?).with("source1").returns(false) expect { @resource.merge(@override) }.to raise_error(Puppet::ParseError) end it "should succeed when the override was created in the current scope" do @resource.source = "source3" @override.source = @resource.source @override.source.expects(:child_of?).with("source3").never params = {:a => :b, :c => :d} @override.expects(:parameters).returns(params) @resource.expects(:override_parameter).with(:b) @resource.expects(:override_parameter).with(:d) @resource.merge(@override) end it "should succeed when a parent class created the override" do @resource.source = "source3" @override.source = "source4" @override.source.expects(:child_of?).with("source3").returns(true) params = {:a => :b, :c => :d} @override.expects(:parameters).returns(params) @resource.expects(:override_parameter).with(:b) @resource.expects(:override_parameter).with(:d) @resource.merge(@override) end it "should add new parameters when the parameter is not set" do @source.stubs(:child_of?).returns true @override.set_parameter(:testing, "value") @resource.merge(@override) @resource[:testing].should == "value" end it "should replace existing parameter values" do @source.stubs(:child_of?).returns true @resource.set_parameter(:testing, "old") @override.set_parameter(:testing, "value") @resource.merge(@override) @resource[:testing].should == "value" end it "should add values to the parameter when the override was created with the '+>' syntax" do @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "testing", :source => @resource.source) param.add = true @override.set_parameter(param) @resource.set_parameter(:testing, "other") @resource.merge(@override) @resource[:testing].should == %w{other testing} end it "should not merge parameter values when multiple resources are overriden with '+>' at once " do @resource_2 = mkresource :source => @source @resource. set_parameter(:testing, "old_val_1") @resource_2.set_parameter(:testing, "old_val_2") @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "new_val", :source => @resource.source) param.add = true @override.set_parameter(param) @resource. merge(@override) @resource_2.merge(@override) @resource [:testing].should == %w{old_val_1 new_val} @resource_2[:testing].should == %w{old_val_2 new_val} end it "should promote tag overrides to real tags" do @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :tag, :value => "testing", :source => @resource.source) @override.set_parameter(param) @resource.merge(@override) @resource.tagged?("testing").should be_true end end it "should be able to be converted to a normal resource" do @source = stub 'scope', :name => "myscope" @resource = mkresource :source => @source @resource.should respond_to(:copy_as_resource) end describe "when being converted to a resource" do before do @parser_resource = mkresource :scope => @scope, :parameters => {:foo => "bar", :fee => "fum"} end it "should create an instance of Puppet::Resource" do @parser_resource.copy_as_resource.should be_instance_of(Puppet::Resource) end it "should set the type correctly on the Puppet::Resource" do @parser_resource.copy_as_resource.type.should == @parser_resource.type end it "should set the title correctly on the Puppet::Resource" do @parser_resource.copy_as_resource.title.should == @parser_resource.title end it "should copy over all of the parameters" do result = @parser_resource.copy_as_resource.to_hash # The name will be in here, also. result[:foo].should == "bar" result[:fee].should == "fum" end it "should copy over the tags" do @parser_resource.tag "foo" @parser_resource.tag "bar" @parser_resource.copy_as_resource.tags.should == @parser_resource.tags end it "should copy over the line" do @parser_resource.line = 40 @parser_resource.copy_as_resource.line.should == 40 end it "should copy over the file" do @parser_resource.file = "/my/file" @parser_resource.copy_as_resource.file.should == "/my/file" end it "should copy over the 'exported' value" do @parser_resource.exported = true @parser_resource.copy_as_resource.exported.should be_true end it "should copy over the 'virtual' value" do @parser_resource.virtual = true @parser_resource.copy_as_resource.virtual.should be_true end it "should convert any parser resource references to Puppet::Resource instances" do ref = Puppet::Resource.new("file", "/my/file") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ref} result = @parser_resource.copy_as_resource result[:fee].should == Puppet::Resource.new(:file, "/my/file") end it "should convert any parser resource references to Puppet::Resource instances even if they are in an array" do ref = Puppet::Resource.new("file", "/my/file") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ["a", ref]} result = @parser_resource.copy_as_resource result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file")] end it "should convert any parser resource references to Puppet::Resource instances even if they are in an array of array, and even deeper" do ref1 = Puppet::Resource.new("file", "/my/file1") ref2 = Puppet::Resource.new("file", "/my/file2") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ["a", [ref1,ref2]]} result = @parser_resource.copy_as_resource result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file1"), Puppet::Resource.new(:file, "/my/file2")] end it "should fail if the same param is declared twice" do lambda do @parser_resource = mkresource :source => @source, :parameters => [ Puppet::Parser::Resource::Param.new( :name => :foo, :value => "bar", :source => @source ), Puppet::Parser::Resource::Param.new( :name => :foo, :value => "baz", :source => @source ) ] end.should raise_error(Puppet::ParseError) end end describe "when validating" do it "should check each parameter" do resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => stub("source") resource[:one] = :two resource[:three] = :four resource.expects(:validate_parameter).with(:one) resource.expects(:validate_parameter).with(:three) resource.send(:validate) end it "should raise a parse error when there's a failure" do resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => stub("source") resource[:one] = :two resource.expects(:validate_parameter).with(:one).raises ArgumentError expect { resource.send(:validate) }.to raise_error(Puppet::ParseError) end end describe "when setting parameters" do before do @source = newclass "foobar" @resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => @source end it "should accept Param instances and add them to the parameter list" do param = Puppet::Parser::Resource::Param.new :name => "foo", :value => "bar", :source => @source @resource.set_parameter(param) @resource["foo"].should == "bar" end it "should fail when provided a parameter name but no value" do expect { @resource.set_parameter("myparam") }.to raise_error(ArgumentError) end it "should allow parameters to be set to 'false'" do @resource.set_parameter("myparam", false) @resource["myparam"].should be_false end it "should use its source when provided a parameter name and value" do @resource.set_parameter("myparam", "myvalue") @resource["myparam"].should == "myvalue" end end # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. it "should not include 'undef' parameters when converting itself to a hash" do resource = Puppet::Parser::Resource.new "file", "/tmp/testing", :source => mock("source"), :scope => mock("scope") resource[:owner] = :undef resource[:mode] = "755" resource.to_hash[:owner].should be_nil end end diff --git a/spec/unit/parser/scope_spec.rb b/spec/unit/parser/scope_spec.rb index b9b10618f..f14faf831 100755 --- a/spec/unit/parser/scope_spec.rb +++ b/spec/unit/parser/scope_spec.rb @@ -1,661 +1,661 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/scope' describe Puppet::Parser::Scope do include PuppetSpec::Scope before :each do @scope = Puppet::Parser::Scope.new( Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) ) @scope.source = Puppet::Resource::Type.new(:node, :foo) @topscope = @scope.compiler.topscope @scope.parent = @topscope end describe "create_test_scope_for_node" do let(:node_name) { "node_name_foo" } let(:scope) { create_test_scope_for_node(node_name) } it "should be a kind of Scope" do scope.should be_a_kind_of(Puppet::Parser::Scope) end it "should set the source to a node resource" do scope.source.should be_a_kind_of(Puppet::Resource::Type) end it "should have a compiler" do scope.compiler.should be_a_kind_of(Puppet::Parser::Compiler) end it "should set the parent to the compiler topscope" do scope.parent.should be(scope.compiler.topscope) end end it "should return a scope for use in a test harness" do create_test_scope_for_node("node_name_foo").should be_a_kind_of(Puppet::Parser::Scope) end it "should be able to retrieve class scopes by name" do @scope.class_set "myname", "myscope" @scope.class_scope("myname").should == "myscope" end it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' klass.expects(:name).returns("myname") @scope.class_set "myname", "myscope" @scope.class_scope(klass).should == "myscope" end it "should be able to retrieve its parent module name from the source of its parent type" do @topscope.source = Puppet::Resource::Type.new(:hostclass, :foo, :module_name => "foo") @scope.parent_module_name.should == "foo" end it "should return a nil parent module name if it has no parent" do @topscope.parent_module_name.should be_nil end it "should return a nil parent module name if its parent has no source" do @scope.parent_module_name.should be_nil end it "should get its environment from its compiler" do - env = Puppet::Node::Environment.create(:testing, [], '') + env = Puppet::Node::Environment.create(:testing, []) compiler = stub 'compiler', :environment => env, :is_a? => true scope = Puppet::Parser::Scope.new(compiler) scope.environment.should equal(env) end it "should fail if no compiler is supplied" do expect { Puppet::Parser::Scope.new }.to raise_error(ArgumentError, /wrong number of arguments/) end it "should fail if something that isn't a compiler is supplied" do expect { Puppet::Parser::Scope.new(:compiler => true) }.to raise_error(Puppet::DevError, /you must pass a compiler instance/) end it "should use the resource type collection helper to find its known resource types" do Puppet::Parser::Scope.ancestors.should include(Puppet::Resource::TypeCollectionHelper) end describe "when custom functions are called" do - let(:env) { Puppet::Node::Environment.create(:testing, [], '') } + let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new('foo', :environment => env)) } let(:scope) { Puppet::Parser::Scope.new(compiler) } it "calls methods prefixed with function_ as custom functions" do scope.function_sprintf(["%b", 123]).should == "1111011" end it "raises an error when arguments are not passed in an Array" do expect do scope.function_sprintf("%b", 123) end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end it "raises an error on subsequent calls when arguments are not passed in an Array" do scope.function_sprintf(["first call"]) expect do scope.function_sprintf("%b", 123) end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end it "raises NoMethodError when the not prefixed" do expect { scope.sprintf(["%b", 123]) }.to raise_error(NoMethodError) end it "raises NoMethodError when prefixed with function_ but it doesn't exist" do expect { scope.function_fake_bs(['cows']) }.to raise_error(NoMethodError) end end describe "when initializing" do it "should extend itself with its environment's Functions module as well as the default" do - env = Puppet::Node::Environment.create(:myenv, [], '') + env = Puppet::Node::Environment.create(:myenv, []) root = Puppet.lookup(:root_environment) compiler = stub 'compiler', :environment => env, :is_a? => true scope = Puppet::Parser::Scope.new(compiler) scope.singleton_class.ancestors.should be_include(Puppet::Parser::Functions.environment_module(env)) scope.singleton_class.ancestors.should be_include(Puppet::Parser::Functions.environment_module(root)) end it "should extend itself with the default Functions module if its environment is the default" do root = Puppet.lookup(:root_environment) node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) scope = Puppet::Parser::Scope.new(compiler) scope.singleton_class.ancestors.should be_include(Puppet::Parser::Functions.environment_module(root)) end end describe "when looking up a variable" do it "should support :lookupvar and :setvar for backward compatibility" do @scope.setvar("var", "yep") @scope.lookupvar("var").should == "yep" end it "should fail if invoked with a non-string name" do expect { @scope[:foo] }.to raise_error(Puppet::ParseError, /Scope variable name .* not a string/) expect { @scope[:foo] = 12 }.to raise_error(Puppet::ParseError, /Scope variable name .* not a string/) end it "should return nil for unset variables" do @scope["var"].should be_nil end it "should be able to look up values" do @scope["var"] = "yep" @scope["var"].should == "yep" end it "should be able to look up hashes" do @scope["var"] = {"a" => "b"} @scope["var"].should == {"a" => "b"} end it "should be able to look up variables in parent scopes" do @topscope["var"] = "parentval" @scope["var"].should == "parentval" end it "should prefer its own values to parent values" do @topscope["var"] = "parentval" @scope["var"] = "childval" @scope["var"].should == "childval" end it "should be able to detect when variables are set" do @scope["var"] = "childval" @scope.should be_include("var") end it "does not allow changing a set value" do @scope["var"] = "childval" expect { @scope["var"] = "change" }.to raise_error(Puppet::Error, "Cannot reassign variable var") end it "should be able to detect when variables are not set" do @scope.should_not be_include("var") end describe "and the variable is qualified" do before :each do @known_resource_types = @scope.known_resource_types node = Puppet::Node.new('localhost') @compiler = Puppet::Parser::Compiler.new(node) end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def create_class_scope(name) klass = newclass(name) catalog = Puppet::Resource::Catalog.new catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => Puppet::Parser::Scope.new(@compiler))) Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source'), :catalog => catalog).evaluate @scope.class_scope(klass) end it "should be able to look up explicitly fully qualified variables from main" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("") other_scope["othervar"] = "otherval" @scope["::othervar"].should == "otherval" end it "should be able to look up explicitly fully qualified variables from other scopes" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("other") other_scope["var"] = "otherval" @scope["::other::var"].should == "otherval" end it "should be able to look up deeply qualified variables" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("other::deep::klass") other_scope["var"] = "otherval" @scope["other::deep::klass::var"].should == "otherval" end it "should return nil for qualified variables that cannot be found in other classes" do other_scope = create_class_scope("other::deep::klass") @scope["other::deep::klass::var"].should be_nil end it "should warn and return nil for qualified variables whose classes have not been evaluated" do klass = newclass("other::deep::klass") @scope.expects(:warning) @scope["other::deep::klass::var"].should be_nil end it "should warn and return nil for qualified variables whose classes do not exist" do @scope.expects(:warning) @scope["other::deep::klass::var"].should be_nil end it "should return nil when asked for a non-string qualified variable from a class that does not exist" do @scope.stubs(:warning) @scope["other::deep::klass::var"].should be_nil end it "should return nil when asked for a non-string qualified variable from a class that has not been evaluated" do @scope.stubs(:warning) klass = newclass("other::deep::klass") @scope["other::deep::klass::var"].should be_nil end end context "and strict_variables is true" do before(:each) do Puppet[:strict_variables] = true end it "should raise an error when unknown variable is looked up" do expect { @scope['john_doe'] }.to raise_error(/Undefined variable/) end it "should raise an error when unknown qualified variable is looked up" do expect { @scope['nowhere::john_doe'] }.to raise_error(/Undefined variable/) end end end describe "when variables are set with append=true" do it "should raise error if the variable is already defined in this scope" do @scope.setvar("var", "1", :append => false) expect { @scope.setvar("var", "1", :append => true) }.to raise_error( Puppet::ParseError, "Cannot append, variable var is defined in this scope" ) end it "should lookup current variable value" do @scope.expects(:[]).with("var").returns("2") @scope.setvar("var", "1", :append => true) end it "should store the concatenated string '42'" do @topscope.setvar("var", "4", :append => false) @scope.setvar("var", "2", :append => true) @scope["var"].should == "42" end it "should store the concatenated array [4,2]" do @topscope.setvar("var", [4], :append => false) @scope.setvar("var", [2], :append => true) @scope["var"].should == [4,2] end it "should store the merged hash {a => b, c => d}" do @topscope.setvar("var", {"a" => "b"}, :append => false) @scope.setvar("var", {"c" => "d"}, :append => true) @scope["var"].should == {"a" => "b", "c" => "d"} end it "should raise an error when appending a hash with something other than another hash" do @topscope.setvar("var", {"a" => "b"}, :append => false) expect { @scope.setvar("var", "not a hash", :append => true) }.to raise_error( ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" ) end end describe "when calling number?" do it "should return nil if called with anything not a number" do Puppet::Parser::Scope.number?([2]).should be_nil end it "should return a Fixnum for a Fixnum" do Puppet::Parser::Scope.number?(2).should be_an_instance_of(Fixnum) end it "should return a Float for a Float" do Puppet::Parser::Scope.number?(2.34).should be_an_instance_of(Float) end it "should return 234 for '234'" do Puppet::Parser::Scope.number?("234").should == 234 end it "should return nil for 'not a number'" do Puppet::Parser::Scope.number?("not a number").should be_nil end it "should return 23.4 for '23.4'" do Puppet::Parser::Scope.number?("23.4").should == 23.4 end it "should return 23.4e13 for '23.4e13'" do Puppet::Parser::Scope.number?("23.4e13").should == 23.4e13 end it "should understand negative numbers" do Puppet::Parser::Scope.number?("-234").should == -234 end it "should know how to convert exponential float numbers ala '23e13'" do Puppet::Parser::Scope.number?("23e13").should == 23e13 end it "should understand hexadecimal numbers" do Puppet::Parser::Scope.number?("0x234").should == 0x234 end it "should understand octal numbers" do Puppet::Parser::Scope.number?("0755").should == 0755 end it "should return nil on malformed integers" do Puppet::Parser::Scope.number?("0.24.5").should be_nil end it "should convert strings with leading 0 to integer if they are not octal" do Puppet::Parser::Scope.number?("0788").should == 788 end it "should convert strings of negative integers" do Puppet::Parser::Scope.number?("-0788").should == -788 end it "should return nil on malformed hexadecimal numbers" do Puppet::Parser::Scope.number?("0x89g").should be_nil end end describe "when using ephemeral variables" do it "should store the variable value" do # @scope.setvar("1", :value, :ephemeral => true) @scope.set_match_data({1 => :value}) @scope["1"].should == :value end it "should remove the variable value when unset_ephemeral_var(:all) is called" do # @scope.setvar("1", :value, :ephemeral => true) @scope.set_match_data({1 => :value}) @scope.stubs(:parent).returns(nil) @scope.unset_ephemeral_var(:all) @scope["1"].should be_nil end it "should not remove classic variables when unset_ephemeral_var(:all) is called" do @scope['myvar'] = :value1 @scope.set_match_data({1 => :value2}) @scope.stubs(:parent).returns(nil) @scope.unset_ephemeral_var(:all) @scope["myvar"].should == :value1 end it "should raise an error when setting numerical variable" do expect { @scope.setvar("1", :value3, :ephemeral => true) }.to raise_error(Puppet::ParseError, /Cannot assign to a numeric match result variable/) end describe "with more than one level" do it "should prefer latest ephemeral scopes" do @scope.set_match_data({0 => :earliest}) @scope.new_ephemeral @scope.set_match_data({0 => :latest}) @scope["0"].should == :latest end it "should be able to report the current level" do @scope.ephemeral_level.should == 1 @scope.new_ephemeral @scope.ephemeral_level.should == 2 end it "should not check presence of an ephemeral variable accross multiple levels" do # This test was testing that scope actuallys screwed up - making values from earlier matches show as if they # where true for latest match - insanity ! @scope.new_ephemeral @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.new_ephemeral @scope.include?("1").should be_false end it "should return false when an ephemeral variable doesn't exist in any ephemeral scope" do @scope.new_ephemeral @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.new_ephemeral @scope.include?("2").should be_false end it "should not get ephemeral values from earlier scope when not in later" do @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.include?("1").should be_false end describe "when calling unset_ephemeral_var with a level" do it "should remove ephemeral scopes up to this level" do @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({1 => :value2}) level = @scope.ephemeral_level() @scope.new_ephemeral @scope.set_match_data({1 => :value3}) @scope.unset_ephemeral_var(level) @scope["1"].should == :value2 end end end end context "when using ephemeral as local scope" do it "should store all variables in local scope" do @scope.new_ephemeral true @scope.setvar("apple", :fruit) @scope["apple"].should == :fruit end it "should remove all local scope variables on unset" do @scope.new_ephemeral true @scope.setvar("apple", :fruit) @scope["apple"].should == :fruit @scope.unset_ephemeral_var @scope["apple"].should == nil end it "should be created from a hash" do @scope.ephemeral_from({ "apple" => :fruit, "strawberry" => :berry}) @scope["apple"].should == :fruit @scope["strawberry"].should == :berry end end describe "when setting ephemeral vars from matches" do before :each do @match = stub 'match', :is_a? => true @match.stubs(:[]).with(0).returns("this is a string") @match.stubs(:captures).returns([]) @scope.stubs(:setvar) end it "should accept only MatchData" do expect { @scope.ephemeral_from("match") }.to raise_error(ArgumentError, /Invalid regex match data/) end it "should set $0 with the full match" do # This is an internal impl detail test @scope.expects(:new_match_scope).with { |*arg| arg[0][0] == "this is a string" } @scope.ephemeral_from(@match) end it "should set every capture as ephemeral var" do # This is an internal impl detail test @match.stubs(:[]).with(1).returns(:capture1) @match.stubs(:[]).with(2).returns(:capture2) @scope.expects(:new_match_scope).with { |*arg| arg[0][1] == :capture1 && arg[0][2] == :capture2 } @scope.ephemeral_from(@match) end it "should shadow previous match variables" do # This is an internal impl detail test @match.stubs(:[]).with(1).returns(:capture1) @match.stubs(:[]).with(2).returns(:capture2) @match2 = stub 'match', :is_a? => true @match2.stubs(:[]).with(1).returns(:capture2_1) @match2.stubs(:[]).with(2).returns(nil) @scope.ephemeral_from(@match) @scope.ephemeral_from(@match2) @scope.lookupvar('2').should == nil end it "should create a new ephemeral level" do level_before = @scope.ephemeral_level @scope.ephemeral_from(@match) expect(level_before < @scope.ephemeral_level) end end it "should use its namespaces to find hostclasses" do klass = @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "a::b::c") @scope.add_namespace "a::b" @scope.find_hostclass("c").should equal(klass) end it "should use its namespaces to find definitions" do define = @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "a::b::c") @scope.add_namespace "a::b" @scope.find_definition("c").should equal(define) end describe "when managing defaults" do it "should be able to set and lookup defaults" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param) @scope.lookupdefaults(:mytype).should == {:myparam => param} end it "should fail if a default is already defined and a new default is being defined" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param) expect { @scope.define_settings(:mytype, param) }.to raise_error(Puppet::ParseError, /Default already defined .* cannot redefine/) end it "should return multiple defaults at once" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param1) param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param2) @scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2} end it "should look up defaults defined in parent scopes" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param1) child_scope = @scope.newscope param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) child_scope.define_settings(:mytype, param2) child_scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2} end end context "#true?" do { "a string" => true, "true" => true, "false" => true, true => true, "" => false, :undef => false, nil => false }.each do |input, output| it "should treat #{input.inspect} as #{output}" do Puppet::Parser::Scope.true?(input).should == output end end end context "when producing a hash of all variables (as used in templates)" do it "should contain all defined variables in the scope" do @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green } end it "should contain variables in all local scopes (#21508)" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green, 'apple' => :red } end it "should contain all defined variables in the scope and all local scopes" do @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green, 'apple' => :red } end it "should not contain varaibles in match scopes (non local emphemeral)" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.ephemeral_from(/(f)(o)(o)/.match('foo')) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green } end it "should delete values that are :undef in inner scope" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.setvar("orange", :undef) @scope.to_hash.should == {'pear' => :green, 'apple' => :red } end end end diff --git a/spec/unit/rails/host_spec.rb b/spec/unit/rails/host_spec.rb index 746c3a075..59f7e64d1 100755 --- a/spec/unit/rails/host_spec.rb +++ b/spec/unit/rails/host_spec.rb @@ -1,162 +1,162 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/node/environment' describe "Puppet::Rails::Host", :if => can_use_scratch_database? do before do require 'puppet/rails/host' setup_scratch_database @node = Puppet::Node.new("foo") @node.environment = "production" @node.ipaddress = "127.0.0.1" @host = stub 'host', :environment= => nil, :ip= => nil end describe "when converting a Puppet::Node instance into a Rails instance" do it "should modify any existing instance in the database" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host Puppet::Rails::Host.from_puppet(@node) end it "should create a new instance in the database if none can be found" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil Puppet::Rails::Host.expects(:new).with(:name => "foo").returns @host Puppet::Rails::Host.from_puppet(@node) end it "should copy the environment from the Puppet instance" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @node.environment = "production" @host.expects(:environment=).with {|x| x.name.to_s == 'production' } Puppet::Rails::Host.from_puppet(@node) end it "should stringify the environment" do host = Puppet::Rails::Host.new - host.environment = Puppet::Node::Environment.create(:production, [], '') + host.environment = Puppet::Node::Environment.create(:production, []) host.environment.class.should == String end it "should copy the ipaddress from the Puppet instance" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @node.ipaddress = "192.168.0.1" @host.expects(:ip=).with "192.168.0.1" Puppet::Rails::Host.from_puppet(@node) end it "should not save the Rails instance" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @host.expects(:save).never Puppet::Rails::Host.from_puppet(@node) end end describe "when converting a Puppet::Rails::Host instance into a Puppet::Node instance" do before do @host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1") @node = Puppet::Node.new("foo") Puppet::Node.stubs(:new).with("foo").returns @node end it "should create a new instance with the correct name" do Puppet::Node.expects(:new).with("foo").returns @node @host.to_puppet end it "should copy the environment from the Rails instance" do @host.environment = "prod" @node.expects(:environment=).with "prod" @host.to_puppet end it "should copy the ipaddress from the Rails instance" do @host.ip = "192.168.0.1" @node.expects(:ipaddress=).with "192.168.0.1" @host.to_puppet end end describe "when merging catalog resources and database resources" do before :each do Puppet[:thin_storeconfigs] = false @resource1 = stub_everything 'res1' @resource2 = stub_everything 'res2' @resources = [ @resource1, @resource2 ] @dbresource1 = stub_everything 'dbres1' @dbresource2 = stub_everything 'dbres2' @dbresources = { 1 => @dbresource1, 2 => @dbresource2 } @host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1") @host.stubs(:find_resources).returns(@dbresources) @host.stubs(:find_resources_parameters_tags) @host.stubs(:compare_to_catalog) @host.stubs(:id).returns(1) end it "should find all database resources" do @host.expects(:find_resources) @host.merge_resources(@resources) end it "should find all paramaters and tags for those database resources" do @host.expects(:find_resources_parameters_tags).with(@dbresources) @host.merge_resources(@resources) end it "should compare all database resources to catalog" do @host.expects(:compare_to_catalog).with(@dbresources, @resources) @host.merge_resources(@resources) end it "should compare only exported resources in thin_storeconfigs mode" do Puppet[:thin_storeconfigs] = true @resource1.stubs(:exported?).returns(true) @host.expects(:compare_to_catalog).with(@dbresources, [ @resource1 ]) @host.merge_resources(@resources) end end describe "when searching the database for host resources" do before :each do Puppet[:thin_storeconfigs] = false @resource1 = stub_everything 'res1', :id => 1 @resource2 = stub_everything 'res2', :id => 2 @resources = [ @resource1, @resource2 ] @dbresources = stub 'resources' @dbresources.stubs(:find).returns(@resources) @host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1") @host.stubs(:resources).returns(@dbresources) end it "should return a hash keyed by id of all resources" do @host.find_resources.should == { 1 => @resource1, 2 => @resource2 } end it "should return a hash keyed by id of only exported resources in thin_storeconfigs mode" do Puppet[:thin_storeconfigs] = true @dbresources.expects(:find).with { |*h| h[1][:conditions] == { :exported => true } }.returns([]) @host.find_resources end end end diff --git a/spec/unit/resource/type_collection_spec.rb b/spec/unit/resource/type_collection_spec.rb index 4cd5b339e..ad54c7ca1 100755 --- a/spec/unit/resource/type_collection_spec.rb +++ b/spec/unit/resource/type_collection_spec.rb @@ -1,417 +1,417 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/type_collection' require 'puppet/resource/type' describe Puppet::Resource::TypeCollection do include PuppetSpec::Files - let(:environment) { Puppet::Node::Environment.create(:testing, [], '') } + let(:environment) { Puppet::Node::Environment.create(:testing, []) } before do @instance = Puppet::Resource::Type.new(:hostclass, "foo") @code = Puppet::Resource::TypeCollection.new(environment) end it "should consider '<<' to be an alias to 'add' but should return self" do @code.expects(:add).with "foo" @code.expects(:add).with "bar" @code << "foo" << "bar" end it "should set itself as the code collection for added resource types" do node = Puppet::Resource::Type.new(:node, "foo") @code.add(node) @code.node("foo").should equal(node) node.resource_type_collection.should equal(@code) end it "should store node resource types as nodes" do node = Puppet::Resource::Type.new(:node, "foo") @code.add(node) @code.node("foo").should equal(node) end it "should fail if a duplicate node is added" do @code.add(Puppet::Resource::Type.new(:node, "foo")) expect do @code.add(Puppet::Resource::Type.new(:node, "foo")) end.to raise_error(Puppet::ParseError, /cannot redefine/) end it "should store hostclasses as hostclasses" do klass = Puppet::Resource::Type.new(:hostclass, "foo") @code.add(klass) @code.hostclass("foo").should equal(klass) end it "merge together hostclasses of the same name" do klass1 = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "first") klass2 = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "second") @code.add(klass1) @code.add(klass2) @code.hostclass("foo").doc.should == "firstsecond" end it "should store definitions as definitions" do define = Puppet::Resource::Type.new(:definition, "foo") @code.add(define) @code.definition("foo").should equal(define) end it "should fail if a duplicate definition is added" do @code.add(Puppet::Resource::Type.new(:definition, "foo")) expect do @code.add(Puppet::Resource::Type.new(:definition, "foo")) end.to raise_error(Puppet::ParseError, /cannot be redefined/) end it "should remove all nodes, classes, and definitions when cleared" do loader = Puppet::Resource::TypeCollection.new(environment) loader.add Puppet::Resource::Type.new(:hostclass, "class") loader.add Puppet::Resource::Type.new(:definition, "define") loader.add Puppet::Resource::Type.new(:node, "node") watched_file = tmpfile('watched_file') loader.watch_file(watched_file) loader.clear loader.hostclass("class").should be_nil loader.definition("define").should be_nil loader.node("node").should be_nil loader.should_not be_watching_file(watched_file) end describe "when resolving namespaces" do [ ['', '::foo', ['foo']], ['a', '::foo', ['foo']], ['a::b', '::foo', ['foo']], [['a::b'], '::foo', ['foo']], [['a::b', 'c'], '::foo', ['foo']], [['A::B', 'C'], '::Foo', ['foo']], ['', '', ['']], ['a', '', ['']], ['a::b', '', ['']], [['a::b'], '', ['']], [['a::b', 'c'], '', ['']], [['A::B', 'C'], '', ['']], ['', 'foo', ['foo']], ['a', 'foo', ['a::foo', 'foo']], ['a::b', 'foo', ['a::b::foo', 'a::foo', 'foo']], ['A::B', 'Foo', ['a::b::foo', 'a::foo', 'foo']], [['a::b'], 'foo', ['a::b::foo', 'a::foo', 'foo']], [['a', 'b'], 'foo', ['a::foo', 'foo', 'b::foo']], [['a::b', 'c::d'], 'foo', ['a::b::foo', 'a::foo', 'foo', 'c::d::foo', 'c::foo']], [['a::b', 'a::c'], 'foo', ['a::b::foo', 'a::foo', 'foo', 'a::c::foo']], ].each do |namespaces, name, expected_result| it "should resolve #{name.inspect} in namespaces #{namespaces.inspect} correctly" do @code.instance_eval { resolve_namespaces(namespaces, name) }.should == expected_result end end end describe "when looking up names" do before do @type = Puppet::Resource::Type.new(:hostclass, "ns::klass") end it "should support looking up with multiple namespaces" do @code.add @type @code.find_hostclass(%w{boo baz ns}, "klass").should equal(@type) end it "should not attempt to import anything when the type is already defined" do @code.add @type @code.loader.expects(:import).never @code.find_hostclass(%w{ns}, "klass").should equal(@type) end describe "that need to be loaded" do it "should use the loader to load the files" do @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass") @code.loader.expects(:try_load_fqname).with(:hostclass, "klass") @code.find_hostclass(["ns"], "klass") end it "should downcase the name and downcase and array-fy the namespaces before passing to the loader" do @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass") @code.loader.expects(:try_load_fqname).with(:hostclass, "klass") @code.find_hostclass("Ns", "Klass") end it "should use the class returned by the loader" do @code.loader.expects(:try_load_fqname).returns(:klass) @code.expects(:hostclass).with("ns::klass").returns(false) @code.find_hostclass("ns", "klass").should == :klass end it "should return nil if the name isn't found" do @code.loader.stubs(:try_load_fqname).returns(nil) @code.find_hostclass("Ns", "Klass").should be_nil end it "already-loaded names at broader scopes should not shadow autoloaded names" do @code.add Puppet::Resource::Type.new(:hostclass, "bar") @code.loader.expects(:try_load_fqname).with(:hostclass, "foo::bar").returns(:foobar) @code.find_hostclass("foo", "bar").should == :foobar end it "should not try to autoload names that we couldn't autoload in a previous step if ignoremissingtypes is enabled" do Puppet[:ignoremissingtypes] = true @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass").returns(nil) @code.loader.expects(:try_load_fqname).with(:hostclass, "klass").returns(nil) @code.find_hostclass("Ns", "Klass").should be_nil Puppet.expects(:debug).at_least_once.with {|msg| msg =~ /Not attempting to load hostclass/} @code.find_hostclass("Ns", "Klass").should be_nil end end end %w{hostclass node definition}.each do |data| describe "behavior of add for #{data}" do it "should return the added #{data}" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(data, "foo") loader.add(instance).should equal(instance) end it "should retrieve #{data} insensitive to case" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(data, "Bar") loader.add instance loader.send(data, "bAr").should equal(instance) end it "should return nil when asked for a #{data} that has not been added" do Puppet::Resource::TypeCollection.new(environment).send(data, "foo").should be_nil end end end describe "when finding a qualified instance" do it "should return any found instance if the instance name is fully qualified" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance loader.find_hostclass("namespace", "::foo::bar").should equal(instance) end it "should return nil if the instance name is fully qualified and no such instance exists" do loader = Puppet::Resource::TypeCollection.new(environment) loader.find_hostclass("namespace", "::foo::bar").should be_nil end it "should be able to find classes in the base namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo") loader.add instance loader.find_hostclass("", "foo").should equal(instance) end it "should return the partially qualified object if it exists in a provided namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo", "bar::baz").should equal(instance) end it "should be able to find partially qualified objects in any of the provided namespaces" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass(["nons", "foo", "otherns"], "bar::baz").should equal(instance) end it "should return the unqualified object if it exists in a provided namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance loader.find_hostclass("foo", "bar").should equal(instance) end it "should return the unqualified object if it exists in the parent namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance loader.find_hostclass("foo::bar::baz", "bar").should equal(instance) end it "should should return the partially qualified object if it exists in the parent namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo::bar", "bar::baz").should equal(instance) end it "should return the qualified object if it exists in the root namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo::bar", "foo::bar::baz").should equal(instance) end it "should return nil if the object cannot be found" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo::bar", "eh").should be_nil end describe "when topscope has a class that has the same name as a local class" do before do @loader = Puppet::Resource::TypeCollection.new(environment) [ "foo::bar", "bar" ].each do |name| @loader.add Puppet::Resource::Type.new(:hostclass, name) end end it "should favor the local class, if the name is unqualified" do @loader.find_hostclass("foo", "bar").name.should == 'foo::bar' end it "should only look in the topclass, if the name is qualified" do @loader.find_hostclass("foo", "::bar").name.should == 'bar' end it "should only look in the topclass, if we assume the name is fully qualified" do @loader.find_hostclass("foo", "bar", :assume_fqname => true).name.should == 'bar' end end it "should not look in the local scope for classes when the name is qualified" do @loader = Puppet::Resource::TypeCollection.new(environment) @loader.add Puppet::Resource::Type.new(:hostclass, "foo::bar") @loader.find_hostclass("foo", "::bar").should == nil end end it "should be able to find nodes" do node = Puppet::Resource::Type.new(:node, "bar") loader = Puppet::Resource::TypeCollection.new(environment) loader.add(node) loader.find_node(stub("ignored"), "bar").should == node end it "should indicate whether any nodes are defined" do loader = Puppet::Resource::TypeCollection.new(environment) loader.add_node(Puppet::Resource::Type.new(:node, "foo")) loader.should be_nodes end it "should indicate whether no nodes are defined" do Puppet::Resource::TypeCollection.new(environment).should_not be_nodes end describe "when finding nodes" do before :each do @loader = Puppet::Resource::TypeCollection.new(environment) end it "should return any node whose name exactly matches the provided node name" do node = Puppet::Resource::Type.new(:node, "foo") @loader << node @loader.node("foo").should equal(node) end it "should return the first regex node whose regex matches the provided node name" do node1 = Puppet::Resource::Type.new(:node, /\w/) node2 = Puppet::Resource::Type.new(:node, /\d/) @loader << node1 << node2 @loader.node("foo10").should equal(node1) end it "should preferentially return a node whose name is string-equal over returning a node whose regex matches a provided name" do node1 = Puppet::Resource::Type.new(:node, /\w/) node2 = Puppet::Resource::Type.new(:node, "foo") @loader << node1 << node2 @loader.node("foo").should equal(node2) end end describe "when managing files" do before do @loader = Puppet::Resource::TypeCollection.new(environment) Puppet::Util::WatchedFile.stubs(:new).returns stub("watched_file") end it "should have a method for specifying a file should be watched" do @loader.should respond_to(:watch_file) end it "should have a method for determining if a file is being watched" do @loader.watch_file("/foo/bar") @loader.should be_watching_file("/foo/bar") end it "should use WatchedFile to watch files" do Puppet::Util::WatchedFile.expects(:new).with("/foo/bar").returns stub("watched_file") @loader.watch_file("/foo/bar") end it "should be considered stale if any files have changed" do file1 = stub 'file1', :changed? => false file2 = stub 'file2', :changed? => true Puppet::Util::WatchedFile.expects(:new).times(2).returns(file1).then.returns(file2) @loader.watch_file("/foo/bar") @loader.watch_file("/other/bar") @loader.should be_stale end it "should not be considered stable if no files have changed" do file1 = stub 'file1', :changed? => false file2 = stub 'file2', :changed? => false Puppet::Util::WatchedFile.expects(:new).times(2).returns(file1).then.returns(file2) @loader.watch_file("/foo/bar") @loader.watch_file("/other/bar") @loader.should_not be_stale end end describe "when determining the configuration version" do before do @code = Puppet::Resource::TypeCollection.new(environment) end it "should default to the current time" do time = Time.now Time.stubs(:now).returns time @code.version.should == time.to_i end it "should use the output of the environment's config_version setting if one is provided" do @code.environment.stubs(:[]).with(:config_version).returns("/my/foo") Puppet::Util::Execution.expects(:execute).with(["/my/foo"]).returns "output\n" @code.version.should == "output" end it "should raise a puppet parser error if executing config_version fails" do @code.environment.stubs(:[]).with(:config_version).returns("test") Puppet::Util::Execution.expects(:execute).raises(Puppet::ExecutionFailure.new("msg")) lambda { @code.version }.should raise_error(Puppet::ParseError) end end end diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index b3a4aab78..497f0e711 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -1,777 +1,777 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/type' require 'matchers/json' describe Puppet::Resource::Type do include JSONMatchers it "should have a 'name' attribute" do Puppet::Resource::Type.new(:hostclass, "foo").name.should == "foo" end [:code, :doc, :line, :file, :resource_type_collection, :ruby_code].each do |attr| it "should have a '#{attr}' attribute" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.send(attr.to_s + "=", "yay") type.send(attr).should == "yay" end end [:hostclass, :node, :definition].each do |type| it "should know when it is a #{type}" do Puppet::Resource::Type.new(type, "foo").send("#{type}?").should be_true end end it "should indirect 'resource_type'" do Puppet::Resource::Type.indirection.name.should == :resource_type end it "should default to 'parser' for its terminus class" do Puppet::Resource::Type.indirection.terminus_class.should == :parser end describe "when converting to json" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo") end def from_json(json) Puppet::Resource::Type.from_data_hash(json) end def double_convert Puppet::Resource::Type.from_data_hash(PSON.parse(@type.to_pson)) end it "should include the name and type" do double_convert.name.should == @type.name double_convert.type.should == @type.type end it "should validate with only name and kind" do expect(@type.to_pson).to validate_against('api/schemas/resource_type.json') end it "should validate with all fields set" do @type.set_arguments("one" => nil, "two" => "foo") @type.line = 100 @type.doc = "A weird type" @type.file = "/etc/manifests/thing.pp" @type.parent = "one::two" expect(@type.to_pson).to validate_against('api/schemas/resource_type.json') end it "should include any arguments" do @type.set_arguments("one" => nil, "two" => "foo") double_convert.arguments.should == {"one" => nil, "two" => "foo"} end it "should not include arguments if none are present" do @type.to_pson["arguments"].should be_nil end [:line, :doc, :file, :parent].each do |attr| it "should include #{attr} when set" do @type.send(attr.to_s + "=", "value") double_convert.send(attr).should == "value" end it "should not include #{attr} when not set" do @type.to_pson[attr.to_s].should be_nil end end it "should not include docs if they are empty" do @type.doc = "" @type.to_pson["doc"].should be_nil end end describe "when a node" do it "should allow a regex as its name" do lambda { Puppet::Resource::Type.new(:node, /foo/) }.should_not raise_error end it "should allow an AST::HostName instance as its name" do regex = Puppet::Parser::AST::Regex.new(:value => /foo/) name = Puppet::Parser::AST::HostName.new(:value => regex) lambda { Puppet::Resource::Type.new(:node, name) }.should_not raise_error end it "should match against the regexp in the AST::HostName when a HostName instance is provided" do regex = Puppet::Parser::AST::Regex.new(:value => /\w/) name = Puppet::Parser::AST::HostName.new(:value => regex) node = Puppet::Resource::Type.new(:node, name) node.match("foo").should be_true end it "should return the value of the hostname if provided a string-form AST::HostName instance as the name" do name = Puppet::Parser::AST::HostName.new(:value => "foo") node = Puppet::Resource::Type.new(:node, name) node.name.should == "foo" end describe "and the name is a regex" do it "should have a method that indicates that this is the case" do Puppet::Resource::Type.new(:node, /w/).should be_name_is_regex end it "should set its namespace to ''" do Puppet::Resource::Type.new(:node, /w/).namespace.should == "" end it "should return the regex converted to a string when asked for its name" do Puppet::Resource::Type.new(:node, /ww/).name.should == "ww" end it "should downcase the regex when returning the name as a string" do Puppet::Resource::Type.new(:node, /W/).name.should == "w" end it "should remove non-alpha characters when returning the name as a string" do Puppet::Resource::Type.new(:node, /w*w/).name.should_not include("*") end it "should remove leading dots when returning the name as a string" do Puppet::Resource::Type.new(:node, /.ww/).name.should_not =~ /^\./ end it "should have a method for matching its regex name against a provided name" do Puppet::Resource::Type.new(:node, /.ww/).should respond_to(:match) end it "should return true when its regex matches the provided name" do Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true end it "should return true when its regex matches the provided name" do Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true end it "should return false when its regex does not match the provided name" do (!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).should be_false end it "should return true when its name, as a string, is matched against an equal string" do Puppet::Resource::Type.new(:node, "foo").match("foo").should be_true end it "should return false when its name is matched against an unequal string" do Puppet::Resource::Type.new(:node, "foo").match("bar").should be_false end it "should match names insensitive to case" do Puppet::Resource::Type.new(:node, "fOo").match("foO").should be_true end end end describe "when initializing" do it "should require a resource super type" do Puppet::Resource::Type.new(:hostclass, "foo").type.should == :hostclass end it "should fail if provided an invalid resource super type" do lambda { Puppet::Resource::Type.new(:nope, "foo") }.should raise_error(ArgumentError) end it "should set its name to the downcased, stringified provided name" do Puppet::Resource::Type.new(:hostclass, "Foo::Bar".intern).name.should == "foo::bar" end it "should set its namespace to the downcased, stringified qualified name for classes" do Puppet::Resource::Type.new(:hostclass, "Foo::Bar::Baz".intern).namespace.should == "foo::bar::baz" end [:definition, :node].each do |type| it "should set its namespace to the downcased, stringified qualified portion of the name for #{type}s" do Puppet::Resource::Type.new(type, "Foo::Bar::Baz".intern).namespace.should == "foo::bar" end end %w{code line file doc}.each do |arg| it "should set #{arg} if provided" do type = Puppet::Resource::Type.new(:hostclass, "foo", arg.to_sym => "something") type.send(arg).should == "something" end end it "should set any provided arguments with the keys as symbols" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"}) type.should be_valid_parameter("foo") type.should be_valid_parameter("baz") end it "should set any provided arguments with they keys as strings" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"}) type.should be_valid_parameter(:foo) type.should be_valid_parameter(:baz) end it "should function if provided no arguments" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.should_not be_valid_parameter(:foo) end end describe "when testing the validity of an attribute" do it "should return true if the parameter was typed at initialization" do Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_valid_parameter("foo") end it "should return true if it is a metaparam" do Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("require") end it "should return true if the parameter is named 'name'" do Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("name") end it "should return false if it is not a metaparam and was not provided at initialization" do Puppet::Resource::Type.new(:hostclass, "foo").should_not be_valid_parameter("yayness") end end describe "when setting its parameters in the scope" do before do @scope = Puppet::Parser::Scope.new(Puppet::Parser::Compiler.new(Puppet::Node.new("foo")), :source => stub("source")) @resource = Puppet::Parser::Resource.new(:foo, "bar", :scope => @scope) @type = Puppet::Resource::Type.new(:definition, "foo") @resource.environment.known_resource_types.add @type end ['module_name', 'name', 'title'].each do |variable| it "should allow #{variable} to be evaluated as param default" do @type.instance_eval { @module_name = "bar" } var = Puppet::Parser::AST::Variable.new({'value' => variable}) @type.set_arguments :foo => var @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == 'bar' end end # this test is to clarify a crazy edge case # if you specify these special names as params, the resource # will override the special variables it "should allow the resource to override defaults" do @type.set_arguments :name => nil @resource[:name] = 'foobar' var = Puppet::Parser::AST::Variable.new({'value' => 'name'}) @type.set_arguments :foo => var @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == 'foobar' end it "should set each of the resource's parameters as variables in the scope" do @type.set_arguments :foo => nil, :boo => nil @resource[:foo] = "bar" @resource[:boo] = "baz" @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == "bar" @scope['boo'].should == "baz" end it "should set the variables as strings" do @type.set_arguments :foo => nil @resource[:foo] = "bar" @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == "bar" end it "should fail if any of the resource's parameters are not valid attributes" do @type.set_arguments :foo => nil @resource[:boo] = "baz" lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError) end it "should evaluate and set its default values as variables for parameters not provided by the resource" do @type.set_arguments :foo => Puppet::Parser::AST::String.new(:value => "something") @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == "something" end it "should set all default values as parameters in the resource" do @type.set_arguments :foo => Puppet::Parser::AST::String.new(:value => "something") @type.set_resource_parameters(@resource, @scope) @resource[:foo].should == "something" end it "should fail if the resource does not provide a value for a required argument" do @type.set_arguments :foo => nil lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError) end it "should set the resource's title as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) @scope['title'].should == "bar" end it "should set the resource's name as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) @scope['name'].should == "bar" end it "should set its module name in the scope if available" do @type.instance_eval { @module_name = "mymod" } @type.set_resource_parameters(@resource, @scope) @scope["module_name"].should == "mymod" end it "should set its caller module name in the scope if available" do @scope.expects(:parent_module_name).returns "mycaller" @type.set_resource_parameters(@resource, @scope) @scope["caller_module_name"].should == "mycaller" end end describe "when describing and managing parent classes" do before do - environment = Puppet::Node::Environment.create(:testing, [], '') + environment = Puppet::Node::Environment.create(:testing, []) @krt = environment.known_resource_types @parent = Puppet::Resource::Type.new(:hostclass, "bar") @krt.add @parent @child = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") @krt.add @child @scope = Puppet::Parser::Scope.new(Puppet::Parser::Compiler.new(Puppet::Node.new("foo", :environment => environment))) end it "should be able to define a parent" do Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") end it "should use the code collection to find the parent resource type" do @child.parent_type(@scope).should equal(@parent) end it "should be able to find parent nodes" do parent = Puppet::Resource::Type.new(:node, "bar") @krt.add parent child = Puppet::Resource::Type.new(:node, "foo", :parent => "bar") @krt.add child child.parent_type(@scope).should equal(parent) end it "should cache a reference to the parent type" do @krt.stubs(:hostclass).with("foo::bar").returns nil @krt.expects(:hostclass).with("bar").once.returns @parent @child.parent_type(@scope) @child.parent_type end it "should correctly state when it is another type's child" do @child.parent_type(@scope) @child.should be_child_of(@parent) end it "should be considered the child of a parent's parent" do @grandchild = Puppet::Resource::Type.new(:hostclass, "baz", :parent => "foo") @krt.add @grandchild @child.parent_type(@scope) @grandchild.parent_type(@scope) @grandchild.should be_child_of(@parent) end it "should correctly state when it is not another type's child" do @notchild = Puppet::Resource::Type.new(:hostclass, "baz") @krt.add @notchild @notchild.should_not be_child_of(@parent) end end describe "when evaluating its code" do before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new @compiler @resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope) # This is so the internal resource lookup works, yo. @compiler.catalog.add_resource @resource @type = Puppet::Resource::Type.new(:hostclass, "foo") @resource.environment.known_resource_types.add @type end it "should add node regex captures to its scope" do @type = Puppet::Resource::Type.new(:node, /f(\w)o(.*)$/) match = @type.match('foo') code = stub 'code' @type.stubs(:code).returns code subscope = stub 'subscope', :compiler => @compiler @scope.expects(:newscope).with(:source => @type, :namespace => '', :resource => @resource).returns subscope elevel = 876 subscope.expects(:ephemeral_level).returns elevel subscope.expects(:ephemeral_from).with(match, nil, nil).returns subscope code.expects(:safeevaluate).with(subscope) subscope.expects(:unset_ephemeral_var).with(elevel) # Just to keep the stub quiet about intermediate calls @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should add hostclass names to the classes list" do @type.evaluate_code(@resource) @compiler.catalog.classes.should be_include("foo") end it "should not add defined resource names to the classes list" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) @compiler.catalog.classes.should_not be_include("foo") end it "should set all of its parameters in a subscope" do subscope = stub 'subscope', :compiler => @compiler @scope.expects(:newscope).with(:source => @type, :namespace => 'foo', :resource => @resource).returns subscope @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should not create a subscope for the :main class" do @resource.stubs(:title).returns(:main) @type.expects(:subscope).never @type.expects(:set_resource_parameters).with(@resource, @scope) @type.evaluate_code(@resource) end it "should store the class scope" do @type.evaluate_code(@resource) @scope.class_scope(@type).should be_instance_of(@scope.class) end it "should still create a scope but not store it if the type is a definition" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) @scope.class_scope(@type).should be_nil end it "should evaluate the AST code if any is provided" do code = stub 'code' @type.stubs(:code).returns code subscope = stub_everything("subscope", :compiler => @compiler) @scope.stubs(:newscope).returns subscope code.expects(:safeevaluate).with subscope @type.evaluate_code(@resource) end describe "and ruby code is provided" do it "should create a DSL Resource API and evaluate it" do @type.stubs(:ruby_code).returns(proc { "foo" }) @api = stub 'api' Puppet::DSL::ResourceAPI.expects(:new).with { |res, scope, code| code == @type.ruby_code }.returns @api @api.expects(:evaluate) @type.evaluate_code(@resource) end end it "should noop if there is no code" do @type.expects(:code).returns nil @type.evaluate_code(@resource) end describe "and it has a parent class" do before do @parent_type = Puppet::Resource::Type.new(:hostclass, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:class, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.known_resource_types @type.resource_type_collection.add @parent_type end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@parent_type).should_not be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@type).parent.object_id.should == @scope.class_scope(@parent_type).object_id end end describe "and it has a parent node" do before do @type = Puppet::Resource::Type.new(:node, "foo") @parent_type = Puppet::Resource::Type.new(:node, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:node, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.known_resource_types @type.resource_type_collection.add(@parent_type) end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@parent_type).should_not be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@type).parent.object_id.should == @scope.class_scope(@parent_type).object_id end end end describe "when creating a resource" do before do @node = Puppet::Node.new("foo", :environment => 'env') @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @top = Puppet::Resource::Type.new :hostclass, "top" @middle = Puppet::Resource::Type.new :hostclass, "middle", :parent => "top" @code = Puppet::Resource::TypeCollection.new("env") @code.add @top @code.add @middle @node.environment.stubs(:known_resource_types).returns(@code) end it "should create a resource instance" do @top.ensure_in_catalog(@scope).should be_instance_of(Puppet::Parser::Resource) end it "should set its resource type to 'class' when it is a hostclass" do Puppet::Resource::Type.new(:hostclass, "top").ensure_in_catalog(@scope).type.should == "Class" end it "should set its resource type to 'node' when it is a node" do Puppet::Resource::Type.new(:node, "top").ensure_in_catalog(@scope).type.should == "Node" end it "should fail when it is a definition" do lambda { Puppet::Resource::Type.new(:definition, "top").ensure_in_catalog(@scope) }.should raise_error(ArgumentError) end it "should add the created resource to the scope's catalog" do @top.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should add specified parameters to the resource" do @top.ensure_in_catalog(@scope, {'one'=>'1', 'two'=>'2'}) @compiler.catalog.resource(:class, "top")['one'].should == '1' @compiler.catalog.resource(:class, "top")['two'].should == '2' end it "should not require params for a param class" do @top.ensure_in_catalog(@scope, {}) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope, {}) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should fail if you try to create duplicate class resources" do othertop = Puppet::Parser::Resource.new(:class, 'top',:source => @source, :scope => @scope ) # add the same class resource to the catalog @compiler.catalog.add_resource(othertop) lambda { @top.ensure_in_catalog(@scope, {}) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should fail to evaluate if a parent class is defined but cannot be found" do othertop = Puppet::Resource::Type.new :hostclass, "something", :parent => "yay" @code.add othertop lambda { othertop.ensure_in_catalog(@scope) }.should raise_error(Puppet::ParseError) end it "should not create a new resource if one already exists" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope) end it "should return the existing resource when not creating a new one" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope).should == "something" end it "should not create a new parent resource if one already exists and it has a parent class" do @top.ensure_in_catalog(@scope) top_resource = @compiler.catalog.resource(:class, "top") @middle.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should equal(top_resource) end # #795 - tag before evaluation. it "should tag the catalog with the resource tags when it is evaluated" do @middle.ensure_in_catalog(@scope) @compiler.catalog.should be_tagged("middle") end it "should tag the catalog with the parent class tags when it is evaluated" do @middle.ensure_in_catalog(@scope) @compiler.catalog.should be_tagged("top") end end describe "when merging code from another instance" do def code(str) Puppet::Parser::AST::Leaf.new :value => str end it "should fail unless it is a class" do lambda { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.should raise_error(Puppet::Error) end it "should fail unless the source instance is a class" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:node, "foo") lambda { dest.merge(source) }.should raise_error(Puppet::Error) end it "should fail if both classes have different parent classes" do code = Puppet::Resource::TypeCollection.new("env") {"a" => "b", "c" => "d"}.each do |parent, child| code.add Puppet::Resource::Type.new(:hostclass, parent) code.add Puppet::Resource::Type.new(:hostclass, child, :parent => parent) end lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(Puppet::Error) end it "should fail if it's named 'main' and 'freeze_main' is enabled" do Puppet.settings[:freeze_main] = true code = Puppet::Resource::TypeCollection.new("env") code.add Puppet::Resource::Type.new(:hostclass, "") other = Puppet::Resource::Type.new(:hostclass, "") lambda { code.hostclass("").merge(other) }.should raise_error(Puppet::Error) end it "should copy the other class's parent if it has not parent" do dest = Puppet::Resource::Type.new(:hostclass, "bar") parent = Puppet::Resource::Type.new(:hostclass, "parent") source = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "parent") dest.merge(source) dest.parent.should == "parent" end it "should copy the other class's documentation as its docs if it has no docs" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) dest.doc.should == "yayness" end it "should append the other class's docs to its docs if it has any" do dest = Puppet::Resource::Type.new(:hostclass, "bar", :doc => "fooness") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) dest.doc.should == "foonessyayness" end it "should set the other class's code as its code if it has none" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar")) dest.merge(source) dest.code.value.should == "bar" end it "should append the other class's code to its code if it has any" do dcode = Puppet::Parser::AST::BlockExpression.new(:children => [code("dest")]) dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => dcode) scode = Puppet::Parser::AST::BlockExpression.new(:children => [code("source")]) source = Puppet::Resource::Type.new(:hostclass, "foo", :code => scode) dest.merge(source) dest.code.children.collect { |l| l.value }.should == %w{dest source} end end end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 42d0a7ec8..73c27c1fe 100755 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -1,979 +1,979 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource' describe Puppet::Resource do include PuppetSpec::Files let(:basepath) { make_absolute("/somepath") } - let(:environment) { Puppet::Node::Environment.create(:testing, [], '') } + let(:environment) { Puppet::Node::Environment.create(:testing, []) } [:catalog, :file, :line].each do |attr| it "should have an #{attr} attribute" do resource = Puppet::Resource.new("file", "/my/file") resource.should respond_to(attr) resource.should respond_to(attr.to_s + "=") end end it "should have a :title attribute" do Puppet::Resource.new(:user, "foo").title.should == "foo" end it "should require the type and title" do expect { Puppet::Resource.new }.to raise_error(ArgumentError) end it "should canonize types to capitalized strings" do Puppet::Resource.new(:user, "foo").type.should == "User" end it "should canonize qualified types so all strings are capitalized" do Puppet::Resource.new("foo::bar", "foo").type.should == "Foo::Bar" end it "should tag itself with its type" do Puppet::Resource.new("file", "/f").should be_tagged("file") end it "should tag itself with its title if the title is a valid tag" do Puppet::Resource.new("user", "bar").should be_tagged("bar") end it "should not tag itself with its title if the title is a not valid tag" do Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar") end it "should allow setting of attributes" do Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo" Puppet::Resource.new("file", "/bar", :exported => true).should be_exported end it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do ref = Puppet::Resource.new(:component, "foo") ref.type.should == "Class" ref.title.should == "Foo" end it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do ref = Puppet::Resource.new(:component, "foo::bar[yay]") ref.type.should == "Foo::Bar" ref.title.should == "yay" end it "should set the type to 'Class' if it is nil and the title contains no square brackets" do ref = Puppet::Resource.new(nil, "yay") ref.type.should == "Class" ref.title.should == "Yay" end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[yay]") ref.type.should == "Foo::Bar" ref.title.should == "yay" end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]") ref.type.should == "Foo::Bar" ref.title.should =="baz[yay]" end it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do ref = Puppet::Resource.new("foo::bar[baz]") ref.type.should == "Foo::Bar" ref.title.should =="baz" end it "should be able to extract its information from a Puppet::Type instance" do ral = Puppet::Type.type(:file).new :path => basepath+"/foo" ref = Puppet::Resource.new(ral) ref.type.should == "File" ref.title.should == basepath+"/foo" end it "should fail if the title is nil and the type is not a valid resource reference string" do expect { Puppet::Resource.new("resource-spec-foo") }.to raise_error(ArgumentError) end it 'should fail if strict is set and type does not exist' do expect { Puppet::Resource.new('resource-spec-foo', 'title', {:strict=>true}) }.to raise_error(ArgumentError, 'Invalid resource type resource-spec-foo') end it 'should fail if strict is set and class does not exist' do expect { Puppet::Resource.new('Class', 'resource-spec-foo', {:strict=>true}) }.to raise_error(ArgumentError, 'Could not find declared class resource-spec-foo') end it "should fail if the title is a hash and the type is not a valid resource reference string" do expect { Puppet::Resource.new({:type => "resource-spec-foo", :title => "bar"}) }. to raise_error ArgumentError, /Puppet::Resource.new does not take a hash/ end it "should be taggable" do Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging) end it "should have an 'exported' attribute" do resource = Puppet::Resource.new("file", "/f") resource.exported = true resource.exported.should == true resource.should be_exported end describe "and munging its type and title" do describe "when modeling a builtin resource" do it "should be able to find the resource type" do Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file)) end it "should set its type to the capitalized type name" do Puppet::Resource.new("file", "/my/file").type.should == "File" end end describe "when modeling a defined resource" do describe "that exists" do before do @type = Puppet::Resource::Type.new(:definition, "foo::bar") environment.known_resource_types.add @type end it "should set its type to the capitalized type name" do Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).type.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).resource_type.should equal(@type) end it "should set its title to the provided title" do Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).title.should == "/my/file" end end describe "that does not exist" do it "should set its resource type to the capitalized resource type name" do Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar" end end end describe "when modeling a node" do # Life's easier with nodes, because they can't be qualified. it "should set its type to 'Node' and its title to the provided title" do node = Puppet::Resource.new("node", "foo") node.type.should == "Node" node.title.should == "foo" end end describe "when modeling a class" do it "should set its type to 'Class'" do Puppet::Resource.new("class", "foo").type.should == "Class" end describe "that exists" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo::bar") environment.known_resource_types.add @type end it "should set its title to the capitalized, fully qualified resource type" do Puppet::Resource.new("class", "foo::bar", :environment => environment).title.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("class", "foo::bar", :environment => environment).resource_type.should equal(@type) end end describe "that does not exist" do it "should set its type to 'Class' and its title to the capitalized provided name" do klass = Puppet::Resource.new("class", "foo::bar") klass.type.should == "Class" klass.title.should == "Foo::Bar" end end describe "and its name is set to the empty string" do it "should set its title to :main" do Puppet::Resource.new("class", "").title.should == :main end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") environment.known_resource_types.add @type Puppet::Resource.new("class", "", :environment => environment).title.should == :main end end end describe "and its name is set to :main" do it "should set its title to :main" do Puppet::Resource.new("class", :main).title.should == :main end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") environment.known_resource_types.add @type Puppet::Resource.new("class", :main, :environment => environment).title.should == :main end end end end end it "should return nil when looking up resource types that don't exist" do Puppet::Resource.new("foobar", "bar").resource_type.should be_nil end it "should not fail when an invalid parameter is used and strict mode is disabled" do type = Puppet::Resource::Type.new(:definition, "foobar") environment.known_resource_types.add type resource = Puppet::Resource.new("foobar", "/my/file", :environment => environment) resource[:yay] = true end it "should be considered equivalent to another resource if their type and title match and no parameters are set" do Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f") end it "should be considered equivalent to another resource if their type, title, and parameters are equal" do Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}).should == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}) end it "should not be considered equivalent to another resource if their type and title match but parameters are different" do Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"}).should_not == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}) end it "should not be considered equivalent to a non-resource" do Puppet::Resource.new("file", "/f").should_not == "foo" end it "should not be considered equivalent to another resource if their types do not match" do Puppet::Resource.new("file", "/f").should_not == Puppet::Resource.new("exec", "/f") end it "should not be considered equivalent to another resource if their titles do not match" do Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f") end describe "when setting default parameters" do let(:foo_node) { Puppet::Node.new('foo', :environment => environment) } let(:compiler) { Puppet::Parser::Compiler.new(foo_node) } let(:scope) { Puppet::Parser::Scope.new(compiler) } def ast_string(value) Puppet::Parser::AST::String.new({:value => value}) end it "should fail when asked to set default values and it is not a parser resource" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_string("default")}) ) resource = Puppet::Resource.new("default_param", "name", :environment => environment) lambda { resource.set_default_parameters(scope) }.should raise_error(Puppet::DevError) end it "should evaluate and set any default values when no value is provided" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_string("a_default_value")}) ) resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope) resource.set_default_parameters(scope) resource["a"].should == "a_default_value" end it "should skip attributes with no default value" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "no_default_param", :arguments => {"a" => ast_string("a_default_value")}) ) resource = Puppet::Parser::Resource.new("no_default_param", "name", :scope => scope) lambda { resource.set_default_parameters(scope) }.should_not raise_error end it "should return the list of default parameters set" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_string("a_default_value")}) ) resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope) resource.set_default_parameters(scope).should == ["a"] end describe "when the resource type is :hostclass" do let(:environment_name) { "testing env" } let(:fact_values) { { :a => 1 } } let(:port) { Puppet::Parser::AST::String.new(:value => '80') } let(:apache) { Puppet::Resource::Type.new(:hostclass, 'apache', :arguments => { 'port' => port }) } before do environment.known_resource_types.add(apache) scope.stubs(:host).returns('host') scope.stubs(:environment).returns(environment) scope.stubs(:facts).returns(Puppet::Node::Facts.new("facts", fact_values)) end context "when no value is provided" do before(:each) do Puppet[:binder] = true end let(:resource) do Puppet::Parser::Resource.new("class", "apache", :scope => scope) end it "should query the data_binding terminus using a namespaced key" do Puppet::DataBinding.indirection.expects(:find).with( 'apache::port', all_of(has_key(:environment), has_key(:variables))) resource.set_default_parameters(scope) end it "should use the value from the data_binding terminus" do Puppet::DataBinding.indirection.expects(:find).returns('443') resource.set_default_parameters(scope) resource[:port].should == '443' end it "should use the default value if the data_binding terminus returns nil" do Puppet::DataBinding.indirection.expects(:find).returns(nil) resource.set_default_parameters(scope) resource[:port].should == '80' end it "should fail with error message about data binding on a hiera failure" do Puppet::DataBinding.indirection.expects(:find).raises(Puppet::DataBinding::LookupError, 'Forgettabotit') expect { resource.set_default_parameters(scope) }.to raise_error(Puppet::Error, /Error from DataBinding 'hiera' while looking up 'apache::port':.*Forgettabotit/) end end context "when a value is provided" do let(:port_parameter) do Puppet::Parser::Resource::Param.new( { :name => 'port', :value => '8080' } ) end let(:resource) do Puppet::Parser::Resource.new("class", "apache", :scope => scope, :parameters => [port_parameter]) end it "should not query the data_binding terminus" do Puppet::DataBinding.indirection.expects(:find).never resource.set_default_parameters(scope) end it "should not query the injector" do # enable the injector Puppet[:binder] = true compiler.injector.expects(:find).never resource.set_default_parameters(scope) end it "should use the value provided" do Puppet::DataBinding.indirection.expects(:find).never resource.set_default_parameters(scope).should == [] resource[:port].should == '8080' end end end end describe "when validating all required parameters are present" do it "should be able to validate that all required parameters are present" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "required_param", :arguments => {"a" => nil}) ) lambda { Puppet::Resource.new("required_param", "name", :environment => environment).validate_complete }.should raise_error(Puppet::ParseError) end it "should not fail when all required parameters are present" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "no_required_param") ) resource = Puppet::Resource.new("no_required_param", "name", :environment => environment) resource["a"] = "meh" lambda { resource.validate_complete }.should_not raise_error end it "should not validate against builtin types" do lambda { Puppet::Resource.new("file", "/bar").validate_complete }.should_not raise_error end end describe "when referring to a resource with name canonicalization" do it "should canonicalize its own name" do res = Puppet::Resource.new("file", "/path/") res.uniqueness_key.should == ["/path"] res.ref.should == "File[/path/]" end end describe "when running in strict mode" do it "should be strict" do Puppet::Resource.new("file", "/path", :strict => true).should be_strict end it "should fail if invalid parameters are used" do expect { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.to raise_error end it "should fail if the resource type cannot be resolved" do expect { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.to raise_error end end describe "when managing parameters" do before do @resource = Puppet::Resource.new("file", "/my/file") end it "should correctly detect when provided parameters are not valid for builtin types" do Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar") end it "should correctly detect when provided parameters are valid for builtin types" do Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode") end it "should correctly detect when provided parameters are not valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar") environment.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file", :environment => environment).should_not be_valid_parameter("myparam") end it "should correctly detect when provided parameters are valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil}) environment.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file", :environment => environment).should be_valid_parameter("myparam") end it "should allow setting and retrieving of parameters" do @resource[:foo] = "bar" @resource[:foo].should == "bar" end it "should allow setting of parameters at initialization" do Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar" end it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do @resource[:foo] = "bar" @resource["foo"].should == "bar" end it "should canonicalize set parameter names to treat symbols and strings equivalently" do @resource["foo"] = "bar" @resource[:foo].should == "bar" end it "should set the namevar when asked to set the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:name] = "bob" resource[:myvar].should == "bob" end it "should return the namevar when asked to return the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:myvar] = "test" resource[:name].should == "test" end it "should be able to set the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" expect { resource[:name] = "eh" }.to_not raise_error end it "should be able to return the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" resource[:name].should == "eh" end it "should be able to iterate over parameters" do @resource[:foo] = "bar" @resource[:fee] = "bare" params = {} @resource.each do |key, value| params[key] = value end params.should == {:foo => "bar", :fee => "bare"} end it "should include Enumerable" do @resource.class.ancestors.should be_include(Enumerable) end it "should have a method for testing whether a parameter is included" do @resource[:foo] = "bar" @resource.should be_has_key(:foo) @resource.should_not be_has_key(:eh) end it "should have a method for providing the list of parameters" do @resource[:foo] = "bar" @resource[:bar] = "foo" keys = @resource.keys keys.should be_include(:foo) keys.should be_include(:bar) end it "should have a method for providing the number of parameters" do @resource[:foo] = "bar" @resource.length.should == 1 end it "should have a method for deleting parameters" do @resource[:foo] = "bar" @resource.delete(:foo) @resource[:foo].should be_nil end it "should have a method for testing whether the parameter list is empty" do @resource.should be_empty @resource[:foo] = "bar" @resource.should_not be_empty end it "should be able to produce a hash of all existing parameters" do @resource[:foo] = "bar" @resource[:fee] = "yay" hash = @resource.to_hash hash[:foo].should == "bar" hash[:fee].should == "yay" end it "should not provide direct access to the internal parameters hash when producing a hash" do hash = @resource.to_hash hash[:foo] = "bar" @resource[:foo].should be_nil end it "should use the title as the namevar to the hash if no namevar is present" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource.to_hash[:myvar].should == "bob" end it "should set :name to the title if :name is not present for non-builtin types" do krt = Puppet::Resource::TypeCollection.new("myenv") krt.add Puppet::Resource::Type.new(:definition, :foo) resource = Puppet::Resource.new :foo, "bar" resource.stubs(:known_resource_types).returns krt resource.to_hash[:name].should == "bar" end end describe "when serializing a native type" do before do @resource = Puppet::Resource.new("file", "/my/file") @resource["one"] = "test" @resource["two"] = "other" end it "should produce an equivalent yaml object" do text = @resource.render('yaml') newresource = Puppet::Resource.convert_from('yaml', text) newresource.should equal_attributes_of @resource end end describe "when serializing a defined type" do before do type = Puppet::Resource::Type.new(:definition, "foo::bar") environment.known_resource_types.add type @resource = Puppet::Resource.new('foo::bar', 'xyzzy', :environment => environment) @resource['one'] = 'test' @resource['two'] = 'other' @resource.resource_type end it "doesn't include transient instance variables (#4506)" do expect(@resource.to_yaml_properties).to_not include :@rstype end it "produces an equivalent yaml object" do text = @resource.render('yaml') newresource = Puppet::Resource.convert_from('yaml', text) newresource.should equal_attributes_of @resource end end describe "when converting to a RAL resource" do it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do resource = Puppet::Resource.new("file", basepath+"/my/file") result = resource.to_ral result.must be_instance_of(Puppet::Type.type(:file)) result[:path].should == basepath+"/my/file" end it "should convert to a component instance if the resource type is not of a builtin type" do resource = Puppet::Resource.new("foobar", "somename") result = resource.to_ral result.must be_instance_of(Puppet::Type.type(:component)) result.title.should == "Foobar[somename]" end end describe "when converting to puppet code" do before do @resource = Puppet::Resource.new("one::two", "/my/file", :parameters => { :noop => true, :foo => %w{one two}, :ensure => 'present', } ) end it "should align, sort and add trailing commas to attributes with ensure first" do @resource.to_manifest.should == <<-HEREDOC.gsub(/^\s{8}/, '').gsub(/\n$/, '') one::two { '/my/file': ensure => 'present', foo => ['one', 'two'], noop => 'true', } HEREDOC end end describe "when converting to pson" do def pson_output_should @resource.class.expects(:pson_create).with { |hash| yield hash } end it "should include the pson util module" do Puppet::Resource.singleton_class.ancestors.should be_include(Puppet::Util::Pson) end # LAK:NOTE For all of these tests, we convert back to the resource so we can # trap the actual data structure then. it "should set its type to the provided type" do Puppet::Resource.from_data_hash(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_data_hash(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo" end it "should include all tags from the resource" do resource = Puppet::Resource.new("File", "/foo") resource.tag("yay") Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).tags.should == resource.tags end it "should include the file if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.file = "/my/file" Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).file.should == "/my/file" end it "should include the line if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.line = 50 Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).line.should == 50 end it "should include the 'exported' value if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.exported = true Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_true end it "should set 'exported' to false if no value is set" do resource = Puppet::Resource.new("File", "/foo") Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_false end it "should set all of its parameters as the 'parameters' entry" do resource = Puppet::Resource.new("File", "/foo") resource[:foo] = %w{bar eh} resource[:fee] = %w{baz} result = Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)) result["foo"].should == %w{bar eh} result["fee"].should == %w{baz} end it "should serialize relationships as reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = Puppet::Resource.new("File", "/bar") result = Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)) result[:requires].should == "File[/bar]" end it "should serialize multiple relationships as arrays of reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = [Puppet::Resource.new("File", "/bar"), Puppet::Resource.new("File", "/baz")] result = Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)) result[:requires].should == [ "File[/bar]", "File[/baz]" ] end end describe "when converting from pson" do def pson_result_should Puppet::Resource.expects(:new).with { |hash| yield hash } end before do @data = { 'type' => "file", 'title' => basepath+"/yay", } end it "should set its type to the provided type" do Puppet::Resource.from_data_hash(@data).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_data_hash(@data).title.should == basepath+"/yay" end it "should tag the resource with any provided tags" do @data['tags'] = %w{foo bar} resource = Puppet::Resource.from_data_hash(@data) resource.tags.should be_include("foo") resource.tags.should be_include("bar") end it "should set its file to the provided file" do @data['file'] = "/foo/bar" Puppet::Resource.from_data_hash(@data).file.should == "/foo/bar" end it "should set its line to the provided line" do @data['line'] = 50 Puppet::Resource.from_data_hash(@data).line.should == 50 end it "should 'exported' to true if set in the pson data" do @data['exported'] = true Puppet::Resource.from_data_hash(@data).exported.should be_true end it "should 'exported' to false if not set in the pson data" do Puppet::Resource.from_data_hash(@data).exported.should be_false end it "should fail if no title is provided" do @data.delete('title') expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError) end it "should fail if no type is provided" do @data.delete('type') expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError) end it "should set each of the provided parameters" do @data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}} resource = Puppet::Resource.from_data_hash(@data) resource['foo'].should == %w{one two} resource['fee'].should == %w{three four} end it "should convert single-value array parameters to normal values" do @data['parameters'] = {'foo' => %w{one}} resource = Puppet::Resource.from_data_hash(@data) resource['foo'].should == %w{one} end end it "implements copy_as_resource" do resource = Puppet::Resource.new("file", "/my/file") resource.copy_as_resource.should == resource end describe "because it is an indirector model" do it "should include Puppet::Indirector" do Puppet::Resource.should be_is_a(Puppet::Indirector) end it "should have a default terminus" do Puppet::Resource.indirection.terminus_class.should be end it "should have a name" do Puppet::Resource.new("file", "/my/file").name.should == "File//my/file" end end describe "when resolving resources with a catalog" do it "should resolve all resources using the catalog" do catalog = mock 'catalog' resource = Puppet::Resource.new("foo::bar", "yay") resource.catalog = catalog catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource) resource.resolve.should == :myresource end end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:myvar, :owner, :path] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) res = Puppet::Resource.new("file", "/my/file", :parameters => {:owner => 'root', :content => 'hello'}) res.uniqueness_key.should == [ nil, 'root', '/my/file'] end end describe '#parse_title' do describe 'with a composite namevar' do before do Puppet::Type.newtype(:composite) do newparam(:name) newparam(:value) # Configure two title patterns to match a title that is either # separated with a colon or exclamation point. The first capture # will be used for the :name param, and the second capture will be # used for the :value param. def self.title_patterns identity = lambda {|x| x } reverse = lambda {|x| x.reverse } [ [ /^(.*?):(.*?)$/, [ [:name, identity], [:value, identity], ] ], [ /^(.*?)!(.*?)$/, [ [:name, reverse], [:value, reverse], ] ], ] end end end describe "with no matching title patterns" do subject { Puppet::Resource.new(:composite, 'unmatching title')} it "should raise an exception if no title patterns match" do expect do subject.to_hash end.to raise_error(Puppet::Error, /No set of title patterns matched/) end end describe "with a matching title pattern" do subject { Puppet::Resource.new(:composite, 'matching:title') } it "should not raise an exception if there was a match" do expect do subject.to_hash end.to_not raise_error end it "should set the resource parameters from the parsed title values" do h = subject.to_hash h[:name].should == 'matching' h[:value].should == 'title' end end describe "and multiple title patterns" do subject { Puppet::Resource.new(:composite, 'matching!title') } it "should use the first title pattern that matches" do h = subject.to_hash h[:name].should == 'gnihctam' h[:value].should == 'eltit' end end end end describe "#prune_parameters" do before do Puppet.newtype('blond') do newproperty(:ensure) newproperty(:height) newproperty(:weight) newproperty(:sign) newproperty(:friends) newparam(:admits_to_dying_hair) newparam(:admits_to_age) newparam(:name) end end it "should strip all parameters and strip properties that are nil, empty or absent except for ensure" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'absent', :height => '', :weight => 'absent', :friends => [], :admits_to_age => true, :admits_to_dying_hair => false }) pruned_resource = resource.prune_parameters pruned_resource.should == Puppet::Resource.new("blond", "Bambi", :parameters => {:ensure => 'absent'}) end it "should leave parameters alone if in parameters_to_include" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :admits_to_age => true, :admits_to_dying_hair => false }) pruned_resource = resource.prune_parameters(:parameters_to_include => [:admits_to_dying_hair]) pruned_resource.should == Puppet::Resource.new("blond", "Bambi", :parameters => {:admits_to_dying_hair => false}) end it "should leave properties if not nil, absent or empty" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'silly', :height => '7 ft 5 in', :friends => ['Oprah'], }) pruned_resource = resource.prune_parameters pruned_resource.should == resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'silly', :height => '7 ft 5 in', :friends => ['Oprah'], }) end end end diff --git a/spec/unit/util/autoload_spec.rb b/spec/unit/util/autoload_spec.rb index 1800dd2dc..3028c9e82 100755 --- a/spec/unit/util/autoload_spec.rb +++ b/spec/unit/util/autoload_spec.rb @@ -1,256 +1,256 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/autoload' describe Puppet::Util::Autoload do include PuppetSpec::Files before do @autoload = Puppet::Util::Autoload.new("foo", "tmp") @autoload.stubs(:eachdir).yields make_absolute("/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 lib directories that exist in the current environment's module path" do - environment = Puppet::Node::Environment.create(:foo, [@dira, @dirb, @dirc], '') + environment = Puppet::Node::Environment.create(:foo, [@dira, @dirb, @dirc]) Dir.expects(:entries).with(@dira).returns %w{. .. one two} Dir.expects(:entries).with(@dirb).returns %w{. .. one two} Puppet::FileSystem.expects(:directory?).with(@dira).returns true Puppet::FileSystem.expects(:directory?).with(@dirb).returns true Puppet::FileSystem.expects(:directory?).with(@dirc).returns false FileTest.expects(:directory?).with(regexp_matches(%r{two/lib})).times(2).returns true FileTest.expects(:directory?).with(regexp_matches(%r{one/lib})).times(2).returns false @autoload.class.module_directories(environment).should == ["#{@dira}/two/lib", "#{@dirb}/two/lib"] 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(:gem_directories).returns %w{/one /two} @autoload.class.expects(:module_directories).returns %w{/three /four} @autoload.class.search_directories(nil).should == %w{/one /two /three /four} + Puppet[:libdir].split(File::PATH_SEPARATOR) + $LOAD_PATH end end describe "when loading a file" do before do @autoload.class.stubs(:search_directories).returns [make_absolute("/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 Puppet::FileSystem.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 Puppet::FileSystem.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 be seen by loaded? on the instance using the short name" do Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @autoload.loaded?("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 Puppet::FileSystem.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 [make_absolute("/a"), make_absolute("/b")] FileTest.stubs(:directory?).returns true Puppet::FileSystem.stubs(:exist?).returns true Kernel.expects(:load).with(make_absolute("/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 Puppet::FileSystem.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 [make_absolute("/a")] FileTest.stubs(:directory?).returns true Dir.stubs(:glob).returns [make_absolute("/a/foo/file.rb")] Puppet::FileSystem.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" 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" do Kernel.expects(:load).with(make_absolute("/a/foo/file.rb"), optionally(anything)) @autoload.loadall end end describe "when reloading files" do before :each do @file_a = make_absolute("/a/file.rb") @file_b = make_absolute("/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 it "#changed? should return true for a file that was not loaded" do @autoload.class.changed?(@file_a).should be end it "changes should be seen by changed? on the instance using the short name" do File.stubs(:mtime).returns(@first_time) Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @autoload.loaded?("myfile").should be @autoload.changed?("myfile").should_not be File.stubs(:mtime).returns(@second_time) @autoload.changed?("myfile").should be $LOADED_FEATURES.delete("tmp/myfile.rb") end describe "in one directory" do before :each do @autoload.class.stubs(:search_directories).returns [make_absolute("/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) Puppet::FileSystem.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) Puppet::FileSystem.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 [make_absolute("/a"), make_absolute("/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) Puppet::FileSystem.stubs(:exist?).with(@file_a).returns false Puppet::FileSystem.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 Puppet::FileSystem.stubs(:exist?).with(@file_b).returns true @autoload.class.mark_loaded("file", @file_b) File.stubs(:mtime).with(@file_a).returns @first_time Puppet::FileSystem.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 describe "#cleanpath" do it "should leave relative paths relative" do path = "hello/there" Puppet::Util::Autoload.cleanpath(path).should == path end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should convert c:\ to c:/" do Puppet::Util::Autoload.cleanpath('c:\\').should == 'c:/' end end end describe "#expand" do it "should expand relative to the autoloader's prefix" do @autoload.expand('bar').should == 'tmp/bar' end end end diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index fa536703a..53231ef48 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -1,599 +1,599 @@ #! /usr/bin/env ruby require 'spec_helper' describe "RDoc::Parser", :if => Puppet.features.rdoc1? do before :all do require 'puppet/resource/type_collection' require 'puppet/util/rdoc/parser' require 'puppet/util/rdoc/code_objects' require 'rdoc/options' require 'rdoc/rdoc' end include PuppetSpec::Files before :each do stub_file = stub('init.pp', :stat => stub()) # Ruby 1.8.7 needs the following call to be stubs and not expects Puppet::FileSystem.stubs(:stat).with('init.pp').returns stub() # stub_file @top_level = stub_everything 'toplevel', :file_relative_name => "init.pp" @parser = RDoc::Parser.new(@top_level, "module/manifests/init.pp", nil, Options.instance, RDoc::Stats.new) end describe "when scanning files" do it "should parse puppet files with the puppet parser" do @parser.stubs(:scan_top_level) parser = stub 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once parser.expects(:file=).with("module/manifests/init.pp") parser.expects(:file=).with do |args| args =~ /.*\/etc\/manifests\/site.pp/ end @parser.scan end it "should scan the ast for Puppet files" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @parser.scan end it "should return a PuppetTopLevel to RDoc" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @parser.scan.should be_a(RDoc::PuppetTopLevel) end it "should scan the top level even if the file has already parsed" do known_type = stub 'known_types' env = stub 'env' Puppet::Node::Environment.stubs(:new).returns(env) env.stubs(:known_resource_types).returns(known_type) known_type.expects(:watching_file?).with("module/manifests/init.pp").returns(true) @parser.expects(:scan_top_level) @parser.scan end end describe "when scanning top level entities" do - let(:environment) { Puppet::Node::Environment.create(:env, [], '') } + let(:environment) { Puppet::Node::Environment.create(:env, []) } before :each do @resource_type_collection = resource_type_collection = stub_everything('resource_type_collection') environment.stubs(:known_resource_types).returns(@resource_type_collection) @parser.stubs(:split_module).returns("module") @topcontainer = stub_everything 'topcontainer' @container = stub_everything 'container' @module = stub_everything 'module' @container.stubs(:add_module).returns(@module) @parser.stubs(:get_class_or_module).returns([@container, "module"]) end it "should read any present README as module documentation" do FileTest.stubs(:readable?).with("module/README").returns(true) FileTest.stubs(:readable?).with("module/README.rdoc").returns(false) File.stubs(:open).returns("readme") @parser.stubs(:parse_elements) @module.expects(:add_comment).with("readme", "module/manifests/init.pp") @parser.scan_top_level(@topcontainer, environment) end it "should read any present README.rdoc as module documentation" do FileTest.stubs(:readable?).with("module/README.rdoc").returns(true) FileTest.stubs(:readable?).with("module/README").returns(false) File.stubs(:open).returns("readme") @parser.stubs(:parse_elements) @module.expects(:add_comment).with("readme", "module/manifests/init.pp") @parser.scan_top_level(@topcontainer, environment) end it "should prefer README.rdoc over README as module documentation" do FileTest.stubs(:readable?).with("module/README.rdoc").returns(true) FileTest.stubs(:readable?).with("module/README").returns(true) File.stubs(:open).with("module/README", "r").returns("readme") File.stubs(:open).with("module/README.rdoc", "r").returns("readme.rdoc") @parser.stubs(:parse_elements) @module.expects(:add_comment).with("readme.rdoc", "module/manifests/init.pp") @parser.scan_top_level(@topcontainer, environment) end it "should tell the container its module name" do @parser.stubs(:parse_elements) @topcontainer.expects(:module_name=).with("module") @parser.scan_top_level(@topcontainer, environment) end it "should not document our toplevel if it isn't a valid module" do @parser.stubs(:split_module).returns(nil) @topcontainer.expects(:document_self=).with(false) @parser.expects(:parse_elements).never @parser.scan_top_level(@topcontainer, environment) end it "should set the module as global if we parse the global manifests (ie __site__ module)" do @parser.stubs(:split_module).returns(RDoc::Parser::SITE) @parser.stubs(:parse_elements) @topcontainer.expects(:global=).with(true) @parser.scan_top_level(@topcontainer, environment) end it "should attach this module container to the toplevel container" do @parser.stubs(:parse_elements) @container.expects(:add_module).with(RDoc::PuppetModule, "module").returns(@module) @parser.scan_top_level(@topcontainer, environment) end it "should defer ast parsing to parse_elements for this module" do @parser.expects(:parse_elements).with(@module, @resource_type_collection) @parser.scan_top_level(@topcontainer, environment) end it "should defer plugins parsing to parse_plugins for this module" do @parser.input_file_name = "module/lib/puppet/parser/function.rb" @parser.expects(:parse_plugins).with(@module) @parser.scan_top_level(@topcontainer, environment) end end describe "when finding modules from filepath" do let(:environment) { Puppet::FileSystem.expects(:directory?).with("/path/to/modules").at_least_once.returns(true) - Puppet::Node::Environment.create(:env, ["/path/to/modules"], '') + Puppet::Node::Environment.create(:env, ["/path/to/modules"]) } it "should return the module name for modulized puppet manifests" do File.stubs(:identical?).with("/path/to/modules", "/path/to/modules").returns(true) @parser.split_module("/path/to/modules/mymodule/manifests/init.pp", environment).should == "mymodule" end it "should return for manifests not under module path" do File.stubs(:identical?).returns(false) @parser.split_module("/path/to/manifests/init.pp", environment).should == RDoc::Parser::SITE end it "should handle windows paths with drive letters", :if => Puppet.features.microsoft_windows? && Puppet.features.rdoc1? do @parser.split_module("C:/temp/init.pp", environment).should == RDoc::Parser::SITE end end describe "when parsing AST elements" do before :each do @klass = stub_everything 'klass', :file => "module/manifests/init.pp", :name => "myclass", :type => :hostclass @definition = stub_everything 'definition', :file => "module/manifests/init.pp", :type => :definition, :name => "mydef" @node = stub_everything 'node', :file => "module/manifests/init.pp", :type => :node, :name => "mynode" @resource_type_collection = resource_type_collection = Puppet::Resource::TypeCollection.new("env") @parser.instance_eval { @known_resource_types = resource_type_collection } @container = stub_everything 'container' end it "should document classes in the parsed file" do @resource_type_collection.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container) @parser.parse_elements(@container, @resource_type_collection) end it "should not document class parsed in an other file" do @klass.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container).never @parser.parse_elements(@container, @resource_type_collection) end it "should document vardefs for the main class" do @klass.stubs(:name).returns :main @resource_type_collection.add_hostclass(@klass) code = stub 'code', :is_a? => false @klass.stubs(:name).returns("") @klass.stubs(:code).returns(code) @parser.expects(:scan_for_vardef).with(@container, code) @parser.parse_elements(@container, @resource_type_collection) end it "should document definitions in the parsed file" do @resource_type_collection.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container) @parser.parse_elements(@container, @resource_type_collection) end it "should not document definitions parsed in an other file" do @definition.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container).never @parser.parse_elements(@container, @resource_type_collection) end it "should document nodes in the parsed file" do @resource_type_collection.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container) @parser.parse_elements(@container, @resource_type_collection) end it "should not document node parsed in an other file" do @node.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container).never @parser.parse_elements(@container, @resource_type_collection) end end describe "when documenting definition" do before(:each) do @define = stub_everything 'define', :arguments => [], :doc => "mydoc", :file => "file", :line => 42 @class = stub_everything 'class' @parser.stubs(:get_class_or_module).returns([@class, "mydef"]) end it "should register a RDoc method to the current container" do @class.expects(:add_method).with { |m| m.name == "mydef"} @parser.document_define("mydef", @define, @class) end it "should attach the documentation to this method" do @class.expects(:add_method).with { |m| m.comment = "mydoc" } @parser.document_define("mydef", @define, @class) end it "should produce a better error message on unhandled exception" do @class.expects(:add_method).raises(ArgumentError) lambda { @parser.document_define("mydef", @define, @class) }.should raise_error(Puppet::ParseError, /in file at line 42/) end it "should convert all definition parameter to string" do arg = stub 'arg' val = stub 'val' @define.stubs(:arguments).returns({arg => val}) arg.expects(:to_s).returns("arg") val.expects(:to_s).returns("val") @parser.document_define("mydef", @define, @class) end end describe "when documenting nodes" do before :each do @code = stub_everything 'code' @node = stub_everything 'node', :doc => "mydoc", :parent => "parent", :code => @code, :file => "file", :line => 42 @rdoc_node = stub_everything 'rdocnode' @class = stub_everything 'class' @class.stubs(:add_node).returns(@rdoc_node) end it "should add a node to the current container" do @class.expects(:add_node).with("mynode", "parent").returns(@rdoc_node) @parser.document_node("mynode", @node, @class) end it "should associate the node documentation to the rdoc node" do @rdoc_node.expects(:add_comment).with("mydoc", "file") @parser.document_node("mynode", @node, @class) end it "should scan for include and require" do @parser.expects(:scan_for_include_or_require).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should scan for variable definition" do @parser.expects(:scan_for_vardef).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should scan for resources if needed" do Puppet[:document_all] = true @parser.expects(:scan_for_resource).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should produce a better error message on unhandled exception" do @class.stubs(:add_node).raises(ArgumentError) lambda { @parser.document_node("mynode", @node, @class) }.should raise_error(Puppet::ParseError, /in file at line 42/) end end describe "when documenting classes" do before :each do @code = stub_everything 'code' @class = stub_everything 'class', :doc => "mydoc", :parent => "parent", :code => @code, :file => "file", :line => 42 @rdoc_class = stub_everything 'rdoc-class' @module = stub_everything 'class' @module.stubs(:add_class).returns(@rdoc_class) @parser.stubs(:get_class_or_module).returns([@module, "myclass"]) end it "should add a class to the current container" do @module.expects(:add_class).with(RDoc::PuppetClass, "myclass", "parent").returns(@rdoc_class) @parser.document_class("mynode", @class, @module) end it "should set the superclass" do @rdoc_class.expects(:superclass=).with("parent") @parser.document_class("mynode", @class, @module) end it "should associate the node documentation to the rdoc class" do @rdoc_class.expects(:add_comment).with("mydoc", "file") @parser.document_class("mynode", @class, @module) end it "should scan for include and require" do @parser.expects(:scan_for_include_or_require).with(@rdoc_class, @code) @parser.document_class("mynode", @class, @module) end it "should scan for resources if needed" do Puppet[:document_all] = true @parser.expects(:scan_for_resource).with(@rdoc_class, @code) @parser.document_class("mynode", @class, @module) end it "should produce a better error message on unhandled exception" do @module.stubs(:add_class).raises(ArgumentError) lambda { @parser.document_class("mynode", @class, @module) }.should raise_error(Puppet::ParseError, /in file at line 42/) end end describe "when scanning for includes and requires" do def create_stmt(name) stmt_value = stub "#{name}_value", :to_s => "myclass" Puppet::Parser::AST::Function.new( :name => name, :arguments => [stmt_value], :doc => 'mydoc' ) end before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should also scan mono-instruction code" do @class.expects(:add_include).with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, create_stmt("include")) end it "should register recursively includes to the current container" do @code.stubs(:children).returns([ create_stmt("include") ]) @class.expects(:add_include)#.with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, [@code]) end it "should register requires to the current container" do @code.stubs(:children).returns([ create_stmt("require") ]) @class.expects(:add_require).with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, [@code]) end end describe "when scanning for realized virtual resources" do def create_stmt stmt_value = stub "resource_ref", :to_s => "File[\"/tmp/a\"]" Puppet::Parser::AST::Function.new( :name => 'realize', :arguments => [stmt_value], :doc => 'mydoc' ) end before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should also scan mono-instruction code" do @class.expects(:add_realize).with { |i| i.is_a?(RDoc::Include) and i.name == "File[\"/tmp/a\"]" and i.comment == "mydoc" } @parser.scan_for_realize(@class,create_stmt) end it "should register recursively includes to the current container" do @code.stubs(:children).returns([ create_stmt ]) @class.expects(:add_realize).with { |i| i.is_a?(RDoc::Include) and i.name == "File[\"/tmp/a\"]" and i.comment == "mydoc" } @parser.scan_for_realize(@class, [@code]) end end describe "when scanning for variable definition" do before :each do @class = stub_everything 'class' @stmt = stub_everything 'stmt', :name => "myvar", :value => "myvalue", :doc => "mydoc" @stmt.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(false) @stmt.stubs(:is_a?).with(Puppet::Parser::AST::VarDef).returns(true) @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should recursively register variables to the current container" do @code.stubs(:children).returns([ @stmt ]) @class.expects(:add_constant).with { |i| i.is_a?(RDoc::Constant) and i.name == "myvar" and i.comment == "mydoc" } @parser.scan_for_vardef(@class, [ @code ]) end it "should also scan mono-instruction code" do @class.expects(:add_constant).with { |i| i.is_a?(RDoc::Constant) and i.name == "myvar" and i.comment == "mydoc" } @parser.scan_for_vardef(@class, @stmt) end end describe "when scanning for resources" do before :each do @class = stub_everything 'class' @stmt = Puppet::Parser::AST::Resource.new( :type => "File", :instances => Puppet::Parser::AST::BlockExpression.new(:children => [ Puppet::Parser::AST::ResourceInstance.new( :title => Puppet::Parser::AST::Name.new(:value => "myfile"), :parameters => Puppet::Parser::AST::BlockExpression.new(:children => []) ) ]), :doc => 'mydoc' ) @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should register a PuppetResource to the current container" do @code.stubs(:children).returns([ @stmt ]) @class.expects(:add_resource).with { |i| i.is_a?(RDoc::PuppetResource) and i.title == "myfile" and i.comment == "mydoc" } @parser.scan_for_resource(@class, [ @code ]) end it "should also scan mono-instruction code" do @class.expects(:add_resource).with { |i| i.is_a?(RDoc::PuppetResource) and i.title == "myfile" and i.comment == "mydoc" } @parser.scan_for_resource(@class, @stmt) end end describe "when parsing plugins" do before :each do @container = stub 'container' end it "should delegate parsing custom facts to parse_facts" do @parser = RDoc::Parser.new(@top_level, "module/manifests/lib/puppet/facter/test.rb", nil, Options.instance, RDoc::Stats.new) @parser.expects(:parse_fact).with(@container) @parser.parse_plugins(@container) end it "should delegate parsing plugins to parse_plugins" do @parser = RDoc::Parser.new(@top_level, "module/manifests/lib/puppet/functions/test.rb", nil, Options.instance, RDoc::Stats.new) @parser.expects(:parse_puppet_plugin).with(@container) @parser.parse_plugins(@container) end end describe "when parsing plugins" do before :each do @container = stub_everything 'container' end it "should add custom functions to the container" do File.stubs(:open).yields("# documentation module Puppet::Parser::Functions newfunction(:myfunc, :type => :rvalue) do |args| File.dirname(args[0]) end end".split("\n")) @container.expects(:add_plugin).with do |plugin| plugin.comment == "documentation\n" #and plugin.name == "myfunc" end @parser.parse_puppet_plugin(@container) end it "should add custom types to the container" do File.stubs(:open).yields("# documentation Puppet::Type.newtype(:mytype) do end".split("\n")) @container.expects(:add_plugin).with do |plugin| plugin.comment == "documentation\n" #and plugin.name == "mytype" end @parser.parse_puppet_plugin(@container) end end describe "when parsing facts" do before :each do @container = stub_everything 'container' File.stubs(:open).yields(["# documentation", "Facter.add('myfact') do", "confine :kernel => :linux", "end"]) end it "should add facts to the container" do @container.expects(:add_fact).with do |fact| fact.comment == "documentation\n" and fact.name == "myfact" end @parser.parse_fact(@container) end it "should add confine to the parsed facts" do ourfact = nil @container.expects(:add_fact).with do |fact| ourfact = fact true end @parser.parse_fact(@container) ourfact.confine.should == { :type => "kernel", :value => ":linux" } end end end