diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb index 10adafd4e..826ef6110 100644 --- a/lib/puppet/resource/type.rb +++ b/lib/puppet/resource/type.rb @@ -1,408 +1,406 @@ require 'puppet/parser' require 'puppet/util/warnings' require 'puppet/util/errors' require 'puppet/util/inline_docs' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/block_expression' # Puppet::Resource::Type represents nodes, classes and defined types. # # It has a standard format for external consumption, usable from the # resource_type indirection via rest and the resource_type face. See the # {file:api_docs/http_resource_type.md#Schema resource type schema # description}. # # @api public class Puppet::Resource::Type Puppet::ResourceType = self include Puppet::Util::InlineDocs include Puppet::Util::Warnings include Puppet::Util::Errors RESOURCE_KINDS = [:hostclass, :node, :definition] # Map the names used in our documentation to the names used internally RESOURCE_KINDS_TO_EXTERNAL_NAMES = { :hostclass => "class", :node => "node", :definition => "defined_type", } RESOURCE_EXTERNAL_NAMES_TO_KINDS = RESOURCE_KINDS_TO_EXTERNAL_NAMES.invert - attr_accessor :file, :line, :doc, :code, :ruby_code, :parent, :resource_type_collection + attr_accessor :file, :line, :doc, :code, :parent, :resource_type_collection attr_reader :namespace, :arguments, :behaves_like, :module_name # Map from argument (aka parameter) names to Puppet Type # @return [Hash :parser def self.from_data_hash(data) name = data.delete('name') or raise ArgumentError, "Resource Type names must be specified" kind = data.delete('kind') || "definition" unless type = RESOURCE_EXTERNAL_NAMES_TO_KINDS[kind] raise ArgumentError, "Unsupported resource kind '#{kind}'" end data = data.inject({}) { |result, ary| result[ary[0].intern] = ary[1]; result } # External documentation uses "parameters" but the internal name # is "arguments" data[:arguments] = data.delete(:parameters) new(type, name, data) end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end def to_data_hash data = [:doc, :line, :file, :parent].inject({}) do |hash, param| next hash unless (value = self.send(param)) and (value != "") hash[param.to_s] = value hash end # External documentation uses "parameters" but the internal name # is "arguments" data['parameters'] = arguments.dup unless arguments.empty? data['name'] = name unless RESOURCE_KINDS_TO_EXTERNAL_NAMES.has_key?(type) raise ArgumentError, "Unsupported resource kind '#{type}'" end data['kind'] = RESOURCE_KINDS_TO_EXTERNAL_NAMES[type] data end # Are we a child of the passed class? Do a recursive search up our # parentage tree to figure it out. def child_of?(klass) return false unless parent return(klass == parent_type ? true : parent_type.child_of?(klass)) end # Now evaluate the code associated with this class or definition. def evaluate_code(resource) static_parent = evaluate_parent_type(resource) scope = static_parent || resource.scope scope = scope.newscope(:namespace => namespace, :source => self, :resource => resource) unless resource.title == :main scope.compiler.add_class(name) unless definition? set_resource_parameters(resource, scope) resource.add_edge_to_stage if code if @match # Only bother setting up the ephemeral scope if there are match variables to add into it begin elevel = scope.ephemeral_level scope.ephemeral_from(@match, file, line) code.safeevaluate(scope) ensure scope.unset_ephemeral_var(elevel) end else code.safeevaluate(scope) end end - - evaluate_ruby_code(resource, scope) if ruby_code end def initialize(type, name, options = {}) @type = type.to_s.downcase.to_sym raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_KINDS.include?(@type) name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName) set_name_and_namespace(name) [:code, :doc, :line, :file, :parent].each do |param| next unless value = options[param] send(param.to_s + "=", value) end set_arguments(options[:arguments]) set_argument_types(options[:argument_types]) @match = nil @module_name = options[:module_name] end # This is only used for node names, and really only when the node name # is a regexp. def match(string) return string.to_s.downcase == name unless name_is_regex? @match = @name.match(string) end # Add code from a new instance to our code. def merge(other) fail "#{name} is not a class; cannot add code to it" unless type == :hostclass fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass fail "Cannot have code outside of a class/node/define because 'freeze_main' is enabled" if name == "" and Puppet.settings[:freeze_main] if parent and other.parent and parent != other.parent fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{other.parent})" end # We know they're either equal or only one is set, so keep whichever parent is specified. self.parent ||= other.parent if other.doc self.doc ||= "" self.doc += other.doc end # This might just be an empty, stub class. return unless other.code unless self.code self.code = other.code return end self.code = Puppet::Parser::ParserFactory.code_merger.concatenate([self, other]) # self.code = self.code.sequence_with(other.code) end # Make an instance of the resource type, and place it in the catalog # if it isn't in the catalog already. This is only possible for # classes and nodes. No parameters are be supplied--if this is a # parameterized class, then all parameters take on their default # values. def ensure_in_catalog(scope, parameters=nil) type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" resource_type = type == :hostclass ? :class : :node # Do nothing if the resource already exists; this makes sure we don't # get multiple copies of the class resource, which helps provide the # singleton nature of classes. # we should not do this for classes with parameters # if parameters are passed, we should still try to create the resource # even if it exists so that we can fail # this prevents us from being able to combine param classes with include if resource = scope.catalog.resource(resource_type, name) and !parameters return resource end resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self) assign_parameter_values(parameters, resource) instantiate_resource(scope, resource) scope.compiler.add_resource(scope, resource) resource end def instantiate_resource(scope, resource) # Make sure our parent class has been evaluated, if we have one. if parent && !scope.catalog.resource(resource.type, parent) parent_type(scope).ensure_in_catalog(scope) end if ['Class', 'Node'].include? resource.type scope.catalog.tag(*resource.tags) end end def name return @name unless @name.is_a?(Regexp) @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') end def name_is_regex? @name.is_a?(Regexp) end def assign_parameter_values(parameters, resource) return unless parameters # It'd be nice to assign default parameter values here, # but we can't because they often rely on local variables # created during set_resource_parameters. parameters.each do |name, value| resource.set_parameter name, value end end # MQR TODO: # # The change(s) introduced by the fix for #4270 are mostly silly & should be # removed, though we didn't realize it at the time. If it can be established/ # ensured that nodes never call parent_type and that resource_types are always # (as they should be) members of exactly one resource_type_collection the # following method could / should be replaced with: # # def parent_type # @parent_type ||= parent && ( # resource_type_collection.find_or_load([name],parent,type.to_sym) || # fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{resource_type_collection.environment}" # ) # end # # ...and then the rest of the changes around passing in scope reverted. # def parent_type(scope = nil) return nil unless parent unless @parent_type raise "Must pass scope to parent_type when called first time" unless scope unless @parent_type = scope.environment.known_resource_types.send("find_#{type}", [name], parent) fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}" end end @parent_type end # Set any arguments passed by the resource as variables in the scope. def set_resource_parameters(resource, scope) set = {} resource.to_hash.each do |param, value| param = param.to_sym fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param) exceptwrap { scope[param.to_s] = value } set[param] = true end if @type == :hostclass scope["title"] = resource.title.to_s.downcase unless set.include? :title scope["name"] = resource.name.to_s.downcase unless set.include? :name else scope["title"] = resource.title unless set.include? :title scope["name"] = resource.name unless set.include? :name end scope["module_name"] = module_name if module_name and ! set.include? :module_name if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name) scope["caller_module_name"] = caller_name end scope.class_set(self.name,scope) if hostclass? or node? # Evaluate the default parameters, now that all other variables are set default_params = resource.set_default_parameters(scope) default_params.each { |param| scope[param] = resource[param] } # This has to come after the above parameters so that default values # can use their values resource.validate_complete end # Check whether a given argument is valid. def valid_parameter?(param) param = param.to_s return true if param == "name" return true if Puppet::Type.metaparam?(param) return false unless defined?(@arguments) return(arguments.include?(param) ? true : false) end def set_arguments(arguments) @arguments = {} return if arguments.nil? arguments.each do |arg, default| arg = arg.to_s warn_if_metaparam(arg, default) @arguments[arg] = default end end # Sets the argument name to Puppet Type hash used for type checking. # Names must correspond to available arguments (they must be defined first). # Arguments not mentioned will not be type-checked. Only supported when parser == "future" # def set_argument_types(name_to_type_hash) @argument_types = {} # Stop here if not running under future parser, the rest requires pops to be initialized # and that the type system is available return unless Puppet[:parser] == 'future' && name_to_type_hash name_to_type_hash.each do |name, t| # catch internal errors unless @arguments.include?(name) raise Puppet::DevError, "Parameter '#{name}' is given a type, but is not a valid parameter." end unless t.is_a? Puppet::Pops::Types::PAnyType raise Puppet::DevError, "Parameter '#{name}' is given a type that is not a Puppet Type, got #{t.class}" end @argument_types[name] = t end end private def convert_from_ast(name) value = name.value if value.is_a?(Puppet::Parser::AST::Regex) name = value.value else name = value end end def evaluate_parent_type(resource) return unless klass = parent_type(resource.scope) and parent_resource = resource.scope.compiler.catalog.resource(:class, klass.name) || resource.scope.compiler.catalog.resource(:node, klass.name) parent_resource.evaluate unless parent_resource.evaluated? parent_scope(resource.scope, klass) end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end def parent_scope(scope, klass) scope.class_scope(klass) || raise(Puppet::DevError, "Could not find scope for #{klass.name}") end def set_name_and_namespace(name) if name.is_a?(Regexp) @name = name @namespace = "" else @name = name.to_s.downcase # Note we're doing something somewhat weird here -- we're setting # the class's namespace to its fully qualified name. This means # anything inside that class starts looking in that namespace first. @namespace, ignored_shortname = @type == :hostclass ? [@name, ''] : namesplit(@name) end end def warn_if_metaparam(param, default) return unless Puppet::Type.metaparamclass(param) if default warnonce "#{param} is a metaparam; this value will inherit to all contained resources in the #{self.name} definition" else raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition" end end end diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index 22fd3532a..f10ecada5 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -1,199 +1,199 @@ # Bundler and rubygems maintain a set of directories from which to # load gems. If Bundler is loaded, let it determine what can be # loaded. If it's not loaded, then use rubygems. But do this before # loading any puppet code, so that our gem loading system is sane. if not defined? ::Bundler begin require 'rubygems' rescue LoadError end end require 'puppet' require 'puppet/util' require "puppet/util/plugins" require "puppet/util/rubygems" require "puppet/util/limits" require 'puppet/util/colors' module Puppet module Util # This is the main entry point for all puppet applications / faces; it # is basically where the bootstrapping process / lifecycle of an app # begins. class CommandLine include Puppet::Util::Limits - OPTION_OR_MANIFEST_FILE = /^-|\.pp$|\.rb$/ + OPTION_OR_MANIFEST_FILE = /^-|\.pp$/ # @param zero [String] the name of the executable # @param argv [Array] the arguments passed on the command line # @param stdin [IO] (unused) def initialize(zero = $0, argv = ARGV, stdin = STDIN) @command = File.basename(zero, '.rb') @argv = argv Puppet::Plugins.on_commandline_initialization(:command_line_object => self) end # @return [String] name of the subcommand is being executed # @api public def subcommand_name return @command if @command != 'puppet' if @argv.first =~ OPTION_OR_MANIFEST_FILE nil else @argv.first end end # @return [Array] the command line arguments being passed to the subcommand # @api public def args return @argv if @command != 'puppet' if subcommand_name.nil? @argv else @argv[1..-1] end end # @api private # @deprecated def self.available_subcommands Puppet.deprecation_warning('Puppet::Util::CommandLine.available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.') Puppet::Application.available_application_names end # available_subcommands was previously an instance method, not a class # method, and we have an unknown number of user-implemented applications # that depend on that behaviour. Forwarding allows us to preserve a # backward compatible API. --daniel 2011-04-11 # @api private # @deprecated def available_subcommands Puppet.deprecation_warning('Puppet::Util::CommandLine#available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.') Puppet::Application.available_application_names end # Run the puppet subcommand. If the subcommand is determined to be an # external executable, this method will never return and the current # process will be replaced via {Kernel#exec}. # # @return [void] def execute Puppet::Util.exit_on_fail("initialize global default settings") do Puppet.initialize_settings(args) end setpriority(Puppet[:priority]) find_subcommand.run end # @api private def external_subcommand Puppet::Util.which("puppet-#{subcommand_name}") end private def find_subcommand if subcommand_name.nil? NilSubcommand.new(self) elsif Puppet::Application.available_application_names.include?(subcommand_name) ApplicationSubcommand.new(subcommand_name, self) elsif path_to_subcommand = external_subcommand ExternalSubcommand.new(path_to_subcommand, self) else UnknownSubcommand.new(subcommand_name, self) end end # @api private class ApplicationSubcommand def initialize(subcommand_name, command_line) @subcommand_name = subcommand_name @command_line = command_line end def run # For most applications, we want to be able to load code from the modulepath, # such as apply, describe, resource, and faces. # For agent, we only want to load pluginsync'ed code from libdir. # For master, we shouldn't ever be loading per-enviroment code into the master's # ruby process, but that requires fixing (#17210, #12173, #8750). So for now # we try to restrict to only code that can be autoloaded from the node's # environment. # PUP-2114 - at this point in the bootstrapping process we do not # have an appropriate application-wide current_environment set. # If we cannot find the configured environment, which may not exist, # we do not attempt to add plugin directories to the load path. # if @subcommand_name != 'master' and @subcommand_name != 'agent' if configured_environment = Puppet.lookup(:environments).get(Puppet[:environment]) configured_environment.each_plugin_directory do |dir| $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) end end end app = Puppet::Application.find(@subcommand_name).new(@command_line) Puppet::Plugins.on_application_initialization(:application_object => @command_line) app.run end end # @api private class ExternalSubcommand def initialize(path_to_subcommand, command_line) @path_to_subcommand = path_to_subcommand @command_line = command_line end def run Kernel.exec(@path_to_subcommand, *@command_line.args) end end # @api private class NilSubcommand include Puppet::Util::Colors def initialize(command_line) @command_line = command_line end def run args = @command_line.args if args.include? "--version" or args.include? "-V" puts Puppet.version elsif @command_line.subcommand_name.nil? && args.count > 0 # If the subcommand is truly nil and there is an arg, it's an option; print out the invalid option message puts colorize(:hred, "Error: Could not parse application options: invalid option: #{args[0]}") exit 1 else puts "See 'puppet help' for help on available puppet subcommands" end end end # @api private class UnknownSubcommand < NilSubcommand def initialize(subcommand_name, command_line) @subcommand_name = subcommand_name super(command_line) end def run puts colorize(:hred, "Error: Unknown Puppet subcommand '#{@subcommand_name}'") super exit 1 end end end end end diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index 2315ae4de..dade96a4b 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -1,766 +1,766 @@ #! /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| + [:code, :doc, :line, :file, :resource_type_collection].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, []) @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 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/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb index 9eb61b077..cb6ec7a48 100755 --- a/spec/unit/util/command_line_spec.rb +++ b/spec/unit/util/command_line_spec.rb @@ -1,192 +1,185 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' require 'puppet/util/command_line' describe Puppet::Util::CommandLine do include PuppetSpec::Files context "#initialize" do it "should pull off the first argument if it looks like a subcommand" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ client --help whatever.pp }) command_line.subcommand_name.should == "client" command_line.args.should == %w{ --help whatever.pp } end it "should return nil if the first argument looks like a .pp file" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ whatever.pp }) command_line.subcommand_name.should == nil command_line.args.should == %w{ whatever.pp } end - it "should return nil if the first argument looks like a .rb file" do - command_line = Puppet::Util::CommandLine.new("puppet", %w{ whatever.rb }) - - command_line.subcommand_name.should == nil - command_line.args.should == %w{ whatever.rb } - end - it "should return nil if the first argument looks like a flag" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --debug }) command_line.subcommand_name.should == nil command_line.args.should == %w{ --debug } end it "should return nil if the first argument is -" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ - }) command_line.subcommand_name.should == nil command_line.args.should == %w{ - } end it "should return nil if the first argument is --help" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --help }) command_line.subcommand_name.should == nil end it "should return nil if there are no arguments" do command_line = Puppet::Util::CommandLine.new("puppet", []) command_line.subcommand_name.should == nil command_line.args.should == [] end it "should pick up changes to the array of arguments" do args = %w{subcommand} command_line = Puppet::Util::CommandLine.new("puppet", args) args[0] = 'different_subcommand' command_line.subcommand_name.should == 'different_subcommand' end end context "#execute" do %w{--version -V}.each do |arg| it "should print the version and exit if #{arg} is given" do expect do described_class.new("puppet", [arg]).execute end.to have_printed(/^#{Regexp.escape(Puppet.version)}$/) end end end describe "when dealing with puppet commands" do it "should return the executable name if it is not puppet" do command_line = Puppet::Util::CommandLine.new("puppetmasterd", []) command_line.subcommand_name.should == "puppetmasterd" end describe "when the subcommand is not implemented" do it "should find and invoke an executable with a hyphenated name" do commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) Puppet::Util.expects(:which).with('puppet-whatever'). returns('/dev/null/puppet-whatever') Kernel.expects(:exec).with('/dev/null/puppet-whatever', 'argument') commandline.execute end describe "and an external implementation cannot be found" do before :each do Puppet::Util::CommandLine::UnknownSubcommand.any_instance.stubs(:console_has_color?).returns false end it "should abort and show the usage message" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/Unknown Puppet subcommand 'whatever'/).and_exit_with(1) end it "should abort and show the help message" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/See 'puppet help' for help on available puppet subcommands/).and_exit_with(1) end %w{--version -V}.each do |arg| it "should abort and display #{arg} information" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg]) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(%r[^#{Regexp.escape(Puppet.version)}$]).and_exit_with(1) end end end end describe 'when loading commands' do it "should deprecate the available_subcommands instance method" do Puppet::Application.expects(:available_application_names) Puppet.expects(:deprecation_warning).with("Puppet::Util::CommandLine#available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.") command_line = Puppet::Util::CommandLine.new("foo", %w{ client --help whatever.pp }) command_line.available_subcommands end it "should deprecate the available_subcommands class method" do Puppet::Application.expects(:available_application_names) Puppet.expects(:deprecation_warning).with("Puppet::Util::CommandLine.available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.") Puppet::Util::CommandLine.available_subcommands end end describe 'when setting process priority' do let(:command_line) do Puppet::Util::CommandLine.new("puppet", %w{ agent }) end before :each do Puppet::Util::CommandLine::ApplicationSubcommand.any_instance.stubs(:run) end it 'should never set priority by default' do Process.expects(:setpriority).never command_line.execute end it 'should lower the process priority if one has been specified' do Puppet[:priority] = 10 Process.expects(:setpriority).with(0, Process.pid, 10) command_line.execute end it 'should warn if trying to raise priority, but not privileged user' do Puppet[:priority] = -10 Process.expects(:setpriority).raises(Errno::EACCES, 'Permission denied') Puppet.expects(:warning).with("Failed to set process priority to '-10'") command_line.execute end it "should warn if the platform doesn't support `Process.setpriority`" do Puppet[:priority] = 15 Process.expects(:setpriority).raises(NotImplementedError, 'NotImplementedError: setpriority() function is unimplemented on this machine') Puppet.expects(:warning).with("Failed to set process priority to '15'") command_line.execute end end end end