Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F16569984
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
60 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/acceptance/tests/environment/use_enc_environment_for_files.rb b/acceptance/tests/environment/use_enc_environment_for_files.rb
index 5f3dc6dd0..357d1a8ff 100644
--- a/acceptance/tests/environment/use_enc_environment_for_files.rb
+++ b/acceptance/tests/environment/use_enc_environment_for_files.rb
@@ -1,60 +1,63 @@
test_name "Agent should use environment given by ENC for fetching remote files"
testdir = master.tmpdir('respect_enc_test')
create_remote_file master, "#{testdir}/enc.rb", <<END
#!#{master['puppetbindir']}/ruby
puts <<YAML
parameters:
environment: special
YAML
END
on master, "chmod 755 #{testdir}/enc.rb"
on master, "mkdir -p #{testdir}/modules"
# Create a plugin file on the master
on master, "mkdir -p #{testdir}/special/amod/files"
create_remote_file(master, "#{testdir}/special/amod/files/testy", "special_environment")
on master, "chown -R #{master['user']}:#{master['group']} #{testdir}"
on master, "chmod -R g+rwX #{testdir}"
master_opts = {
'master' => {
'node_terminus' => 'exec',
'external_nodes' => "#{testdir}/enc.rb",
'filetimeout' => 1
},
'special' => {
'modulepath' => "#{testdir}/special",
'manifest' => "#{testdir}/different.pp"
}
}
+if master.is_pe?
+ master_opts['special']['modulepath'] << ":#{master['sitemoduledir']}"
+end
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
atmp = agent.tmpdir('respect_enc_test')
logger.debug "agent: #{agent} \tagent.tmpdir => #{atmp}"
create_remote_file master, "#{testdir}/different.pp", <<END
file { "#{atmp}/special_testy":
source => "puppet:///modules/amod/testy",
}
notify { "mytemp is ${::mytemp}": }
END
on master, "chmod 644 #{testdir}/different.pp"
sleep 2 # Make sure the master has time to reload the file
run_agent_on(agent, "--no-daemonize --onetime --server #{master} --verbose --trace")
on agent, "cat #{atmp}/special_testy" do |result|
assert_match(/special_environment/,
result.stdout,
"The file from environment 'special' was not found")
end
on agent, "rm -rf #{atmp}"
end
end
diff --git a/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb b/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb
index e1e54f8c9..05902a37e 100644
--- a/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb
+++ b/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb
@@ -1,40 +1,43 @@
test_name "Agent should use environment given by ENC for pluginsync"
testdir = master.tmpdir('respect_enc_test')
create_remote_file master, "#{testdir}/enc.rb", <<END
#!#{master['puppetbindir']}/ruby
puts <<YAML
parameters:
environment: special
YAML
END
on master, "chmod 755 #{testdir}/enc.rb"
master_opts = {
'master' => {
'node_terminus' => 'exec',
'external_nodes' => "#{testdir}/enc.rb"
},
'special' => {
'modulepath' => "#{testdir}/special"
}
}
+if master.is_pe?
+ master_opts['special']['modulepath'] << ":#{master['sitemoduledir']}"
+end
on master, "mkdir -p #{testdir}/modules"
# Create a plugin file on the master
on master, "mkdir -p #{testdir}/special/amod/lib/puppet"
create_remote_file(master, "#{testdir}/special/amod/lib/puppet/foo.rb", "#special_version")
on master, "chown -R #{master['user']}:#{master['group']} #{testdir}"
on master, "chmod -R g+rwX #{testdir}"
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
run_agent_on(agent, "--no-daemonize --onetime --server #{master}")
on agent, "cat #{agent['puppetvardir']}/lib/puppet/foo.rb"
assert_match(/#special_version/, stdout, "The plugin from environment 'special' was not synced")
on agent, "rm -rf #{agent['puppetvardir']}/lib"
end
end
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index b7e324687..ed2c66bba 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -1,130 +1,129 @@
# the parent class for all of our syntactical objects
require 'puppet'
require 'puppet/util/autoload'
# The base class for all of the objects that make up the parse trees.
# Handles things like file name, line #, and also does the initialization
# for all of the parameters of all of the child objects.
class Puppet::Parser::AST
# Do this so I don't have to type the full path in all of the subclasses
AST = Puppet::Parser::AST
include Puppet::Util::Errors
include Puppet::Util::MethodHelper
include Puppet::Util::Docs
attr_accessor :parent, :scope, :file, :line, :pos
def inspect
"( #{self.class} #{self.to_s} #{@children.inspect} )"
end
# don't fetch lexer comment by default
def use_docs
self.class.use_docs
end
# allow our subclass to specify they want documentation
class << self
attr_accessor :use_docs
def associates_doc
self.use_docs = true
end
end
# Evaluate the current object. Just a stub method, since the subclass
# should override this method.
def evaluate(*options)
end
# Throw a parse error.
def parsefail(message)
self.fail(Puppet::ParseError, message)
end
# Wrap a statemp in a reusable way so we always throw a parse error.
def parsewrap
exceptwrap :type => Puppet::ParseError do
yield
end
end
# The version of the evaluate method that should be called, because it
# correctly handles errors. It is critical to use this method because
# it can enable you to catch the error where it happens, rather than
# much higher up the stack.
def safeevaluate(*options)
# We duplicate code here, rather than using exceptwrap, because this
# is called so many times during parsing.
begin
return self.evaluate(*options)
rescue Puppet::Error => detail
raise adderrorcontext(detail)
rescue => detail
error = Puppet::ParseError.new(detail.to_s, nil, nil, detail)
# We can't use self.fail here because it always expects strings,
# not exceptions.
raise adderrorcontext(error, detail)
end
end
# Initialize the object. Requires a hash as the argument, and
# takes each of the parameters of the hash and calls the settor
# method for them. This is probably pretty inefficient and should
# likely be changed at some point.
def initialize(args)
set_options(args)
end
# evaluate ourselves, and match
def evaluate_match(value, scope)
obj = self.safeevaluate(scope)
obj = obj.downcase if obj.respond_to?(:downcase)
value = value.downcase if value.respond_to?(:downcase)
obj = Puppet::Parser::Scope.number?(obj) || obj
value = Puppet::Parser::Scope.number?(value) || value
# "" == undef for case/selector/if
obj == value or (obj == "" and value == :undef) or (obj == :undef and value == "")
end
end
# And include all of the AST subclasses.
require 'puppet/parser/ast/arithmetic_operator'
require 'puppet/parser/ast/astarray'
require 'puppet/parser/ast/asthash'
require 'puppet/parser/ast/boolean_operator'
require 'puppet/parser/ast/branch'
require 'puppet/parser/ast/caseopt'
require 'puppet/parser/ast/casestatement'
require 'puppet/parser/ast/collection'
require 'puppet/parser/ast/collexpr'
require 'puppet/parser/ast/comparison_operator'
require 'puppet/parser/ast/definition'
require 'puppet/parser/ast/else'
require 'puppet/parser/ast/function'
require 'puppet/parser/ast/hostclass'
require 'puppet/parser/ast/ifstatement'
require 'puppet/parser/ast/in_operator'
require 'puppet/parser/ast/lambda'
require 'puppet/parser/ast/leaf'
require 'puppet/parser/ast/match_operator'
require 'puppet/parser/ast/method_call'
require 'puppet/parser/ast/minus'
require 'puppet/parser/ast/node'
require 'puppet/parser/ast/nop'
require 'puppet/parser/ast/not'
require 'puppet/parser/ast/relationship'
require 'puppet/parser/ast/resource'
require 'puppet/parser/ast/resource_defaults'
require 'puppet/parser/ast/resource_instance'
require 'puppet/parser/ast/resource_override'
require 'puppet/parser/ast/resource_reference'
require 'puppet/parser/ast/resourceparam'
require 'puppet/parser/ast/selector'
-require 'puppet/parser/ast/tag'
require 'puppet/parser/ast/vardef'
require 'puppet/parser/code_merger'
diff --git a/lib/puppet/parser/ast/collexpr.rb b/lib/puppet/parser/ast/collexpr.rb
index 9f266ed47..481c14b20 100644
--- a/lib/puppet/parser/ast/collexpr.rb
+++ b/lib/puppet/parser/ast/collexpr.rb
@@ -1,109 +1,109 @@
require 'puppet'
require 'puppet/parser/ast/branch'
require 'puppet/parser/collector'
# An object that collects stored objects from the central cache and returns
# them to the current host, yo.
class Puppet::Parser::AST
class CollExpr < AST::Branch
attr_accessor :test1, :test2, :oper, :form, :type, :parens
def evaluate(scope)
if Puppet[:parser] == 'future'
evaluate4x(scope)
else
evaluate3x(scope)
end
end
# We return an object that does a late-binding evaluation.
def evaluate3x(scope)
# Make sure our contained expressions have all the info they need.
[@test1, @test2].each do |t|
if t.is_a?(self.class)
t.form ||= self.form
t.type ||= self.type
end
end
# The code is only used for virtual lookups
match1, code1 = @test1.safeevaluate scope
match2, code2 = @test2.safeevaluate scope
# First build up the virtual code.
# If we're a conjunction operator, then we're calling code. I did
# some speed comparisons, and it's at least twice as fast doing these
# case statements as doing an eval here.
code = proc do |resource|
case @oper
when "and"; code1.call(resource) and code2.call(resource)
when "or"; code1.call(resource) or code2.call(resource)
when "=="
if match1 == "tag"
resource.tagged?(match2)
else
if resource[match1].is_a?(Array)
resource[match1].include?(match2)
else
resource[match1] == match2
end
end
when "!="; resource[match1] != match2
end
end
match = [match1, @oper, match2]
return match, code
end
- # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Langauge
+ # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Language
# semantics for equals and include
#
def evaluate4x(scope)
# Make sure our contained expressions have all the info they need.
[@test1, @test2].each do |t|
if t.is_a?(self.class)
t.form ||= self.form
t.type ||= self.type
end
end
# The code is only used for virtual lookups
match1, code1 = @test1.safeevaluate scope
match2, code2 = @test2.safeevaluate scope
# First build up the virtual code.
# If we're a conjunction operator, then we're calling code. I did
# some speed comparisons, and it's at least twice as fast doing these
# case statements as doing an eval here.
code = proc do |resource|
case @oper
when "and"; code1.call(resource) and code2.call(resource)
when "or"; code1.call(resource) or code2.call(resource)
when "=="
if match1 == "tag"
resource.tagged?(match2)
else
if resource[match1].is_a?(Array)
@@compare_operator.include?(resource[match1], match2)
else
@@compare_operator.equals(resource[match1], match2)
end
end
when "!="; ! @@compare_operator.equals(resource[match1], match2)
end
end
match = [match1, @oper, match2]
return match, code
end
def initialize(hash = {})
super
if Puppet[:parser] == "future"
@@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new
end
raise ArgumentError, "Invalid operator #{@oper}" unless %w{== != and or}.include?(@oper)
end
end
end
diff --git a/lib/puppet/parser/ast/tag.rb b/lib/puppet/parser/ast/tag.rb
deleted file mode 100644
index 6f906a1c6..000000000
--- a/lib/puppet/parser/ast/tag.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'puppet/parser/ast/branch'
-
-class Puppet::Parser::AST
- # The code associated with a class. This is different from components
- # in that each class is a singleton -- only one will exist for a given
- # node.
- class Tag < AST::Branch
- @name = :class
- attr_accessor :type
-
- def evaluate(scope)
- types = @type.safeevaluate(scope)
-
- types = [types] unless types.is_a? Array
-
- types.each do |type|
- # Now set our class. We don't have to worry about checking
- # whether we've been evaluated because we're not evaluating
- # any code.
- scope.setclass(self.object_id, type)
- end
- end
- end
-end
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index e17479ba6..506f8a27e 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -1,267 +1,278 @@
require 'puppet/resource'
# The primary difference between this class and its
# parent is that this class has rules on who can set
# parameters
class Puppet::Parser::Resource < Puppet::Resource
require 'puppet/parser/resource/param'
require 'puppet/util/tagging'
require 'puppet/parser/yaml_trimmer'
require 'puppet/resource/type_collection_helper'
include Puppet::Resource::TypeCollectionHelper
include Puppet::Util
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
include Puppet::Util::Logging
include Puppet::Parser::YamlTrimmer
attr_accessor :source, :scope, :collector_id
attr_accessor :virtual, :override, :translated, :catalog, :evaluated
attr_accessor :file, :line
attr_reader :exported, :parameters
# Determine whether the provided parameter name is a relationship parameter.
def self.relationship_parameter?(name)
@relationship_names ||= Puppet::Type.relationship_params.collect { |p| p.name }
@relationship_names.include?(name)
end
# Set up some boolean test methods
def translated?; !!@translated; end
def override?; !!@override; end
def evaluated?; !!@evaluated; end
def [](param)
param = param.intern
if param == :title
return self.title
end
if @parameters.has_key?(param)
@parameters[param].value
else
nil
end
end
def eachparam
@parameters.each do |name, param|
yield param
end
end
def environment
scope.environment
end
# Process the stage metaparameter for a class. A containment edge
# is drawn from the class to the stage. The stage for containment
# defaults to main, if none is specified.
def add_edge_to_stage
return unless self.class?
unless stage = catalog.resource(:stage, self[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main)
raise ArgumentError, "Could not find stage #{self[:stage] || :main} specified by #{self}"
end
self[:stage] ||= stage.title unless stage.title == :main
catalog.add_edge(stage, self)
end
# Retrieve the associated definition and evaluate it.
def evaluate
return if evaluated?
@evaluated = true
if klass = resource_type and ! builtin_type?
finish
evaluated_code = klass.evaluate_code(self)
return evaluated_code
elsif builtin?
devfail "Cannot evaluate a builtin type (#{type})"
else
self.fail "Cannot find definition #{type}"
end
end
# Mark this resource as both exported and virtual,
# or remove the exported mark.
def exported=(value)
if value
@virtual = true
@exported = value
else
@exported = value
end
end
# Do any finishing work on this object, called before evaluation or
# before storage/translation.
def finish
return if finished?
@finished = true
add_defaults
add_scope_tags
validate
end
# Has this resource already been finished?
def finished?
@finished
end
def initialize(*args)
raise ArgumentError, "Resources require a hash as last argument" unless args.last.is_a? Hash
raise ArgumentError, "Resources require a scope" unless args.last[:scope]
super
@source ||= scope.source
end
# Is this resource modeling an isomorphic resource type?
def isomorphic?
if builtin_type?
return resource_type.isomorphic?
else
return true
end
end
# Merge an override resource in. This will throw exceptions if
# any overrides aren't allowed.
def merge(resource)
# Test the resource scope, to make sure the resource is even allowed
# to override.
unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source)
raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file)
end
# Some of these might fail, but they'll fail in the way we want.
resource.parameters.each do |name, param|
override_parameter(param)
end
end
# This only mattered for clients < 0.25, which we don't support any longer.
# ...but, since this hasn't been deprecated, and at least some functions
# used it, deprecate now rather than just eliminate. --daniel 2012-07-15
def metaparam_compatibility_mode?
Puppet.deprecation_warning "metaparam_compatibility_mode? is obsolete since < 0.25 clients are really, really not supported any more"
false
end
def name
self[:name] || self.title
end
# A temporary occasion, until I get paths in the scopes figured out.
alias path to_s
# Define a parameter in our resource.
# if we ever receive a parameter named 'tag', set
# the resource tags with its value.
def set_parameter(param, value = nil)
if ! value.nil?
param = Puppet::Parser::Resource::Param.new(
:name => param, :value => value, :source => self.source
)
elsif ! param.is_a?(Puppet::Parser::Resource::Param)
raise ArgumentError, "Received incomplete information - no value provided for parameter #{param}"
end
tag(*param.value) if param.name == :tag
# And store it in our parameter hash.
@parameters[param.name] = param
end
alias []= set_parameter
def to_hash
@parameters.inject({}) do |hash, ary|
param = ary[1]
# Skip "undef" values.
hash[param.name] = param.value if param.value != :undef
hash
end
end
# Convert this resource to a RAL resource.
def to_ral
copy_as_resource.to_ral
end
+ # Is the receiver tagged with the given tags?
+ # This match takes into account the tags that a resource will inherit from its container
+ # but have not been set yet.
+ # It does *not* take tags set via resource defaults as these will *never* be set on
+ # the resource itself since all resources always have tags that are automatically
+ # assigned.
+ #
+ def tagged?(*tags)
+ super || ((scope_resource = scope.resource) && scope_resource != self && scope_resource.tagged?(tags))
+ end
+
private
# Add default values from our definition.
def add_defaults
scope.lookupdefaults(self.type).each do |name, param|
unless @parameters.include?(name)
self.debug "Adding default for #{name}"
@parameters[name] = param.dup
end
end
end
def add_scope_tags
if scope_resource = scope.resource
tag(*scope_resource.tags)
end
end
# Accept a parameter from an override.
def override_parameter(param)
# This can happen if the override is defining a new parameter, rather
# than replacing an existing one.
(set_parameter(param) and return) unless current = @parameters[param.name]
# The parameter is already set. Fail if they're not allowed to override it.
unless param.source.child_of?(current.source)
msg = "Parameter '#{param.name}' is already set on #{self}"
msg += " by #{current.source}" if current.source.to_s != ""
if current.file or current.line
fields = []
fields << current.file if current.file
fields << current.line.to_s if current.line
msg += " at #{fields.join(":")}"
end
msg += "; cannot redefine"
Puppet.log_exception(ArgumentError.new(), msg)
raise Puppet::ParseError.new(msg, param.line, param.file)
end
# If we've gotten this far, we're allowed to override.
# Merge with previous value, if the parameter was generated with the +>
# syntax. It's important that we use a copy of the new param instance
# here, not the old one, and not the original new one, so that the source
# is registered correctly for later overrides but the values aren't
# implcitly shared when multiple resources are overrriden at once (see
# ticket #3556).
if param.add
param = param.dup
param.value = [current.value, param.value].flatten
end
set_parameter(param)
end
# Make sure the resource's parameters are all valid for the type.
def validate
@parameters.each do |name, param|
validate_parameter(name)
end
rescue => detail
self.fail Puppet::ParseError, detail.to_s + " on #{self}", detail
end
def extract_parameters(params)
params.each do |param|
# Don't set the same parameter twice
self.fail Puppet::ParseError, "Duplicate parameter '#{param.name}' for on #{self}" if @parameters[param.name]
set_parameter(param)
end
end
end
diff --git a/lib/puppet/util/tagging.rb b/lib/puppet/util/tagging.rb
index 031b27f93..266029a1f 100644
--- a/lib/puppet/util/tagging.rb
+++ b/lib/puppet/util/tagging.rb
@@ -1,55 +1,56 @@
require 'puppet/util/tag_set'
module Puppet::Util::Tagging
ValidTagRegex = /^\w[-\w:.]*$/
- # Add a tag to our current list. These tags will be added to all
- # of the objects contained in this scope.
+ # Add a tag to the current tag set.
+ # When a tag set is used for a scope, these tags will be added to all of
+ # the objects contained in this scope when the objects are finished.
+ #
def tag(*ary)
@tags ||= new_tags
- ary.each do |tag|
+ ary.flatten.each do |tag|
name = tag.to_s.downcase
if name =~ ValidTagRegex
@tags << name
name.split("::").each do |section|
@tags << section
end
else
- fail(Puppet::ParseError, "Invalid tag #{name}")
+ fail(Puppet::ParseError, "Invalid tag '#{name}'")
end
end
end
- # Are we tagged with the provided tag?
+ # Is the receiver tagged with the given tags?
def tagged?(*tags)
not ( self.tags & tags.flatten.collect { |t| t.to_s } ).empty?
end
# Return a copy of the tag list, so someone can't ask for our tags
# and then modify them.
def tags
@tags ||= new_tags
@tags.dup
end
def tags=(tags)
@tags = new_tags
return if tags.nil? or tags == ""
tags = tags.strip.split(/\s*,\s*/) if tags.is_a?(String)
-
tags.each {|t| tag(t) }
end
private
def valid_tag?(tag)
tag.is_a?(String) and tag =~ ValidTagRegex
end
def new_tags
Puppet::Util::TagSet.new
end
end
diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb
index f10ce1adc..6ce0b7d67 100755
--- a/spec/integration/parser/compiler_spec.rb
+++ b/spec/integration/parser/compiler_spec.rb
@@ -1,513 +1,541 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/parser/parser_factory'
require 'puppet_spec/compiler'
require 'matchers/resource'
describe "Puppet::Parser::Compiler" do
include PuppetSpec::Compiler
include Matchers::Resource
before :each do
@node = Puppet::Node.new "testnode"
@scope_resource = stub 'scope_resource', :builtin? => true, :finish => nil, :ref => 'Class[main]'
@scope = stub 'scope', :resource => @scope_resource, :source => mock("source")
end
after do
Puppet.settings.clear
end
# shared because tests are invoked both for classic and future parser
#
shared_examples_for "the compiler" do
it "should be able to determine the configuration version from a local version control repository" do
pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
# This should always work, because we should always be
# in the puppet repo when we run this.
version = %x{git rev-parse HEAD}.chomp
Puppet.settings[:config_version] = 'git rev-parse HEAD'
@parser = Puppet::Parser::ParserFactory.parser "development"
@compiler = Puppet::Parser::Compiler.new(@node)
@compiler.catalog.version.should == version
end
end
it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
Puppet[:code] = <<-PP
class foo
{
notify { foo_notify: }
include bar
}
class bar
{
notify { bar_notify: }
}
PP
@node.stubs(:classes).returns(['foo', 'bar'])
catalog = Puppet::Parser::Compiler.compile(@node)
catalog.resource("Notify[foo_notify]").should_not be_nil
catalog.resource("Notify[bar_notify]").should_not be_nil
end
describe "when resolving class references" do
it "should favor local scope, even if there's an included class in topscope" do
Puppet[:code] = <<-PP
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include baz
include experiment
include experiment::baz
PP
catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
notify_resource = catalog.resource( "Notify[x]" )
notify_resource[:require].title.should == "Experiment::Baz"
end
it "should favor local scope, even if there's an unincluded class in topscope" do
Puppet[:code] = <<-PP
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include experiment
include experiment::baz
PP
catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
notify_resource = catalog.resource( "Notify[x]" )
notify_resource[:require].title.should == "Experiment::Baz"
end
end
describe "(ticket #13349) when explicitly specifying top scope" do
["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
describe "with #{include}" do
it "should find the top level class" do
Puppet[:code] = <<-MANIFEST
class { 'foo::test': }
class foo::test {
#{include}
}
class bar::baz {
notify { 'good!': }
}
class foo::bar::baz {
notify { 'bad!': }
}
MANIFEST
catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
catalog.resource("Class[Bar::Baz]").should_not be_nil
catalog.resource("Notify[good!]").should_not be_nil
catalog.resource("Class[Foo::Bar::Baz]").should be_nil
catalog.resource("Notify[bad!]").should be_nil
end
end
end
end
it "should recompute the version after input files are re-parsed" do
Puppet[:code] = 'class foo { }'
Time.stubs(:now).returns(1)
node = Puppet::Node.new('mynode')
Puppet::Parser::Compiler.compile(node).version.should == 1
Time.stubs(:now).returns(2)
Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
Puppet::Parser::Compiler.compile(node).version.should == 2
end
['class', 'define', 'node'].each do |thing|
it "should not allow '#{thing}' inside evaluated conditional constructs" do
Puppet[:code] = <<-PP
if true {
#{thing} foo {
}
notify { decoy: }
}
PP
begin
Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
raise "compilation should have raised Puppet::Error"
rescue Puppet::Error => e
e.message.should =~ /at line 2/
end
end
end
it "should not allow classes inside unevaluated conditional constructs" do
Puppet[:code] = <<-PP
if false {
class foo {
}
}
PP
lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error)
end
describe "when defining relationships" do
def extract_name(ref)
ref.sub(/File\[(\w+)\]/, '\1')
end
let(:node) { Puppet::Node.new('mynode') }
let(:code) do
<<-MANIFEST
file { [a,b,c]:
mode => 0644,
}
file { [d,e]:
mode => 0755,
}
MANIFEST
end
let(:expected_relationships) { [] }
let(:expected_subscriptions) { [] }
before :each do
Puppet[:code] = code
end
after :each do
catalog = Puppet::Parser::Compiler.compile(node)
resources = catalog.resources.select { |res| res.type == 'File' }
actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
resources.map do |res|
dependents = Array(res[relation])
dependents.map { |ref| [res.title, extract_name(ref)] }
end.inject(&:concat)
end
actual_relationships.should =~ expected_relationships
actual_subscriptions.should =~ expected_subscriptions
end
it "should create a relationship" do
code << "File[a] -> File[b]"
expected_relationships << ['a','b']
end
it "should create a subscription" do
code << "File[a] ~> File[b]"
expected_subscriptions << ['a', 'b']
end
it "should create relationships using title arrays" do
code << "File[a,b] -> File[c,d]"
expected_relationships.concat [
['a', 'c'],
['b', 'c'],
['a', 'd'],
['b', 'd'],
]
end
it "should create relationships using collection expressions" do
code << "File <| mode == 0644 |> -> File <| mode == 0755 |>"
expected_relationships.concat [
['a', 'd'],
['b', 'd'],
['c', 'd'],
['a', 'e'],
['b', 'e'],
['c', 'e'],
]
end
it "should create relationships using resource names" do
code << "'File[a]' -> 'File[b]'"
expected_relationships << ['a', 'b']
end
it "should create relationships using variables" do
code << <<-MANIFEST
$var = File[a]
$var -> File[b]
MANIFEST
expected_relationships << ['a', 'b']
end
it "should create relationships using case statements" do
code << <<-MANIFEST
$var = 10
case $var {
10: {
file { s1: }
}
12: {
file { s2: }
}
}
->
case $var + 2 {
10: {
file { t1: }
}
12: {
file { t2: }
}
}
MANIFEST
expected_relationships << ['s1', 't2']
end
it "should create relationships using array members" do
code << <<-MANIFEST
$var = [ [ [ File[a], File[b] ] ] ]
$var[0][0][0] -> $var[0][0][1]
MANIFEST
expected_relationships << ['a', 'b']
end
it "should create relationships using hash members" do
code << <<-MANIFEST
$var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
$var[foo][bar][source] -> $var[foo][bar][target]
MANIFEST
expected_relationships << ['a', 'b']
end
it "should create relationships using resource declarations" do
code << "file { l: } -> file { r: }"
expected_relationships << ['l', 'r']
end
it "should chain relationships" do
code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"
expected_relationships << ['a', 'b'] << ['d', 'c']
expected_subscriptions << ['b', 'c'] << ['e', 'd']
end
end
context 'when working with immutable node data' do
context 'and have opted in to immutable_node_data' do
before :each do
Puppet[:immutable_node_data] = true
end
def node_with_facts(facts)
Puppet[:facts_terminus] = :memory
Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts))
node = Puppet::Node.new("testing")
node.fact_merge
node
end
matcher :fail_compile_with do |node, message_regex|
match do |manifest|
@error = nil
begin
compile_to_catalog(manifest, node)
false
rescue Puppet::Error => e
@error = e
message_regex.match(e.message)
end
end
failure_message_for_should do
if @error
"failed with #{@error}\n#{@error.backtrace}"
else
"did not fail"
end
end
end
it 'should make $facts available' do
node = node_with_facts('the_facts' => 'straight')
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $facts[the_facts] }
MANIFEST
catalog.resource("Notify[test]")[:message].should == "straight"
end
it 'should make $facts reserved' do
node = node_with_facts('the_facts' => 'straight')
expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
end
it 'should make $facts immutable' do
node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true)
expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i)
expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i)
expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i)
expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i)
expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
end
it 'should make $facts available even if there are no facts' do
Puppet[:facts_terminus] = :memory
node = Puppet::Node.new("testing2")
node.fact_merge
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $facts }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {})
end
end
context 'and have not opted in to immutable_node_data' do
before :each do
Puppet[:immutable_node_data] = false
end
it 'should not make $facts available' do
Puppet[:facts_terminus] = :memory
facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight')
Puppet::Node::Facts.indirection.save(facts)
node = Puppet::Node.new("testing")
node.fact_merge
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => "An $facts space" }
MANIFEST
catalog.resource("Notify[test]")[:message].should == "An space"
end
end
end
context 'when working with the trusted data hash' do
context 'and have opted in to trusted_node_data' do
before :each do
Puppet[:trusted_node_data] = true
end
it 'should make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $trusted[data] }
MANIFEST
catalog.resource("Notify[test]")[:message].should == "value"
end
it 'should not allow assignment to $trusted' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/)
end
it 'should not allow addition to $trusted hash' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
$trusted['extra'] = 'added'
notify { 'test': message => $trusted['extra'] == 'added' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
# different errors depending on regular or future parser
end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/)
end
it 'should not allow addition to $trusted hash via Ruby inline template' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
$dummy = inline_template("<% @trusted['extra'] = 'added' %> lol")
notify { 'test': message => $trusted['extra'] == 'added' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/)
end
end
context 'and have not opted in to trusted_node_data' do
before :each do
Puppet[:trusted_node_data] = false
end
it 'should not make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $trusted == undef }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end
it 'should allow assignment to $trusted' do
node = Puppet::Node.new("testing")
catalog = compile_to_catalog(<<-MANIFEST, node)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end
end
end
+
+ context 'when evaluating collection' do
+ it 'matches on container inherited tags' do
+ Puppet[:code] = <<-MANIFEST
+ class xport_test {
+ tag 'foo_bar'
+ @notify { 'nbr1':
+ message => 'explicitly tagged',
+ tag => 'foo_bar'
+ }
+
+ @notify { 'nbr2':
+ message => 'implicitly tagged'
+ }
+
+ Notify <| tag == 'foo_bar' |> {
+ message => 'overridden'
+ }
+ }
+ include xport_test
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden')
+ expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden')
+ end
+ end
end
describe 'using classic parser' do
before :each do
Puppet[:parser] = 'current'
end
it_behaves_like 'the compiler' do
end
end
end
diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb
index bcf2273a0..eac0fa921 100644
--- a/spec/integration/parser/future_compiler_spec.rb
+++ b/spec/integration/parser/future_compiler_spec.rb
@@ -1,402 +1,431 @@
require 'spec_helper'
require 'puppet/pops'
require 'puppet/parser/parser_factory'
require 'puppet_spec/compiler'
require 'puppet_spec/pops'
require 'puppet_spec/scope'
require 'matchers/resource'
require 'rgen/metamodel_builder'
# Test compilation using the future evaluator
describe "Puppet::Parser::Compiler" do
include PuppetSpec::Compiler
include Matchers::Resource
before :each do
Puppet[:parser] = 'future'
end
describe "the compiler when using future parser and evaluator" do
it "should be able to determine the configuration version from a local version control repository" do
pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
# This should always work, because we should always be
# in the puppet repo when we run this.
version = %x{git rev-parse HEAD}.chomp
Puppet.settings[:config_version] = 'git rev-parse HEAD'
compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("testnode"))
compiler.catalog.version.should == version
end
end
it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
node = Puppet::Node.new("testnodex")
node.classes = ['foo', 'bar']
catalog = compile_to_catalog(<<-PP, node)
class foo
{
notify { foo_notify: }
include bar
}
class bar
{
notify { bar_notify: }
}
PP
catalog = Puppet::Parser::Compiler.compile(node)
expect(catalog).to have_resource("Notify[foo_notify]")
expect(catalog).to have_resource("Notify[bar_notify]")
end
it 'applies defaults for defines with qualified names (PUP-2302)' do
catalog = compile_to_catalog(<<-CODE)
define my::thing($msg = 'foo') { notify {'check_me': message => $msg } }
My::Thing { msg => 'evoe' }
my::thing { 'name': }
CODE
expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe")
end
it 'does not apply defaults from dynamic scopes (PUP-867)' do
catalog = compile_to_catalog(<<-CODE)
class a {
Notify { message => "defaulted" }
include b
notify { bye: }
}
class b { notify { hi: } }
include a
CODE
expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, nil)
expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted")
end
it 'gets default from inherited class (PUP-867)' do
catalog = compile_to_catalog(<<-CODE)
class a {
Notify { message => "defaulted" }
include c
notify { bye: }
}
class b { Notify { message => "inherited" } }
class c inherits b { notify { hi: } }
include a
CODE
expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited")
expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted")
end
describe "when resolving class references" do
it "should favor local scope, even if there's an included class in topscope" do
catalog = compile_to_catalog(<<-PP)
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include baz
include experiment
include experiment::baz
PP
expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
end
it "should favor local scope, even if there's an unincluded class in topscope" do
catalog = compile_to_catalog(<<-PP)
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include experiment
include experiment::baz
PP
expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
end
end
describe "(ticket #13349) when explicitly specifying top scope" do
["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
describe "with #{include}" do
it "should find the top level class" do
catalog = compile_to_catalog(<<-MANIFEST)
class { 'foo::test': }
class foo::test {
#{include}
}
class bar::baz {
notify { 'good!': }
}
class foo::bar::baz {
notify { 'bad!': }
}
MANIFEST
expect(catalog).to have_resource("Class[Bar::Baz]")
expect(catalog).to have_resource("Notify[good!]")
expect(catalog).to_not have_resource("Class[Foo::Bar::Baz]")
expect(catalog).to_not have_resource("Notify[bad!]")
end
end
end
end
it "should recompute the version after input files are re-parsed" do
Puppet[:code] = 'class foo { }'
Time.stubs(:now).returns(1)
node = Puppet::Node.new('mynode')
Puppet::Parser::Compiler.compile(node).version.should == 1
Time.stubs(:now).returns(2)
Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
Puppet::Parser::Compiler.compile(node).version.should == 2
end
['define', 'class', 'node'].each do |thing|
it "'#{thing}' is not allowed inside evaluated conditional constructs" do
expect do
compile_to_catalog(<<-PP)
if true {
#{thing} foo {
}
notify { decoy: }
}
PP
end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/)
end
it "'#{thing}' is not allowed inside un-evaluated conditional constructs" do
expect do
compile_to_catalog(<<-PP)
if false {
#{thing} foo {
}
notify { decoy: }
}
PP
end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/)
end
end
describe "relationships can be formed" do
def extract_name(ref)
ref.sub(/File\[(\w+)\]/, '\1')
end
def assert_creates_relationships(relationship_code, expectations)
base_manifest = <<-MANIFEST
file { [a,b,c]:
mode => 0644,
}
file { [d,e]:
mode => 0755,
}
MANIFEST
catalog = compile_to_catalog(base_manifest + relationship_code)
resources = catalog.resources.select { |res| res.type == 'File' }
actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
resources.map do |res|
dependents = Array(res[relation])
dependents.map { |ref| [res.title, extract_name(ref)] }
end.inject(&:concat)
end
actual_relationships.should =~ (expectations[:relationships] || [])
actual_subscriptions.should =~ (expectations[:subscriptions] || [])
end
it "of regular type" do
assert_creates_relationships("File[a] -> File[b]",
:relationships => [['a','b']])
end
it "of subscription type" do
assert_creates_relationships("File[a] ~> File[b]",
:subscriptions => [['a', 'b']])
end
it "between multiple resources expressed as resource with multiple titles" do
assert_creates_relationships("File[a,b] -> File[c,d]",
:relationships => [['a', 'c'],
['b', 'c'],
['a', 'd'],
['b', 'd']])
end
it "between collection expressions" do
assert_creates_relationships("File <| mode == 0644 |> -> File <| mode == 0755 |>",
:relationships => [['a', 'd'],
['b', 'd'],
['c', 'd'],
['a', 'e'],
['b', 'e'],
['c', 'e']])
end
it "between resources expressed as Strings" do
assert_creates_relationships("'File[a]' -> 'File[b]'",
:relationships => [['a', 'b']])
end
it "between resources expressed as variables" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']])
$var = File[a]
$var -> File[b]
MANIFEST
end
it "between resources expressed as case statements" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['s1', 't2']])
$var = 10
case $var {
10: {
file { s1: }
}
12: {
file { s2: }
}
}
->
case $var + 2 {
10: {
file { t1: }
}
12: {
file { t2: }
}
}
MANIFEST
end
it "using deep access in array" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']])
$var = [ [ [ File[a], File[b] ] ] ]
$var[0][0][0] -> $var[0][0][1]
MANIFEST
end
it "using deep access in hash" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']])
$var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
$var[foo][bar][source] -> $var[foo][bar][target]
MANIFEST
end
it "using resource declarations" do
assert_creates_relationships("file { l: } -> file { r: }", :relationships => [['l', 'r']])
end
it "between entries in a chain of relationships" do
assert_creates_relationships("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]",
:relationships => [['a', 'b'], ['d', 'c']],
:subscriptions => [['b', 'c'], ['e', 'd']])
end
end
context "when dealing with variable references" do
it 'an initial underscore in a variable name is ok' do
catalog = compile_to_catalog(<<-MANIFEST)
class a { $_a = 10}
include a
notify { 'test': message => $a::_a }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 10)
end
it 'an initial underscore in not ok if elsewhere than last segment' do
expect do
catalog = compile_to_catalog(<<-MANIFEST)
class a { $_a = 10}
include a
notify { 'test': message => $_a::_a }
MANIFEST
end.to raise_error(/Illegal variable name/)
end
it 'a missing variable as default value becomes undef' do
catalog = compile_to_catalog(<<-MANIFEST)
class a ($b=$x) { notify {$b: message=>'meh'} }
include a
MANIFEST
expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh")
end
end
context 'when working with the trusted data hash' do
context 'and have opted in to hashed_node_data' do
before :each do
Puppet[:trusted_node_data] = true
end
it 'should make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $trusted[data] }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "value")
end
it 'should not allow assignment to $trusted' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
compile_to_catalog(<<-MANIFEST, node)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/)
end
end
context 'and have not opted in to hashed_node_data' do
before :each do
Puppet[:trusted_node_data] = false
end
it 'should not make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => ($trusted == undef) }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true)
end
it 'should allow assignment to $trusted' do
catalog = compile_to_catalog(<<-MANIFEST)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true)
end
end
end
end
+
+ context 'when evaluating collection' do
+ it 'matches on container inherited tags' do
+ Puppet[:code] = <<-MANIFEST
+ class xport_test {
+ tag('foo_bar')
+ @notify { 'nbr1':
+ message => 'explicitly tagged',
+ tag => 'foo_bar'
+ }
+
+ @notify { 'nbr2':
+ message => 'implicitly tagged'
+ }
+
+ Notify <| tag == 'foo_bar' |> {
+ message => 'overridden'
+ }
+ }
+ include xport_test
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden')
+ expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden')
+ end
+ end
+
end
diff --git a/spec/unit/util/tagging_spec.rb b/spec/unit/util/tagging_spec.rb
index 248e915e9..c2ebeaab3 100755
--- a/spec/unit/util/tagging_spec.rb
+++ b/spec/unit/util/tagging_spec.rb
@@ -1,131 +1,162 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/util/tagging'
describe Puppet::Util::Tagging do
let(:tagger) { Object.new.extend(Puppet::Util::Tagging) }
it "should add tags to the returned tag list" do
tagger.tag("one")
expect(tagger.tags).to include("one")
end
it "should return a duplicate of the tag list, rather than the original" do
tagger.tag("one")
tags = tagger.tags
tags << "two"
expect(tagger.tags).to_not include("two")
end
it "should add all provided tags to the tag list" do
tagger.tag("one", "two")
expect(tagger.tags).to include("one")
expect(tagger.tags).to include("two")
end
it "should fail on tags containing '*' characters" do
expect { tagger.tag("bad*tag") }.to raise_error(Puppet::ParseError)
end
it "should fail on tags starting with '-' characters" do
expect { tagger.tag("-badtag") }.to raise_error(Puppet::ParseError)
end
it "should fail on tags containing ' ' characters" do
expect { tagger.tag("bad tag") }.to raise_error(Puppet::ParseError)
end
it "should allow alpha tags" do
expect { tagger.tag("good_tag") }.not_to raise_error
end
it "should allow tags containing '.' characters" do
expect { tagger.tag("good.tag") }.to_not raise_error(Puppet::ParseError)
end
it "should add qualified classes as tags" do
tagger.tag("one::two")
expect(tagger.tags).to include("one::two")
end
it "should add each part of qualified classes as tags" do
tagger.tag("one::two::three")
expect(tagger.tags).to include('one')
expect(tagger.tags).to include("two")
expect(tagger.tags).to include("three")
end
it "should indicate when the object is tagged with a provided tag" do
tagger.tag("one")
expect(tagger).to be_tagged("one")
end
it "should indicate when the object is not tagged with a provided tag" do
expect(tagger).to_not be_tagged("one")
end
it "should indicate when the object is tagged with any tag in an array" do
tagger.tag("one")
expect(tagger).to be_tagged("one","two","three")
end
it "should indicate when the object is not tagged with any tag in an array" do
tagger.tag("one")
expect(tagger).to_not be_tagged("two","three")
end
context "when tagging" do
it "converts symbols to strings" do
tagger.tag(:hello)
expect(tagger.tags).to include('hello')
end
it "downcases tags" do
tagger.tag(:HEllO)
tagger.tag("GooDByE")
expect(tagger).to be_tagged("hello")
expect(tagger).to be_tagged("goodbye")
end
it "accepts hyphenated tags" do
tagger.tag("my-tag")
expect(tagger).to be_tagged("my-tag")
end
end
context "when querying if tagged" do
it "responds true if queried on the entire set" do
tagger.tag("one", "two")
expect(tagger).to be_tagged("one", "two")
end
it "responds true if queried on a subset" do
tagger.tag("one", "two", "three")
expect(tagger).to be_tagged("two", "one")
end
it "responds true if queried on an overlapping but not fully contained set" do
tagger.tag("one", "two")
expect(tagger).to be_tagged("zero", "one")
end
it "responds false if queried on a disjoint set" do
tagger.tag("one", "two", "three")
expect(tagger).to_not be_tagged("five")
end
it "responds false if queried on the empty set" do
expect(tagger).to_not be_tagged
end
end
context "when assigning tags" do
it "splits a string on ','" do
tagger.tags = "one, two, three"
expect(tagger).to be_tagged("one")
expect(tagger).to be_tagged("two")
expect(tagger).to be_tagged("three")
end
+
+ it "protects against empty tags" do
+ expect { tagger.tags = "one,,two"}.to raise_error(/Invalid tag ''/)
+ end
+
+ it "takes an array of tags" do
+ tagger.tags = ["one", "two"]
+
+ expect(tagger).to be_tagged("one")
+ expect(tagger).to be_tagged("two")
+ end
+
+ it "removes any existing tags when reassigning" do
+ tagger.tags = "one, two"
+
+ tagger.tags = "three, four"
+
+ expect(tagger).to_not be_tagged("one")
+ expect(tagger).to_not be_tagged("two")
+ expect(tagger).to be_tagged("three")
+ expect(tagger).to be_tagged("four")
+ end
+
+ it "allows empty tags that are generated from :: separated tags" do
+ tagger.tags = "one::::two::three"
+
+ expect(tagger).to be_tagged("one")
+ expect(tagger).to be_tagged("")
+ expect(tagger).to be_tagged("two")
+ expect(tagger).to be_tagged("three")
+ end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 1, 8:59 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10075398
Default Alt Text
(60 KB)
Attached To
Mode
rPU puppet
Attached
Detach File
Event Timeline
Log In to Comment