diff --git a/lib/puppet/functions/defined.rb b/lib/puppet/functions/defined.rb index 37d8364cd..3af50197a 100644 --- a/lib/puppet/functions/defined.rb +++ b/lib/puppet/functions/defined.rb @@ -1,130 +1,132 @@ # Determines whether # a given class or resource type is defined. This function can also determine whether a # specific resource has been declared, or whether a variable has been assigned a value # (including undef...as opposed to never having been assigned anything). Returns true # or false. Accepts class names, type names, resource references, and variable # reference strings of the form '$name'. When more than one argument is # supplied, defined() returns true if any are defined. # # The `defined` function checks both native and defined types, including types # provided as plugins via modules. Types and classes are both checked using their names: # # defined("file") # defined("customtype") # defined("foo") # defined("foo::bar") # defined('$name') # # Resource declarations are checked using resource references, e.g. # `defined( File['/tmp/myfile'] )`, or `defined( Class[myclass] )`. # Checking whether a given resource # has been declared is, unfortunately, dependent on the evaluation order of # the configuration, and the following code will not work: # # if defined(File['/tmp/foo']) { # notify { "This configuration includes the /tmp/foo file.":} # } # file { "/tmp/foo": # ensure => present, # } # # However, this order requirement refers to evaluation order only, and ordering of # resources in the configuration graph (e.g. with `before` or `require`) does not # affect the behavior of `defined`. # # You may also search using types: # # defined(Resource['file','/some/file']) # defined(File['/some/file']) # defined(Class['foo']) # -# The `defined` function does not answer if data types (e.g. `Integer`) are defined. +# The `defined` function does not answer if data types (e.g. `Integer`) are defined. If +# given the string 'integer' the result is false, and if given a non CatalogEntry type, +# an error is raised. # # The rules for asking for undef, empty strings, and the main class are different from 3.x # (non future parser) and 4.x (with future parser or in Puppet 4.0.0 and later): # # defined('') # 3.x => true, 4.x => false # defined(undef) # 3.x => true, 4.x => error # defined('main') # 3.x => false, 4.x => true # # With the future parser, it is also possible to ask specifically if a name is # a resource type (built in or defined), or a class, by giving its type: # # defined(Type[Class['foo']]) # defined(Type[Resource['foo']]) # # Which is different from asking: # # defined('foo') # # Since the later returns true if 'foo' is either a class, a built-in resource type, or a user defined # resource type, and a specific request like `Type[Class['foo']]` only returns true if `'foo'` is a class. # # @since 2.7.0 # @since 3.6.0 variable reference and future parser types") # @since 3.8.1 type specific requests with future parser # Puppet::Functions.create_function(:'defined', Puppet::Functions::InternalFunction) do ARG_TYPE = 'Variant[String,Type[CatalogEntry], Type[Type[CatalogEntry]]]' dispatch :is_defined do scope_param - optional_repeated_param ARG_TYPE, 'additional_args' + required_repeated_param ARG_TYPE, 'additional_args' end def is_defined(scope, *vals) vals.any? do |val| case val when String - if m = /^\$(.+)$/.match(val) - scope.exist?(m[1]) + if val =~ /^\$(.+)$/ + scope.exist?($1) else case val when '' next nil when 'main' # Find the main class (known as ''), it does not have to be in the catalog - scope.find_hostclass('') # scope.find_hostclass('') + scope.find_hostclass('') else # Find a resource type, definition or class definition scope.find_resource_type(val) || scope.find_definition(val) || scope.find_hostclass(val) #scope.compiler.findresource(:class, val) end end when Puppet::Resource # Find instance of given resource type and title that is in the catalog scope.compiler.findresource(val.type, val.title) when Puppet::Pops::Types::PResourceType - raise ArgumentError, "The given resource type is a reference to all kind of types" if val.type_name.nil? + raise ArgumentError, 'The given resource type is a reference to all kind of types' if val.type_name.nil? if val.title.nil? scope.find_builtin_resource_type(val.type_name) || scope.find_definition(val.type_name) else scope.compiler.findresource(val.type_name, val.title) end when Puppet::Pops::Types::PHostClassType - raise ArgumentError, "The given class type is a reference to all classes" if val.class_name.nil? + raise ArgumentError, 'The given class type is a reference to all classes' if val.class_name.nil? scope.compiler.findresource(:class, val.class_name) when Puppet::Pops::Types::PType case val.type when Puppet::Pops::Types::PResourceType # It is most reasonable to take Type[File] and Type[File[foo]] to mean the same as if not wrapped in a Type # Since the difference between File and File[foo] already captures the distinction of type vs instance. is_defined(scope, val.type) when Puppet::Pops::Types::PHostClassType # Interpreted as asking if a class (and nothing else) is defined without having to be included in the catalog # (this is the same as asking for just the class' name, but with the added certainty that it cannot be a defined type. # - raise ArgumentError, "The given class type is a reference to all classes" if val.type.class_name.nil? + raise ArgumentError, 'The given class type is a reference to all classes' if val.type.class_name.nil? scope.find_hostclass(val.type.class_name) end else raise ArgumentError, "Invalid argument of type '#{val.class}' to 'defined'" end end end end diff --git a/lib/puppet/parser/functions/defined.rb b/lib/puppet/parser/functions/defined.rb index 20adccdf0..37a3b6918 100644 --- a/lib/puppet/parser/functions/defined.rb +++ b/lib/puppet/parser/functions/defined.rb @@ -1,95 +1,98 @@ # Test whether a given class or definition is defined Puppet::Parser::Functions::newfunction(:defined, :type => :rvalue, :arity => -2, :doc => "Determine whether a given class or resource type is defined. This function can also determine whether a specific resource has been declared, or whether a variable has been assigned a value (including undef...as opposed to never having been assigned anything). Returns true or false. Accepts class names, type names, resource references, and variable reference strings of the form '$name'. When more than one argument is supplied, defined() returns true if any are defined. The `defined` function checks both native and defined types, including types provided as plugins via modules. Types and classes are both checked using their names: defined(\"file\") defined(\"customtype\") defined(\"foo\") defined(\"foo::bar\") defined(\'$name\') Resource declarations are checked using resource references, e.g. `defined( File['/tmp/myfile'] )`. Checking whether a given resource has been declared is, unfortunately, dependent on the parse order of the configuration, and the following code will not work: if defined(File['/tmp/foo']) { notify { \"This configuration includes the /tmp/foo file.\":} } file { \"/tmp/foo\": ensure => present, } However, this order requirement refers to parse order only, and ordering of resources in the configuration graph (e.g. with `before` or `require`) does not affect the behavior of `defined`. If the future parser is in effect, you may also search using types: defined(Resource[\'file\',\'/some/file\']) defined(File[\'/some/file\']) defined(Class[\'foo\']) - When used with the future parser (4.x), the `defined` function does not answer if data - types (e.g. `Integer`) are defined, and the rules for asking for undef, empty strings, and - the main class are different: + The `defined` function does not answer if 4.x data types (e.g. `Integer`) are defined. If + given the string 'integer' the result is false, and if given a non CatalogEntry type, + an error is raised. + + The rules for asking for undef, empty strings, and the main class are different from 3.x + (non future parser) and 4.x (with future parser or in Puppet 4.0.0 and later): defined('') # 3.x => true, 4.x => false defined(undef) # 3.x => true, 4.x => error defined('main') # 3.x => false, 4.x => true With the future parser, it is also possible to ask specifically if a name is a resource type (built in or defined), or a class, by giving its type: defined(Type[Class['foo']]) defined(Type[Resource['foo']]) Which is different from asking: defined('foo') Since the later returns true if 'foo' is either a class, a built-in resource type, or a user defined resource type, and a specific request like `Type[Class['foo']]` only returns true if `'foo'` is a class. - Since 2.7.0 - Since 3.6.0 variable reference and future parser types - Since 3.8.1 type specific requests with future parser") do |vals| vals = [vals] unless vals.is_a?(Array) vals.any? do |val| case val when String if m = /^\$(.+)$/.match(val) exist?(m[1]) else find_resource_type(val) or find_definition(val) or find_hostclass(val) end when Puppet::Resource compiler.findresource(val.type, val.title) else if Puppet.future_parser? case val when Puppet::Pops::Types::PResourceType raise ArgumentError, "The given resource type is a reference to all kind of types" if val.type_name.nil? if val.title.nil? find_builtin_resource_type(val.type_name) || find_definition(val.type_name) else compiler.findresource(val.type_name, val.title) end when Puppet::Pops::Types::PHostClassType raise ArgumentError, "The given class type is a reference to all classes" if val.class_name.nil? find_hostclass(val.class_name) end else raise ArgumentError, "Invalid argument of type '#{val.class}' to 'defined'" end end end end diff --git a/lib/puppet/pops/functions/dispatch.rb b/lib/puppet/pops/functions/dispatch.rb index 0a3111898..8a5eafa56 100644 --- a/lib/puppet/pops/functions/dispatch.rb +++ b/lib/puppet/pops/functions/dispatch.rb @@ -1,85 +1,85 @@ # Defines a connection between a implementation method and the signature that # the method will handle. # # This interface should not be used directly. Instead dispatches should be # constructed using the DSL defined in {Puppet::Functions}. # # @api private class Puppet::Pops::Functions::Dispatch < Puppet::Pops::Evaluator::CallableSignature # @api public attr_reader :type # TODO: refactor to parameter_names since that makes it API attr_reader :param_names attr_reader :injections # Describes how arguments are woven if there are injections, a regular argument is a given arg index, an array # an injection description. # attr_reader :weaving # @api public attr_reader :block_name # @api private def initialize(type, method_name, param_names, block_name, injections, weaving, last_captures) @type = type @method_name = method_name @param_names = param_names || [] @block_name = block_name @injections = injections || [] @weaving = weaving @last_captures = last_captures end # @api private def parameter_names @param_names end # @api private def last_captures_rest? !! @last_captures end # @api private def invoke(instance, calling_scope, args, &block) instance.send(@method_name, *weave(calling_scope, args), &block) end # @api private def weave(scope, args) # no need to weave if there are no injections if @injections.empty? args else injector = nil # lazy lookup of injector Puppet.lookup(:injector) new_args = [] @weaving.each do |knit| if knit.is_a?(Array) injection_data = @injections[knit[0]] new_args << case injection_data[3] when :dispatcher_internal # currently only supports :scope injection scope when :producer injector ||= Puppet.lookup(:injector) injector.lookup_producer(scope, injection_data[0], injection_data[2]) else injector ||= Puppet.lookup(:injector) injector.lookup(scope, injection_data[0], injection_data[2]) end else # Careful so no new nil arguments are added since they would override default # parameter values in the received if knit < 0 - idx = -knit -1 + idx = -knit - 1 new_args += args[idx..-1] if idx < args.size else new_args << args[knit] if knit < args.size end end end new_args end end end diff --git a/spec/unit/functions/defined_spec.rb b/spec/unit/functions/defined_spec.rb index caf356168..590749c4c 100755 --- a/spec/unit/functions/defined_spec.rb +++ b/spec/unit/functions/defined_spec.rb @@ -1,291 +1,291 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe "the 'defined' function" do after(:all) { Puppet::Pops::Loaders.clear } # This loads the function once and makes it easy to call it # It does not matter that it is not bound to the env used later since the function # looks up everything via the scope that is given to it. # The individual tests needs to have a fresh env/catalog set up # let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) } let(:func) { loaders.puppet_system_loader.load(:function, 'defined') } before :each do # This is only for the 4.x version of the defined function Puppet[:parser] = 'future' # A fresh environment is needed for each test since tests creates types and resources environment = Puppet::Node::Environment.create(:testing, []) - @node = Puppet::Node.new("yaynode", :environment => environment) + @node = Puppet::Node.new('yaynode', :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) 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 newresource(type, title) resource = Puppet::Resource.new(type, title) @compiler.add_resource(@scope, resource) resource end #--- CLASS # - context "can determine if a class" do - context "is defined" do + context 'can determine if a class' do + context 'is defined' do - it "by using the class name in string form" do + it 'by using the class name in string form' do newclass 'yayness' - expect(func.call(@scope, "yayness")).to be_true + expect(func.call(@scope, 'yayness')).to be_true end - it "by using a Type[Class[name]] type reference" do + it 'by using a Type[Class[name]] type reference' do name = 'yayness' newclass name class_type = Puppet::Pops::Types::TypeFactory.host_class(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(class_type) expect(func.call(@scope, type_type)).to be_true end end - context "is not defined" do - it "by using the class name in string form" do - expect(func.call(@scope, "yayness")).to be_false + context 'is not defined' do + it 'by using the class name in string form' do + expect(func.call(@scope, 'yayness')).to be_false end - it "even if there is a define, by using a Type[Class[name]] type reference" do + it 'even if there is a define, by using a Type[Class[name]] type reference' do name = 'yayness' newdefine name class_type = Puppet::Pops::Types::TypeFactory.host_class(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(class_type) expect(func.call(@scope, type_type)).to be_false end end - context "is defined and realized" do - it "by using a Class[name] reference" do - name = "cowabunga" + context 'is defined and realized' do + it 'by using a Class[name] reference' do + name = 'cowabunga' newclass name newresource(:class, name) class_type = Puppet::Pops::Types::TypeFactory.host_class(name) expect(func.call(@scope, class_type)).to be_true end end - context "is not realized" do - it "(although defined) by using a Class[name] reference" do - name = "cowabunga" + context 'is not realized' do + it '(although defined) by using a Class[name] reference' do + name = 'cowabunga' newclass name class_type = Puppet::Pops::Types::TypeFactory.host_class(name) expect(func.call(@scope, class_type)).to be_false end - it "(and not defined) by using a Class[name] reference" do - name = "cowabunga" + it '(and not defined) by using a Class[name] reference' do + name = 'cowabunga' class_type = Puppet::Pops::Types::TypeFactory.host_class(name) expect(func.call(@scope, class_type)).to be_false end end end #---RESOURCE TYPE # - context "can determine if a resource type" do - context "is defined" do + context 'can determine if a resource type' do + context 'is defined' do - it "by using the type name (of a built in type) in string form" do - expect(func.call(@scope, "file")).to be_true + it 'by using the type name (of a built in type) in string form' do + expect(func.call(@scope, 'file')).to be_true end - it "by using the type name (of a resource type) in string form" do + it 'by using the type name (of a resource type) in string form' do newdefine 'yayness' - expect(func.call(@scope, "yayness")).to be_true + expect(func.call(@scope, 'yayness')).to be_true end - it "by using a File type reference (built in type)" do + it 'by using a File type reference (built in type)' do resource_type = Puppet::Pops::Types::TypeFactory.resource('file') type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_true end - it "by using a Type[File] type reference" do + it 'by using a Type[File] type reference' do resource_type = Puppet::Pops::Types::TypeFactory.resource('file') type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_true end - it "by using a Resource[T] type reference (defined type)" do + it 'by using a Resource[T] type reference (defined type)' do name = 'yayness' newdefine name resource_type = Puppet::Pops::Types::TypeFactory.resource(name) expect(func.call(@scope, resource_type)).to be_true end - it "by using a Type[Resource[T]] type reference (defined type)" do + it 'by using a Type[Resource[T]] type reference (defined type)' do name = 'yayness' newdefine name resource_type = Puppet::Pops::Types::TypeFactory.resource(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_true end end - context "is not defined" do - it "by using the resource name in string form" do - expect(func.call(@scope, "notatype")).to be_false + context 'is not defined' do + it 'by using the resource name in string form' do + expect(func.call(@scope, 'notatype')).to be_false end - it "even if there is a class with the same name, by using a Type[Resource[T]] type reference" do + it 'even if there is a class with the same name, by using a Type[Resource[T]] type reference' do name = 'yayness' newclass name resource_type = Puppet::Pops::Types::TypeFactory.resource(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_false end end - context "is defined and instance realized" do - it "by using a Resource[T, title] reference for a built in type" do + context 'is defined and instance realized' do + it 'by using a Resource[T, title] reference for a built in type' do type_name = 'file' title = '/tmp/myfile' newdefine type_name newresource(type_name, title) class_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, class_type)).to be_true end - it "by using a Resource[T, title] reference for a defined type" do + it 'by using a Resource[T, title] reference for a defined type' do type_name = 'meme' title = 'cowabunga' newdefine type_name newresource(type_name, title) class_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, class_type)).to be_true end end - context "is not realized" do - it "(although defined) by using a Resource[T, title] reference or Type[Resource[T, title]] reference" do + context 'is not realized' do + it '(although defined) by using a Resource[T, title] reference or Type[Resource[T, title]] reference' do type_name = 'meme' - title = "cowabunga" + title = 'cowabunga' newdefine type_name resource_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, resource_type)).to be_false type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_false end - it "(and not defined) by using a Resource[T, title] reference or Type[Resource[T, title]] reference" do + it '(and not defined) by using a Resource[T, title] reference or Type[Resource[T, title]] reference' do type_name = 'meme' - title = "cowabunga" + title = 'cowabunga' resource_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, resource_type)).to be_false type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_false end end end #---VARIABLES # - context "can determine if a variable" do - context "is defined" do - it "by giving the variable in string form" do + context 'can determine if a variable' do + context 'is defined' do + it 'by giving the variable in string form' do @scope['x'] = 'something' expect(func.call(@scope, '$x')).to be_true end - it "by giving a :: prefixed variable in string form" do + it 'by giving a :: prefixed variable in string form' do @compiler.topscope['x'] = 'something' expect(func.call(@scope, '$::x')).to be_true end - it "by giving a numeric variable in string form (when there is a match scope)" do + it 'by giving a numeric variable in string form (when there is a match scope)' do # with no match scope, there are no numeric variables defined expect(func.call(@scope, '$0')).to be_false expect(func.call(@scope, '$42')).to be_false - pattern = Regexp.new(".*") - @scope.new_match_scope(pattern.match("anything")) + pattern = Regexp.new('.*') + @scope.new_match_scope(pattern.match('anything')) # with a match scope, all numeric variables are set (the match defines if they have a value or not, but they are defined) # even if their value is undef. expect(func.call(@scope, '$0')).to be_true expect(func.call(@scope, '$42')).to be_true end end - context "is undefined" do - it "by giving a :: prefixed or regular variable in string form" do + context 'is undefined' do + it 'by giving a :: prefixed or regular variable in string form' do expect(func.call(@scope, '$x')).to be_false expect(func.call(@scope, '$::x')).to be_false end end end - context "has any? semantics when given multiple arguments" do - it "and one of the names is a defined user defined type" do - newdefine "yayness" - expect(func.call(@scope, "meh", "yayness", "booness")).to be_true + context 'has any? semantics when given multiple arguments' do + it 'and one of the names is a defined user defined type' do + newdefine 'yayness' + expect(func.call(@scope, 'meh', 'yayness', 'booness')).to be_true end - it "and one of the names is a built type" do - expect(func.call(@scope, "meh", "file", "booness")).to be_true + it 'and one of the names is a built type' do + expect(func.call(@scope, 'meh', 'file', 'booness')).to be_true end - it "and one of the names is a defined class" do - newclass "yayness" - expect(func.call(@scope, "meh", "yayness", "booness")).to be_true + it 'and one of the names is a defined class' do + newclass 'yayness' + expect(func.call(@scope, 'meh', 'yayness', 'booness')).to be_true end - it "is true when at least one variable exists in scope" do + it 'is true when at least one variable exists in scope' do @scope['x'] = 'something' expect(func.call(@scope, '$y', '$x', '$z')).to be_true end - it "is false when none of the names are defined" do - expect(func.call(@scope, "meh", "yayness", "booness")).to be_false + it 'is false when none of the names are defined' do + expect(func.call(@scope, 'meh', 'yayness', 'booness')).to be_false end end - it "raises an argument error when asking if Resource type is defined" do + it 'raises an argument error when asking if Resource type is defined' do resource_type = Puppet::Pops::Types::TypeFactory.resource expect { func.call(@scope, resource_type)}.to raise_error(ArgumentError, /reference to all.*type/) end - it "raises an argument error if you ask if Class is defined" do + it 'raises an argument error if you ask if Class is defined' do class_type = Puppet::Pops::Types::TypeFactory.host_class expect { func.call(@scope, class_type) }.to raise_error(ArgumentError, /reference to all.*class/) end - it "raises error if referencing undef" do + it 'raises error if referencing undef' do expect{func.call(@scope, nil)}.to raise_error(ArgumentError, /mis-matched arguments/) end - it "raises error if referencing a number" do + it 'raises error if referencing a number' do expect{func.call(@scope, 42)}.to raise_error(ArgumentError, /mis-matched arguments/) end - it "is false if referencing empty string" do + it 'is false if referencing empty string' do expect(func.call(@scope, '')).to be_false end it "is true if referencing 'main'" do # mimic what compiler does with "main" in intial import newclass '' newresource :class, '' expect(func.call(@scope, 'main')).to be_true end end