diff options
| author | Max Martin <max@puppetlabs.com> | 2011-03-30 13:02:19 -0700 |
|---|---|---|
| committer | Max Martin <max@puppetlabs.com> | 2011-03-30 13:02:19 -0700 |
| commit | 9c06fbd762cddcc41a7185a36f2a8e72879125eb (patch) | |
| tree | bfd37ac5887307857d0b0d79a1db034010eed424 | |
| parent | 4196699f5fbb90ceecbb709c8502622eaad39062 (diff) | |
| parent | 11309a214148abb2167c1bd0adba0e035b2d8384 (diff) | |
| download | puppet-9c06fbd762cddcc41a7185a36f2a8e72879125eb.tar.gz puppet-9c06fbd762cddcc41a7185a36f2a8e72879125eb.tar.xz puppet-9c06fbd762cddcc41a7185a36f2a8e72879125eb.zip | |
Merge branch 'next'
* next: (39 commits)
(#5908) Add support for new update-rc.d disable API
(#6830) Fix tests that depended on special inherited behavior
(#6830) Fix overly stubbed tests
(#6830) Fix instance_variables now comes back as symbols
(#6830) Fix badly stubbed Time object in test
(#6830) Fix MD5 handling to work with Ruby 1.9
(#6830) Fix File class scoping
(#6830) Handle case where array is actually a string
(#6830) Fix case where instance_variables returns symbols in Ruby 1.9
(#6862) Add a default subject for the mail_patches rake task
Fixed #6256 - Creation of rrd directory.
(#6855) ResourceType#search now accepts a regex
(#5477) Allow watch_file to watch non-existent files, especially site.pp
(#5477) Allow watch_file to watch non-existent files, especially site.pp
Fixing #6851 - ResourceType#find/search loads types
Fixing Module#path detection
Fixed #6850 - Clean up ResourceType#to_pson
(#6830) Fix stat method calls to not use an unneeded argument
(#4576) Raise an error when a node is classified into a non-existent class
Update CHANGELOG for 2.6.7
...
75 files changed, 1952 insertions, 228 deletions
@@ -1,5 +1,6 @@ -2.6.7rc1 -======== +2.6.7 +===== +17f673d Updated CHANGELOG for 2.6.7rc1 852fb97 (#5073) Download plugins even if you're filtering on tags 4f34dbf Fix #5610: Prevent unnecessary RAL lookups 9781032 Revert "Merge branch 'ticket/2.6.x/5605' of git://github.com/stschulte/puppet into 2.6.next" diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index 3bfad89f4..78499a92a 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -222,7 +222,7 @@ License exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? - Puppet.settings.use :main, :master, :ssl + Puppet.settings.use :main, :master, :ssl, :metrics # Cache our nodes in yaml. Currently not configurable. Puppet::Node.indirection.cache_class = :yaml diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index f308d4476..989ef3f35 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -474,6 +474,7 @@ module Puppet setdefaults(:metrics, :rrddir => {:default => "$vardir/rrd", + :mode => 0750, :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb index fdbcf93a3..c020f036d 100644 --- a/lib/puppet/file_serving/fileset.rb +++ b/lib/puppet/file_serving/fileset.rb @@ -59,6 +59,7 @@ class Puppet::FileServing::Fileset end def initialize(path, options = {}) + path = path.chomp(File::SEPARATOR) raise ArgumentError.new("Fileset paths must be fully qualified") unless File.expand_path(path) == path @path = path diff --git a/lib/puppet/indirector/resource_type/parser.rb b/lib/puppet/indirector/resource_type/parser.rb index 8b1bed0a9..4bcaf3f47 100644 --- a/lib/puppet/indirector/resource_type/parser.rb +++ b/lib/puppet/indirector/resource_type/parser.rb @@ -10,7 +10,10 @@ class Puppet::Indirector::ResourceType::Parser < Puppet::Indirector::Code # This is a bit ugly. [:hostclass, :definition, :node].each do |type| - if r = krt.send(type, request.key) + # We have to us 'find_<type>' here because it will + # load any missing types from disk, whereas the plain + # '<type>' method only returns from memory. + if r = krt.send("find_#{type}", [""], request.key) return r end end @@ -18,9 +21,22 @@ class Puppet::Indirector::ResourceType::Parser < Puppet::Indirector::Code end def search(request) - raise ArgumentError, "Only '*' is acceptable as a search request" unless request.key == "*" krt = request.environment.known_resource_types - result = [krt.hostclasses.values, krt.definitions.values, krt.nodes.values].flatten + # Make sure we've got all of the types loaded. + krt.loader.import_all + result = [krt.hostclasses.values, krt.definitions.values, krt.nodes.values].flatten.reject { |t| t.name == "" } + return nil if result.empty? + return result if request.key == "*" + + # Strip the regex of any wrapping slashes that might exist + key = request.key.sub(/^\//, '').sub(/\/$/, '') + begin + regex = Regexp.new(key) + rescue => detail + raise ArgumentError, "Invalid regex '#{request.key}': #{detail}" + end + + result.reject! { |t| t.name.to_s !~ regex } return nil if result.empty? result end diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 43266b2b5..059591ed8 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -138,7 +138,7 @@ class Puppet::Module # Find this module in the modulepath. def path - environment.modulepath.collect { |path| File.join(path, name) }.find { |d| FileTest.exist?(d) } + environment.modulepath.collect { |path| File.join(path, name) }.find { |d| FileTest.directory?(d) } end # Find all plugin directories. This is used by the Plugins fileserving mount. diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index 7877b7f73..d50530618 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -76,7 +76,7 @@ class Puppet::Node::Environment # per environment semantics with an efficient most common cases; we almost # always just return our thread's known-resource types. Only at the start # of a compilation (after our thread var has been set to nil) or when the - # environment has changed do we delve deeper. + # environment has changed do we delve deeper. Thread.current[:known_resource_types] = nil if (krt = Thread.current[:known_resource_types]) && krt.environment != self Thread.current[:known_resource_types] ||= synchronize { if @known_resource_types.nil? or @known_resource_types.stale? @@ -128,7 +128,7 @@ class Puppet::Node::Environment to_s.to_sym end - # The only thing we care about when serializing an environment is its + # The only thing we care about when serializing an environment is its # identity; everything else is ephemeral and should not be stored or # transmitted. def to_zaml(z) @@ -156,7 +156,6 @@ class Puppet::Node::Environment parser.string = code else file = Puppet.settings.value(:manifest, name.to_s) - return empty_parse_result unless File.exist?(file) parser.file = file end parser.parse diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index fdabd05c9..a891f1a11 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -20,7 +20,7 @@ class Puppet::Parser::Compiler puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "#{detail} on node #{node.name}" ensure - # We get these from the environment and only cache them in a thread + # We get these from the environment and only cache them in a thread # variable for the duration of the compilation. Thread.current[:known_resource_types] = nil Thread.current[:env_module_directories] = nil @@ -133,12 +133,11 @@ class Puppet::Parser::Compiler end # Evaluate each specified class in turn. If there are any classes we can't - # find, just tag the catalog and move on. This method really just - # creates resource objects that point back to the classes, and then the - # resources are themselves evaluated later in the process. + # find, raise an error. This method really just creates resource objects + # that point back to the classes, and then the resources are themselves + # evaluated later in the process. def evaluate_classes(classes, scope, lazy_evaluate = true) raise Puppet::DevError, "No source for scope passed to evaluate_classes" unless scope.source - found = [] param_classes = nil # if we are a param class, save the classes hash # and transform classes to be the keys @@ -153,20 +152,17 @@ class Puppet::Parser::Compiler if param_classes resource = klass.ensure_in_catalog(scope, param_classes[name] || {}) else - found << name and next if scope.class_scope(klass) + next if scope.class_scope(klass) resource = klass.ensure_in_catalog(scope) end # If they've disabled lazy evaluation (which the :include function does), # then evaluate our resource immediately. resource.evaluate unless lazy_evaluate - found << name else - Puppet.info "Could not find class #{name} for #{node.name}" - @catalog.tag(name) + raise Puppet::Error, "Could not find class #{name} for #{node.name}" end end - found end def evaluate_relationships diff --git a/lib/puppet/parser/functions/create_resources.rb b/lib/puppet/parser/functions/create_resources.rb new file mode 100644 index 000000000..430f110b4 --- /dev/null +++ b/lib/puppet/parser/functions/create_resources.rb @@ -0,0 +1,47 @@ +Puppet::Parser::Functions::newfunction(:create_resources, :doc => ' +Converts a hash into a set of resources and adds them to the catalog. +Takes two parameters: + create_resource($type, $resources) + Creates resources of type $type from the $resources hash. Assumes that + hash is in the following form: + {title=>{parameters}} + This is currently tested for defined resources, classes, as well as native types +') do |args| + raise ArgumentError, ("create_resources(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2 + #raise ArgumentError, 'requires resource type and param hash' if args.size < 2 + # figure out what kind of resource we are + type_of_resource = nil + type_name = args[0].downcase + if type_name == 'class' + type_of_resource = :class + else + if resource = Puppet::Type.type(type_name.to_sym) + type_of_resource = :type + elsif resource = find_definition(type_name.downcase) + type_of_resource = :define + else + raise ArgumentError, "could not create resource of unknown type #{type_name}" + end + end + # iterate through the resources to create + args[1].each do |title, params| + raise ArgumentError, 'params should not contain title' if(params['title']) + case type_of_resource + when :type + res = resource.hash2resource(params.merge(:title => title)) + catalog.add_resource(res) + when :define + p_resource = Puppet::Parser::Resource.new(type_name, title, :scope => self, :source => resource) + params.merge(:name => title).each do |k,v| + p_resource.set_parameter(k,v) + end + resource.instantiate_resource(self, p_resource) + compiler.add_resource(self, p_resource) + when :class + klass = find_hostclass(title) + raise ArgumentError, "could not find hostclass #{title}" unless klass + klass.ensure_in_catalog(self, params) + compiler.catalog.add_class([title]) + end + end +end diff --git a/lib/puppet/parser/functions/fqdn_rand.rb b/lib/puppet/parser/functions/fqdn_rand.rb index 52946f2c1..91157a148 100644 --- a/lib/puppet/parser/functions/fqdn_rand.rb +++ b/lib/puppet/parser/functions/fqdn_rand.rb @@ -5,8 +5,8 @@ Puppet::Parser::Functions::newfunction(:fqdn_rand, :type => :rvalue, :doc => $random_number = fqdn_rand(30) $random_number_seed = fqdn_rand(30,30)") do |args| - require 'md5' + require 'digest/md5' max = args.shift - srand MD5.new([lookupvar('fqdn'),args].join(':')).to_s.hex + srand(Digest::MD5.hexdigest([lookupvar('fqdn'),args].join(':')).hex) rand(max).to_s end diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index 4050adeb8..2f416615e 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -312,7 +312,8 @@ class Puppet::Parser::Lexer def file=(file) @file = file @line = 1 - @scanner = StringScanner.new(File.read(file)) + contents = File.exists?(file) ? File.read(file) : "" + @scanner = StringScanner.new(contents) end def shift_token @@ -547,7 +548,7 @@ class Puppet::Parser::Lexer value,terminator = slurpstring('"$') token_queue << [TOKENS[token_type[terminator]],preamble+value] if terminator != '$' or @scanner.scan(/\{/) - token_queue.shift + token_queue.shift elsif var_name = @scanner.scan(%r{(\w*::)*\w+|[0-9]}) token_queue << [TOKENS[:VARIABLE],var_name] tokenize_interpolated_string(DQ_continuation_token_types) diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 746aa0f90..7888fe1dc 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -79,7 +79,6 @@ class Puppet::Parser::Parser unless file =~ /\.pp$/ file = file + ".pp" end - raise Puppet::Error, "Could not find file #{file}" unless FileTest.exist?(file) end raise Puppet::AlreadyImportedError, "Import loop detected" if known_resource_types.watching_file?(file) diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb index 140c9f2ca..1fba73d0b 100644 --- a/lib/puppet/parser/type_loader.rb +++ b/lib/puppet/parser/type_loader.rb @@ -92,6 +92,34 @@ class Puppet::Parser::TypeLoader end end + def import_all + require 'find' + + module_names = [] + # Collect the list of all known modules + environment.modulepath.each do |path| + Dir.chdir(path) do + Dir.glob("*").each do |dir| + next unless FileTest.directory?(dir) + module_names << dir + end + end + end + + module_names.uniq! + # And then load all files from each module, but (relying on system + # behavior) only load files from the first module of a given name. E.g., + # given first/foo and second/foo, only files from first/foo will be loaded. + module_names.each do |name| + mod = Puppet::Module.new(name, environment) + Find.find(File.join(mod.path, "manifests")) do |path| + if path =~ /\.pp$/ or path =~ /\.rb$/ + import(path) + end + end + end + end + def known_resource_types environment.known_resource_types end diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb new file mode 100755 index 000000000..9506c67a2 --- /dev/null +++ b/lib/puppet/provider/aixobject.rb @@ -0,0 +1,393 @@ +# +# Common code for AIX providers. This class implements basic structure for +# AIX resources. +# Author:: Hector Rivas Gandara <keymon@gmail.com> +# +class Puppet::Provider::AixObject < Puppet::Provider + desc "Generic AIX resource provider" + + # The real provider must implement these functions. + def lscmd(value=@resource[:name]) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def lscmd(value=@resource[:name]) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def addcmd(extra_attrs = []) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def modifycmd(attributes_hash) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def deletecmd + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + # Valid attributes to be managed by this provider. + # It is a list of hashes + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Optional. Method name that adapts puppet property to aix command value. + # :from Optional. Method to adapt aix command line value to puppet property. Optional + class << self + attr_accessor :attribute_mapping + end + + # Mapping from Puppet property to AIX attribute. + def self.attribute_mapping_to + if ! @attribute_mapping_to + @attribute_mapping_to = {} + attribute_mapping.each { |elem| + attribute_mapping_to[elem[:puppet_prop]] = { + :key => elem[:aix_attr], + :method => elem[:to] + } + } + end + @attribute_mapping_to + end + + # Mapping from AIX attribute to Puppet property. + def self.attribute_mapping_from + if ! @attribute_mapping_from + @attribute_mapping_from = {} + attribute_mapping.each { |elem| + attribute_mapping_from[elem[:aix_attr]] = { + :key => elem[:puppet_prop], + :method => elem[:from] + } + } + end + @attribute_mapping_from + end + + # This functions translates a key and value using the given mapping. + # Mapping can be nil (no translation) or a hash with this format + # {:key => new_key, :method => translate_method} + # It returns a list with the pair [key, value] + def translate_attr(key, value, mapping) + return [key, value] unless mapping + return nil unless mapping[key] + + if mapping[key][:method] + new_value = method(mapping[key][:method]).call(value) + else + new_value = value + end + [mapping[key][:key], new_value] + end + + # Loads an AIX attribute (key=value) and stores it in the given hash with + # puppet semantics. It translates the pair using the given mapping. + # + # This operation works with each property one by one, + # subclasses must reimplement this if more complex operations are needed + def load_attribute(key, value, mapping, objectinfo) + if mapping.nil? + objectinfo[key] = value + elsif mapping[key].nil? + # is not present in mapping, ignore it. + true + elsif mapping[key][:method].nil? + objectinfo[mapping[key][:key]] = value + elsif + objectinfo[mapping[key][:key]] = method(mapping[key][:method]).call(value) + end + + return objectinfo + end + + # Gets the given command line argument for the given key and value, + # using the given mapping to translate key and value. + # All the objectinfo hash (@resource or @property_hash) is passed. + # + # This operation works with each property one by one, + # and default behaviour is return the arguments as key=value pairs. + # Subclasses must reimplement this if more complex operations/arguments + # are needed + # + def get_arguments(key, value, mapping, objectinfo) + if mapping.nil? + new_key = key + new_value = value + elsif mapping[key].nil? + # is not present in mapping, ignore it. + new_key = nil + new_value = nil + elsif mapping[key][:method].nil? + new_key = mapping[key][:key] + new_value = value + elsif + new_key = mapping[key][:key] + new_value = method(mapping[key][:method]).call(value) + end + + # convert it to string + new_value = Array(new_value).join(',') + + if new_key + return [ "#{new_key}=#{new_value}" ] + else + return [] + end + end + + # Convert the provider properties (hash) to AIX command arguments + # (list of strings) + # This function will translate each value/key and generate the argument using + # the get_arguments function. + def hash2args(hash, mapping=self.class.attribute_mapping_to) + return "" unless hash + arg_list = [] + hash.each {|key, val| + arg_list += self.get_arguments(key, val, mapping, hash) + } + arg_list + end + + # Parse AIX command attributes from the output of an AIX command, that + # which format is a list of space separated of key=value pairs: + # "uid=100 groups=a,b,c". + # It returns an hash. + # + # If a mapping is provided, the keys are translated as defined in the + # mapping hash. And only values included in mapping will be added + # + # NOTE: it will ignore the items not including '=' + def parse_attr_list(str, mapping=self.class.attribute_mapping_from) + properties = {} + attrs = [] + if !str or (attrs = str.split()).empty? + return nil + end + + attrs.each { |i| + if i.include? "=" # Ignore if it does not include '=' + (key_str, val) = i.split('=') + # Check the key + if !key_str or key_str.empty? + info "Empty key in string 'i'?" + continue + end + key = key_str.to_sym + + properties = self.load_attribute(key, val, mapping, properties) + end + } + properties.empty? ? nil : properties + end + + # Parse AIX command output in a colon separated list of attributes, + # This function is useful to parse the output of commands like lsfs -c: + # #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct + # /:/dev/hd4:jfs2::bootfs:557056:rw:yes:no + # /home:/dev/hd1:jfs2:::2129920:rw:yes:no + # /usr:/dev/hd2:jfs2::bootfs:9797632:rw:yes:no + # + # If a mapping is provided, the keys are translated as defined in the + # mapping hash. And only values included in mapping will be added + def parse_colon_list(str, key_list, mapping=self.class.attribute_mapping_from) + properties = {} + attrs = [] + if !str or (attrs = str.split(':')).empty? + return nil + end + + attrs.each { |val| + key = key_list.shift.downcase.to_sym + properties = self.load_attribute(key, val, mapping, properties) + } + properties.empty? ? nil : properties + + end + + # Default parsing function for AIX commands. + # It will choose the method depending of the first line. + # For the colon separated list it will: + # 1. Get keys from first line. + # 2. Parse next line. + def parse_command_output(output, mapping=self.class.attribute_mapping_from) + lines = output.split("\n") + # if it begins with #something:... is a colon separated list. + if lines[0] =~ /^#.*:/ + self.parse_colon_list(lines[1], lines[0][1..-1].split(':'), mapping) + else + self.parse_attr_list(lines[0], mapping) + end + end + + # Retrieve all the information of an existing resource. + # It will execute 'lscmd' command and parse the output, using the mapping + # 'attribute_mapping_from' to translate the keys and values. + def getinfo(refresh = false) + if @objectinfo.nil? or refresh == true + # Execute lsuser, split all attributes and add them to a dict. + begin + output = execute(self.lscmd) + @objectinfo = self.parse_command_output(execute(self.lscmd)) + # All attributtes without translation + @objectosinfo = self.parse_command_output(execute(self.lscmd), nil) + rescue Puppet::ExecutionFailure => detail + # Print error if needed. FIXME: Do not check the user here. + Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" + end + end + @objectinfo + end + + # Like getinfo, but it will not use the mapping to translate the keys and values. + # It might be usefult to retrieve some raw information. + def getosinfo(refresh = false) + if @objectosinfo .nil? or refresh == true + getinfo(refresh) + end + @objectosinfo + end + + + # List all elements of given type. It works for colon separated commands and + # list commands. + # It returns a list of names. + def list_all + names = [] + begin + output = execute(self.lsallcmd()).split('\n') + (output.select{ |l| l != /^#/ }).each { |v| + name = v.split(/[ :]/) + names << name if not name.empty? + } + rescue Puppet::ExecutionFailure => detail + # Print error if needed + Puppet.debug "aix.list_all(): Could not get all resources of type #{@resource.class.name}: #{detail}" + end + names + end + + + #------------- + # Provider API + # ------------ + + # Clear out the cached values. + def flush + @property_hash.clear if @property_hash + @objectinfo.clear if @objectinfo + end + + # Check that the user exists + def exists? + !!getinfo(true) # !! => converts to bool + end + + # Return all existing instances + # The method for returning a list of provider instances. Note that it returns + # providers, preferably with values already filled in, not resources. + def self.instances + objects=[] + self.list_all().each { |entry| + objects << new(:name => entry, :ensure => :present) + } + objects + end + + #- **ensure** + # The basic state that the object should be in. Valid values are + # `present`, `absent`, `role`. + # From ensurable: exists?, create, delete + def ensure + if exists? + :present + else + :absent + end + end + + # Create a new instance of the resource + def create + if exists? + info "already exists" + # The object already exists + return nil + end + + begin + execute(self.addcmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{detail}" + end + end + + # Delete this instance of the resource + def delete + unless exists? + info "already absent" + # the object already doesn't exist + return nil + end + + begin + execute(self.deletecmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not delete #{@resource.class.name} #{@resource.name}: #{detail}" + end + end + + #-------------------------------- + # Call this method when the object is initialized. + # It creates getter/setter methods for each property our resource type supports. + # If setter or getter already defined it will not be overwritten + def self.mk_resource_methods + [resource_type.validproperties, resource_type.parameters].flatten.each do |prop| + next if prop == :ensure + define_method(prop) { get(prop) || :absent} unless public_method_defined?(prop) + define_method(prop.to_s + "=") { |*vals| set(prop, *vals) } unless public_method_defined?(prop.to_s + "=") + end + end + + # Define the needed getters and setters as soon as we know the resource type + def self.resource_type=(resource_type) + super + mk_resource_methods + end + + # Retrieve a specific value by name. + def get(param) + (hash = getinfo(false)) ? hash[param] : nil + end + + # Set a property. + def set(param, value) + @property_hash[symbolize(param)] = value + + if getinfo().nil? + # This is weird... + raise Puppet::Error, "Trying to update parameter '#{param}' to '#{value}' for a resource that does not exists #{@resource.class.name} #{@resource.name}: #{detail}" + end + if value == getinfo()[param.to_sym] + return + end + + #self.class.validate(param, value) + if cmd = modifycmd({param =>value}) + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end + end + + # Refresh de info. + hash = getinfo(true) + end + + def initialize(resource) + super + @objectinfo = nil + @objectosinfo = nil + end + +end diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index 461968245..5488c6674 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -32,10 +32,14 @@ Puppet::Type.type(:augeas).provide(:augeas) do COMMANDS = { "set" => [ :path, :string ], + "setm" => [ :path, :string, :string ], "rm" => [ :path ], "clear" => [ :path ], + "mv" => [ :path, :path ], "insert" => [ :string, :string, :path ], "get" => [ :path, :comparator, :string ], + "defvar" => [ :string, :path ], + "defnode" => [ :string, :path, :string ], "match" => [ :path, :glob ], "size" => [:comparator, :int], "include" => [:string], @@ -46,6 +50,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do COMMANDS["ins"] = COMMANDS["insert"] COMMANDS["remove"] = COMMANDS["rm"] + COMMANDS["move"] = COMMANDS["mv"] attr_accessor :aug @@ -334,6 +339,10 @@ Puppet::Type.type(:augeas).provide(:augeas) do debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.set(cmd_array[0], cmd_array[1]) fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (!rv) + when "setm" + debug("sending command '#{command}' with params #{cmd_array.inspect}") + rv = aug.setm(cmd_array[0], cmd_array[1], cmd_array[2]) + fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (rv == -1) when "rm", "remove" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.rm(cmd_array[0]) @@ -354,6 +363,18 @@ Puppet::Type.type(:augeas).provide(:augeas) do debug("sending command '#{command}' with params #{[label, where, path].inspect}") rv = aug.insert(path, label, before) fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (rv == -1) + when "defvar" + debug("sending command '#{command}' with params #{cmd_array.inspect}") + rv = aug.defvar(cmd_array[0], cmd_array[1]) + fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (!rv) + when "defnode" + debug("sending command '#{command}' with params #{cmd_array.inspect}") + rv = aug.defnode(cmd_array[0], cmd_array[1], cmd_array[2]) + fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (!rv) + when "mv", "move" + debug("sending command '#{command}' with params #{cmd_array.inspect}") + rv = aug.mv(cmd_array[0], cmd_array[1]) + fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (rv == -1) else fail("Command '#{command}' is not supported") end rescue SystemExit,NoMemoryError diff --git a/lib/puppet/provider/file/posix.rb b/lib/puppet/provider/file/posix.rb index f7b8c9797..7b7336b9d 100644 --- a/lib/puppet/provider/file/posix.rb +++ b/lib/puppet/provider/file/posix.rb @@ -54,7 +54,7 @@ Puppet::Type.type(:file).provide :posix do end def retrieve(resource) - unless stat = resource.stat(false) + unless stat = resource.stat return :absent end diff --git a/lib/puppet/provider/file/win32.rb b/lib/puppet/provider/file/win32.rb index 21e7ca974..9423e8f00 100644 --- a/lib/puppet/provider/file/win32.rb +++ b/lib/puppet/provider/file/win32.rb @@ -49,7 +49,7 @@ Puppet::Type.type(:file).provide :microsoft_windows do end def retrieve(resource) - unless stat = resource.stat(false) + unless stat = resource.stat return :absent end diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb new file mode 100755 index 000000000..ecdef6070 --- /dev/null +++ b/lib/puppet/provider/group/aix.rb @@ -0,0 +1,141 @@ +# +# Group Puppet provider for AIX. It uses standard commands to manage groups: +# mkgroup, rmgroup, lsgroup, chgroup +# +# Author:: Hector Rivas Gandara <keymon@gmail.com> +# +require 'puppet/provider/aixobject' + +Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject do + desc "Group management for AIX! Users are managed with mkgroup, rmgroup, lsgroup, chgroup" + + # This will the the default provider for this platform + defaultfor :operatingsystem => :aix + confine :operatingsystem => :aix + + # Provider features + has_features :manages_aix_lam + has_features :manages_members + + # Commands that manage the element + commands :list => "/usr/sbin/lsgroup" + commands :add => "/usr/bin/mkgroup" + commands :delete => "/usr/sbin/rmgroup" + commands :modify => "/usr/bin/chgroup" + + # Group attributes to ignore + def self.attribute_ignore + [] + end + + # AIX attributes to properties mapping. + # + # Valid attributes to be managed by this provider. + # It is a list with of hash + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Method to adapt puppet property to aix command value. Optional. + # :from Method to adapt aix command value to puppet property. Optional + self.attribute_mapping = [ + #:name => :name, + {:aix_attr => :id, :puppet_prop => :gid }, + {:aix_attr => :users, :puppet_prop => :members, + :from => :users_from_attr}, + {:aix_attr => :attributes, :puppet_prop => :attributes}, + ] + + #-------------- + # Command definition + + # Return the IA module arguments based on the resource param ia_load_module + def get_ia_module_args + if @resource[:ia_load_module] + ["-R", @resource[:ia_load_module].to_s] + else + [] + end + end + + def lscmd(value=@resource[:name]) + [self.class.command(:list)] + + self.get_ia_module_args + + [ value] + end + + def lsallcmd() + lscmd("ALL") + end + + def addcmd(extra_attrs = []) + # Here we use the @resource.to_hash to get the list of provided parameters + # Puppet does not call to self.<parameter>= method if it does not exists. + # + # It gets an extra list of arguments to add to the user. + [self.class.command(:add) ] + + self.get_ia_module_args + + self.hash2args(@resource.to_hash) + + extra_attrs + [@resource[:name]] + end + + def modifycmd(hash = property_hash) + args = self.hash2args(hash) + return nil if args.empty? + + [self.class.command(:modify)] + + self.get_ia_module_args + + args + [@resource[:name]] + end + + def deletecmd + [self.class.command(:delete)] + + self.get_ia_module_args + + [@resource[:name]] + end + + + #-------------- + # Overwrite get_arguments to add the attributes arguments + def get_arguments(key, value, mapping, objectinfo) + # In the case of attributes, return a list of key=vlaue + if key == :attributes + raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ + unless value and value.is_a? Hash + return value.select { |k,v| true }.map { |pair| pair.join("=") } + end + super(key, value, mapping, objectinfo) + end + + def filter_attributes(hash) + # Return only not managed attributtes. + hash.select { + |k,v| !self.class.attribute_mapping_from.include?(k) and + !self.class.attribute_ignore.include?(k) + }.inject({}) { + |hash, array| hash[array[0]] = array[1]; hash + } + end + + def attributes + filter_attributes(getosinfo(refresh = false)) + end + + def attributes=(attr_hash) + #self.class.validate(param, value) + param = :attributes + cmd = modifycmd({param => filter_attributes(attr_hash)}) + if cmd + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end + end + end + + # Force convert users it a list. + def users_from_attr(value) + (value.is_a? String) ? value.split(',') : value + end + + +end diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb index 3d09e2849..58b808a8e 100755 --- a/lib/puppet/provider/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb @@ -22,8 +22,12 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do # Remove the symlinks def disable - update_rc "-f", @resource[:name], "remove" - update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." + if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0 + update_rc @resource[:name], "disable" + else + update_rc "-f", @resource[:name], "remove" + update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." + end end def enabled? diff --git a/lib/puppet/provider/service/smf.rb b/lib/puppet/provider/service/smf.rb index 3efb2eb37..f6f221a7c 100755 --- a/lib/puppet/provider/service/smf.rb +++ b/lib/puppet/provider/service/smf.rb @@ -27,7 +27,7 @@ Puppet::Type.type(:service).provide :smf, :parent => :base do end end rescue Puppet::ExecutionFailure => detail - raise Puppet::Error.new( "Cannot config #{self.service} to enable it: #{detail}" ) + raise Puppet::Error.new( "Cannot config #{self.name} to enable it: #{detail}" ) end def enable @@ -54,10 +54,10 @@ Puppet::Type.type(:service).provide :smf, :parent => :base do def startcmd self.setupservice case self.status - when :stopped - [command(:adm), :enable, @resource[:name]] when :maintenance [command(:adm), :clear, @resource[:name]] + else + [command(:adm), :enable, @resource[:name]] end end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb new file mode 100755 index 000000000..032d2b536 --- /dev/null +++ b/lib/puppet/provider/user/aix.rb @@ -0,0 +1,353 @@ +# +# User Puppet provider for AIX. It uses standard commands to manage users: +# mkuser, rmuser, lsuser, chuser +# +# Notes: +# - AIX users can have expiry date defined with minute granularity, +# but puppet does not allow it. There is a ticket open for that (#5431) +# - AIX maximum password age is in WEEKs, not days +# +# See http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development +# for more information +# +# Author:: Hector Rivas Gandara <keymon@gmail.com> +# +require 'puppet/provider/aixobject' +require 'tempfile' +require 'date' + +Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do + desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser" + + # This will the the default provider for this platform + defaultfor :operatingsystem => :aix + confine :operatingsystem => :aix + + # Commands that manage the element + commands :list => "/usr/sbin/lsuser" + commands :add => "/usr/bin/mkuser" + commands :delete => "/usr/sbin/rmuser" + commands :modify => "/usr/bin/chuser" + + commands :lsgroup => "/usr/sbin/lsgroup" + commands :chpasswd => "/bin/chpasswd" + + # Provider features + has_features :manages_aix_lam + has_features :manages_homedir, :manages_passwords + has_features :manages_expiry, :manages_password_age + + # Attribute verification (TODO) + #verify :gid, "GID must be an string or int of a valid group" do |value| + # value.is_a? String || value.is_a? Integer + #end + # + #verify :groups, "Groups must be comma-separated" do |value| + # value !~ /\s/ + #end + + # User attributes to ignore from AIX output. + def self.attribute_ignore + [] + end + + # AIX attributes to properties mapping. + # + # Valid attributes to be managed by this provider. + # It is a list with of hash + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Method to adapt puppet property to aix command value. Optional. + # :from Method to adapt aix command value to puppet property. Optional + self.attribute_mapping = [ + #:name => :name, + {:aix_attr => :pgrp, :puppet_prop => :gid, + :to => :gid_to_attr, :from => :gid_from_attr}, + {:aix_attr => :id, :puppet_prop => :uid}, + {:aix_attr => :groups, :puppet_prop => :groups}, + {:aix_attr => :home, :puppet_prop => :home}, + {:aix_attr => :shell, :puppet_prop => :shell}, + {:aix_attr => :expires, :puppet_prop => :expiry, + :to => :expiry_to_attr, :from => :expiry_from_attr}, + {:aix_attr => :maxage, :puppet_prop => :password_max_age}, + {:aix_attr => :minage, :puppet_prop => :password_min_age}, + {:aix_attr => :attributes, :puppet_prop => :attributes}, + ] + + #-------------- + # Command definition + + # Return the IA module arguments based on the resource param ia_load_module + def get_ia_module_args + if @resource[:ia_load_module] + ["-R", @resource[:ia_load_module].to_s] + else + [] + end + end + + # List groups and Ids + def lsgroupscmd(value=@resource[:name]) + [command(:lsgroup)] + + self.get_ia_module_args + + ["-a", "id", value] + end + + def lscmd(value=@resource[:name]) + [self.class.command(:list)] + self.get_ia_module_args + [ value] + end + + def lsallcmd() + lscmd("ALL") + end + + def addcmd(extra_attrs = []) + # Here we use the @resource.to_hash to get the list of provided parameters + # Puppet does not call to self.<parameter>= method if it does not exists. + # + # It gets an extra list of arguments to add to the user. + [self.class.command(:add)] + self.get_ia_module_args + + self.hash2args(@resource.to_hash) + + extra_attrs + [@resource[:name]] + end + + # Get modify command. Set translate=false if no mapping must be used. + # Needed for special properties like "attributes" + def modifycmd(hash = property_hash) + args = self.hash2args(hash) + return nil if args.empty? + + [self.class.command(:modify)] + self.get_ia_module_args + + args + [@resource[:name]] + end + + def deletecmd + [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]] + end + + #-------------- + # We overwrite the create function to change the password after creation. + def create + super + # Reset the password if needed + self.password = @resource[:password] if @resource[:password] + end + + + def get_arguments(key, value, mapping, objectinfo) + # In the case of attributes, return a list of key=vlaue + if key == :attributes + raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ + unless value and value.is_a? Hash + return value.select { |k,v| true }.map { |pair| pair.join("=") } + end + + super(key, value, mapping, objectinfo) + end + + # Get the groupname from its id + def self.groupname_by_id(gid) + groupname=nil + execute(lsgroupscmd("ALL")).each { |entry| + attrs = self.parse_attr_list(entry, nil) + if attrs and attrs.include? :id and gid == attrs[:id].to_i + groupname = entry.split(" ")[0] + end + } + groupname + end + + # Get the groupname from its id + def groupid_by_name(groupname) + attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) + attrs ? attrs[:id].to_i : nil + end + + # Check that a group exists and is valid + def verify_group(value) + if value.is_a? Integer or value.is_a? Fixnum + groupname = self.groupname_by_id(value) + raise ArgumentError, "AIX group must be a valid existing group" unless groupname + else + raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) + groupname = value + end + groupname + end + + # The user's primary group. Can be specified numerically or by name. + def gid_to_attr(value) + verify_group(value) + end + + # Get the group gid from its name + def gid_from_attr(value) + groupid_by_name(value) + end + + # The expiry date for this user. Must be provided in + # a zero padded YYYY-MM-DD HH:MM format + def expiry_to_attr(value) + # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format + # that is,"%m%d%H%M%y" + newdate = '0' + if value.is_a? String and value!="0000-00-00" + d = DateTime.parse(value, "%Y-%m-%d %H:%M") + newdate = d.strftime("%m%d%H%M%y") + end + newdate + end + + def expiry_from_attr(value) + if value =~ /(..)(..)(..)(..)(..)/ + #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") + #expiry_date = d.strftime("%Y-%m-%d %H:%M") + #expiry_date = d.strftime("%Y-%m-%d") + expiry_date = "20#{$5}-#{$1}-#{$2}" + else + Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \ + unless value == '0' + expiry_date = :absent + end + expiry_date + end + + #-------------------------------- + # Getter and Setter + # When the provider is initialized, create getter/setter methods for each + # property our resource type supports. + # If setter or getter already defined it will not be overwritten + + #- **password** + # The user's password, in whatever encrypted format the local machine + # requires. Be sure to enclose any value that includes a dollar sign ($) + # in single quotes ('). Requires features manages_passwords. + # + # Retrieve the password parsing directly the /etc/security/passwd + def password + password = :absent + user = @resource[:name] + f = File.open("/etc/security/passwd", 'r') + # Skip to the user + f.each { |l| break if l =~ /^#{user}:\s*$/ } + if ! f.eof? + f.each { |l| + # If there is a new user stanza, stop + break if l =~ /^\S*:\s*$/ + # If the password= entry is found, return it + if l =~ /^\s*password\s*=\s*(.*)$/ + password = $1; break; + end + } + end + f.close() + return password + end + + def password=(value) + user = @resource[:name] + + # Puppet execute does not support strings as input, only files. + tmpfile = Tempfile.new('puppet_#{user}_pw') + tmpfile << "#{user}:#{value}\n" + tmpfile.close() + + # Options '-e', '-c', use encrypted password and clear flags + # Must receibe "user:enc_password" as input + # command, arguments = {:failonfail => true, :combine => true} + cmd = [self.class.command(:chpasswd),"-R", self.class.ia_module, + '-e', '-c', user] + begin + execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path }) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + ensure + tmpfile.delete() + end + end + + def filter_attributes(hash) + # Return only not managed attributtes. + hash.select { + |k,v| !self.class.attribute_mapping_from.include?(k) and + !self.class.attribute_ignore.include?(k) + }.inject({}) { + |hash, array| hash[array[0]] = array[1]; hash + } + end + + def attributes + filter_attributes(getosinfo(refresh = false)) + end + + def attributes=(attr_hash) + #self.class.validate(param, value) + param = :attributes + cmd = modifycmd({param => filter_attributes(attr_hash)}) + if cmd + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end + end + end + + #- **comment** + # A description of the user. Generally is a user's full name. + #def comment=(value) + #end + # + #def comment + #end + # UNSUPPORTED + #- **profile_membership** + # Whether specified roles should be treated as the only roles + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + #- **profiles** + # The profiles the user has. Multiple profiles should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **project** + # The name of the project associated with a user Requires features + # manages_solaris_rbac. + # UNSUPPORTED + #- **role_membership** + # Whether specified roles should be treated as the only roles + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + #- **roles** + # The roles the user has. Multiple roles should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **key_membership** + # Whether specified key value pairs should be treated as the only + # attributes + # of the user or whether they should merely + # be treated as the minimum list. Valid values are `inclusive`, + # `minimum`. + # UNSUPPORTED + #- **keys** + # Specify user attributes in an array of keyvalue pairs Requires features + # manages_solaris_rbac. + # UNSUPPORTED + #- **allowdupe** + # Whether to allow duplicate UIDs. Valid values are `true`, `false`. + # UNSUPPORTED + #- **auths** + # The auths the user has. Multiple auths should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **auth_membership** + # Whether specified auths should be treated as the only auths + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + +end diff --git a/lib/puppet/provider/zfs/solaris.rb b/lib/puppet/provider/zfs/solaris.rb index 9aec9d801..b783f9e01 100644 --- a/lib/puppet/provider/zfs/solaris.rb +++ b/lib/puppet/provider/zfs/solaris.rb @@ -31,7 +31,7 @@ Puppet::Type.type(:zfs).provide(:solaris) do end end - [:mountpoint, :recordsize, :aclmode, :aclinherit, :primarycache, :secondarycache, :compression, :copies, :quota, :reservation, :sharenfs, :snapdir].each do |field| + [:aclinherit, :aclmode, :atime, :canmount, :checksum, :compression, :copies, :devices, :exec, :logbias, :mountpoint, :nbmand, :primarycache, :quota, :readonly, :recordsize, :refquota, :refreservation, :reservation, :secondarycache, :setuid, :shareiscsi, :sharenfs, :sharesmb, :snapdir, :version, :volsize, :vscan, :xattr, :zoned, :vscan].each do |field| define_method(field) do zfs(:get, "-H", "-o", "value", field, @resource[:name]).strip end diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb index b9cf6991a..48d8c1f48 100644 --- a/lib/puppet/resource/type.rb +++ b/lib/puppet/resource/type.rb @@ -34,13 +34,13 @@ class Puppet::Resource::Type end def to_pson_data_hash - data = [:code, :doc, :line, :file, :parent].inject({}) do |hash, param| - next hash unless value = self.send(param) + 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 - data['arguments'] = arguments.dup + data['arguments'] = arguments.dup unless arguments.empty? data['name'] = name data['type'] = type diff --git a/lib/puppet/simple_graph.rb b/lib/puppet/simple_graph.rb index e39aa8770..27e068e30 100644 --- a/lib/puppet/simple_graph.rb +++ b/lib/puppet/simple_graph.rb @@ -367,7 +367,7 @@ class Puppet::SimpleGraph return [] unless ns = (options[:direction] == :in) ? @in_to[v] : @out_from[v] (options[:type] == :edges) ? ns.values.flatten : ns.keys end - + # Take container information from another graph and use it # to replace any container vertices with their respective leaves. # This creates direct relationships where there were previously @@ -387,7 +387,7 @@ class Puppet::SimpleGraph children = other.adjacent(container, :direction => :out) # MQR TODO: Luke suggests that it should be possible to refactor the system so that - # container nodes are retained, thus obviating the need for the whit. + # container nodes are retained, thus obviating the need for the whit. children = [whit_class.new(:name => container.name, :catalog => other)] if children.empty? # First create new edges for each of the :in edges @@ -570,7 +570,10 @@ class Puppet::SimpleGraph end def to_yaml_properties - other_vars = instance_variables.reject { |v| %w{@in_to @out_from @upstream_from @downstream_from}.include?(v) } + other_vars = instance_variables. + map {|v| v.to_s}. + reject { |v| %w{@in_to @out_from @upstream_from @downstream_from}.include?(v) } + (other_vars + %w{@vertices @edges}).sort.uniq end diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb index cd695cff8..d3f25b71c 100644 --- a/lib/puppet/transaction/event.rb +++ b/lib/puppet/transaction/event.rb @@ -48,7 +48,7 @@ class Puppet::Transaction::Event end def to_yaml_properties - (YAML_ATTRIBUTES & instance_variables).sort + (YAML_ATTRIBUTES.map {|ya| ya.to_s} & instance_variables.map{|iv| iv.to_s}).sort end private diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index 773df2bb4..3ba488f19 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -220,19 +220,17 @@ module Puppet newparam(:timeout) do desc "The maximum time the command should take. If the command takes longer than the timeout, the command is considered to have failed - and will be stopped. Use any negative number to disable the timeout. + and will be stopped. Use 0 to disable the timeout. The time is specified in seconds." munge do |value| value = value.shift if value.is_a?(Array) - if value.is_a?(String) - unless value =~ /^[-\d.]+$/ - raise ArgumentError, "The timeout must be a number." - end - Float(value) - else - value + begin + value = Float(value) + rescue ArgumentError => e + raise ArgumentError, "The timeout must be a number." end + [value, 0.0].max end defaultto 300 diff --git a/lib/puppet/type/file/ctime.rb b/lib/puppet/type/file/ctime.rb index 24b098703..90d95da64 100644 --- a/lib/puppet/type/file/ctime.rb +++ b/lib/puppet/type/file/ctime.rb @@ -4,7 +4,7 @@ module Puppet def retrieve current_value = :absent - if stat = @resource.stat(false) + if stat = @resource.stat current_value = stat.ctime end current_value diff --git a/lib/puppet/type/file/ensure.rb b/lib/puppet/type/file/ensure.rb index 99652ecc6..0f065da14 100755 --- a/lib/puppet/type/file/ensure.rb +++ b/lib/puppet/type/file/ensure.rb @@ -138,7 +138,7 @@ module Puppet end def retrieve - if stat = @resource.stat(false) + if stat = @resource.stat return stat.ftype.intern else if self.should == :false diff --git a/lib/puppet/type/file/group.rb b/lib/puppet/type/file/group.rb index 5ed5166bc..4d1f2f4e6 100755 --- a/lib/puppet/type/file/group.rb +++ b/lib/puppet/type/file/group.rb @@ -62,7 +62,7 @@ module Puppet end def retrieve - return :absent unless stat = resource.stat(false) + return :absent unless stat = resource.stat currentvalue = stat.gid diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb index 2acd8b359..9f58e6fb0 100755 --- a/lib/puppet/type/file/mode.rb +++ b/lib/puppet/type/file/mode.rb @@ -63,7 +63,7 @@ module Puppet # If we're not following links and we're a link, then we just turn # off mode management entirely. - if stat = @resource.stat(false) + if stat = @resource.stat unless defined?(@fixed) @should &&= @should.collect { |s| self.dirmask(s) } end diff --git a/lib/puppet/type/file/mtime.rb b/lib/puppet/type/file/mtime.rb index 8ca7ed0d6..5952b4b84 100644 --- a/lib/puppet/type/file/mtime.rb +++ b/lib/puppet/type/file/mtime.rb @@ -4,7 +4,7 @@ module Puppet def retrieve current_value = :absent - if stat = @resource.stat(false) + if stat = @resource.stat current_value = stat.mtime end current_value diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb index ea385eec0..1b1a77245 100644 --- a/lib/puppet/type/file/selcontext.rb +++ b/lib/puppet/type/file/selcontext.rb @@ -26,7 +26,7 @@ module Puppet include Puppet::Util::SELinux def retrieve - return :absent unless @resource.stat(false) + return :absent unless @resource.stat context = self.get_selinux_current_context(@resource[:path]) parse_selinux_context(name, context) end diff --git a/lib/puppet/type/file/type.rb b/lib/puppet/type/file/type.rb index 4da54e2cb..864d3b1a4 100755 --- a/lib/puppet/type/file/type.rb +++ b/lib/puppet/type/file/type.rb @@ -5,7 +5,7 @@ module Puppet def retrieve current_value = :absent - if stat = @resource.stat(false) + if stat = @resource.stat current_value = stat.ftype end current_value diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index cde1cfd65..aa96bd9c3 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -15,6 +15,9 @@ module Puppet feature :manages_members, "For directories where membership is an attribute of groups not users." + feature :manages_aix_lam, + "The provider can manage AIX Loadable Authentication Module (LAM) system." + ensurable do desc "Create or remove the group." @@ -95,5 +98,38 @@ module Puppet defaultto false end + + newparam(:ia_load_module, :required_features => :manages_aix_lam) do + desc "The name of the I&A module to use to manage this user" + + defaultto "compat" + end + + newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do + desc "Specify group AIX attributes in an array of keyvalue pairs" + + def membership + :attribute_membership + end + + def delimiter + " " + end + + validate do |value| + raise ArgumentError, "Attributes value pairs must be seperated by an =" unless value.include?("=") + end + end + + newparam(:attribute_membership) do + desc "Whether specified attribute value pairs should be treated as the only attributes + of the user or whether they should merely + be treated as the minimum list." + + newvalues(:inclusive, :minimum) + + defaultto :minimum + end + end end diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index 93a7e96cf..146481fed 100755 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -254,7 +254,7 @@ Puppet::Type.newtype(:tidy) do if parameter files = Puppet::FileServing::Fileset.new(self[:path], parameter).files.collect do |f| - f == "." ? self[:path] : File.join(self[:path], f) + f == "." ? self[:path] : ::File.join(self[:path], f) end else files = [self[:path]] @@ -270,7 +270,7 @@ Puppet::Type.newtype(:tidy) do files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash } files_by_name.keys.sort { |a,b| b <=> b }.each do |path| - dir = File.dirname(path) + dir = ::File.dirname(path) next unless resource = files_by_name[dir] if resource[:require] resource[:require] << Puppet::Resource.new(:file, path) @@ -321,7 +321,7 @@ Puppet::Type.newtype(:tidy) do def stat(path) begin - File.lstat(path) + ::File.lstat(path) rescue Errno::ENOENT => error info "File does not exist" return nil diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index f74e4266f..767959308 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -13,7 +13,7 @@ module Puppet This resource type uses the prescribed native tools for creating groups and generally uses POSIX APIs for retrieving information about them. It does not directly modify `/etc/passwd` or anything. - + **Autorequires:** If Puppet is managing the user's primary group (as provided in the `gid` attribute), the user resource will autorequire that group. If Puppet is managing any role accounts corresponding to the user's roles, the user resource will autorequire those role accounts." feature :allows_duplicates, @@ -39,6 +39,9 @@ module Puppet feature :system_users, "The provider allows you to create system users with lower UIDs." + feature :manages_aix_lam, + "The provider can manage AIX Loadable Authentication Module (LAM) system." + newproperty(:ensure, :parent => Puppet::Property::Ensure) do newvalue(:present, :event => :user_created) do provider.create @@ -445,5 +448,39 @@ module Puppet newproperty(:project, :required_features => :manages_solaris_rbac) do desc "The name of the project associated with a user" end + + newparam(:ia_load_module, :required_features => :manages_aix_lam) do + desc "The name of the I&A module to use to manage this user" + + defaultto "compat" + end + + newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do + desc "Specify user AIX attributes in an array of keyvalue pairs" + + def membership + :attribute_membership + end + + def delimiter + " " + end + + validate do |value| + raise ArgumentError, "Attributes value pairs must be seperated by an =" unless value.include?("=") + end + end + + newparam(:attribute_membership) do + desc "Whether specified attribute value pairs should be treated as the only attributes + of the user or whether they should merely + be treated as the minimum list." + + newvalues(:inclusive, :minimum) + + defaultto :minimum + end + + end end diff --git a/lib/puppet/type/zfs.rb b/lib/puppet/type/zfs.rb index 7123f8ac9..75f821787 100755..100644 --- a/lib/puppet/type/zfs.rb +++ b/lib/puppet/type/zfs.rb @@ -10,52 +10,124 @@ module Puppet desc "The full name for this filesystem. (including the zpool)" end - newproperty(:mountpoint) do - desc "The mountpoint property." - end - - newproperty(:recordsize) do - desc "The recordsize property." + newproperty(:aclinherit) do + desc "The aclinherit property. Values: discard | noallow | restricted | passthrough | passthrough-x" end newproperty(:aclmode) do - desc "The aclmode property." + desc "The aclmode property. Values: discard | groupmask | passthrough" end - newproperty(:aclinherit) do - desc "The aclinherit property." + newproperty(:atime) do + desc "The atime property. Values: on | off" end - newproperty(:primarycache) do - desc "The primarycache property." + newproperty(:canmount) do + desc "The canmount property. Values: on | off | noauto" end - newproperty(:secondarycache) do - desc "The secondarycache property." + newproperty(:checksum) do + desc "The checksum property. Values: on | off | fletcher2 | fletcher4 | sha256" end newproperty(:compression) do - desc "The compression property." + desc "The compression property. Values: on | off | lzjb | gzip | gzip-[1-9] | zle" end newproperty(:copies) do - desc "The copies property." + desc "The copies property. Values: 1 | 2 | 3" + end + + newproperty(:devices) do + desc "The devices property. Values: on | off" + end + + newproperty(:exec) do + desc "The exec property. Values: on | off" + end + + newproperty(:logbias) do + desc "The logbias property. Values: latency | throughput" + end + + newproperty(:mountpoint) do + desc "The mountpoint property. Values: <path> | legacy | none" + end + + newproperty(:nbmand) do + desc "The nbmand property. Values: on | off" + end + + newproperty(:primarycache) do + desc "The primarycache property. Values: all | none | metadata" end newproperty(:quota) do - desc "The quota property." + desc "The quota property. Values: <size> | none" + end + + newproperty(:readonly) do + desc "The readonly property. Values: on | off" + end + + newproperty(:recordsize) do + desc "The recordsize property. Values: 512 to 128k, power of 2" + end + + newproperty(:refquota) do + desc "The refquota property. Values: <size> | none" + end + + newproperty(:refreservation) do + desc "The refreservation property. Values: <size> | none" end newproperty(:reservation) do - desc "The reservation property." + desc "The reservation property. Values: <size> | none" + end + + newproperty(:secondarycache) do + desc "The secondarycache property. Values: all | none | metadata" + end + + newproperty(:setuid) do + desc "The setuid property. Values: on | off" + end + + newproperty(:shareiscsi) do + desc "The shareiscsi property. Values: on | off | type=<type>" end newproperty(:sharenfs) do - desc "The sharenfs property." + desc "The sharenfs property. Values: on | off | share(1M) options" + end + + newproperty(:sharesmb) do + desc "The sharesmb property. Values: on | off | sharemgr(1M) options" end newproperty(:snapdir) do - desc "The snapdir property." + desc "The snapdir property. Values: hidden | visible" + end + + newproperty(:version) do + desc "The version property. Values: 1 | 2 | 3 | 4 | current" + end + + newproperty(:volsize) do + desc "The volsize property. Values: <size>" + end + + newproperty(:vscan) do + desc "The vscan property. Values: on | off" + end + + newproperty(:xattr) do + desc "The xattr property. Values: on | off" + end + + newproperty(:zoned) do + desc "The zoned property. Values: on | off" end autorequire(:zpool) do diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb index 1bae93120..0fc702ccf 100644 --- a/lib/puppet/type/zone.rb +++ b/lib/puppet/type/zone.rb @@ -409,7 +409,7 @@ Puppet::Type.newtype(:zone) do # both as prerequisites. autorequire(:file) do if @parameters.include? :path - [@parameters[:path].value, File.dirname(@parameters[:path].value)] + [@parameters[:path].value, ::File.dirname(@parameters[:path].value)] else nil end diff --git a/lib/puppet/type/zpool.rb b/lib/puppet/type/zpool.rb index 40ee8f286..2da713c2b 100755 --- a/lib/puppet/type/zpool.rb +++ b/lib/puppet/type/zpool.rb @@ -4,6 +4,7 @@ module Puppet class VDev < Property def flatten_and_sort(array) + array = [array] unless array.is_a? Array array.collect { |a| a.split(' ') }.flatten.sort end diff --git a/lib/puppet/util/loadedfile.rb b/lib/puppet/util/loadedfile.rb index 735dba459..d2f5d0923 100755 --- a/lib/puppet/util/loadedfile.rb +++ b/lib/puppet/util/loadedfile.rb @@ -34,10 +34,6 @@ module Puppet # Create the file. Must be passed the file path. def initialize(file) @file = file - unless FileTest.exists?(@file) - raise Puppet::NoSuchFile, - "Can not use a non-existent file for parsing" - end @statted = 0 @stamp = nil @tstamp = stamp @@ -50,7 +46,7 @@ module Puppet @statted = Time.now.to_i begin @stamp = File.stat(@file).ctime - rescue Errno::ENOENT + rescue Errno::ENOENT, Errno::ENOTDIR @stamp = Time.now end end diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index 1173752d9..14478a61a 100644 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -177,8 +177,8 @@ describe Puppet::Application::Master do lambda { @master.setup }.should raise_error(SystemExit) end - it "should tell Puppet.settings to use :main,:ssl and :master category" do - Puppet.settings.expects(:use).with(:main,:master,:ssl) + it "should tell Puppet.settings to use :main,:ssl,:master and :metrics category" do + Puppet.settings.expects(:use).with(:main,:master,:ssl,:metrics) @master.setup end diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index 1ef9cdc50..3b12c953e 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -13,6 +13,14 @@ describe Puppet::FileServing::Fileset, " when initializing" do proc { Puppet::FileServing::Fileset.new("some/file") }.should raise_error(ArgumentError) end + it "should not fail if the path is fully qualified, with a trailing separator" do + path = "/some/path/with/trailing/separator" + path_with_separator = "#{path}#{File::SEPARATOR}" + File.stubs(:lstat).with(path).returns stub('stat') + fileset = Puppet::FileServing::Fileset.new(path_with_separator) + fileset.path.should == path + end + it "should fail if its path does not exist" do File.expects(:lstat).with("/some/file").returns nil proc { Puppet::FileServing::Fileset.new("/some/file") }.should raise_error(ArgumentError) diff --git a/spec/unit/indirector/active_record_spec.rb b/spec/unit/indirector/active_record_spec.rb index 4765cc5ea..4fab17da2 100755 --- a/spec/unit/indirector/active_record_spec.rb +++ b/spec/unit/indirector/active_record_spec.rb @@ -14,10 +14,9 @@ describe Puppet::Indirector::ActiveRecord do @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @active_record_class = Class.new(Puppet::Indirector::ActiveRecord) do - def self.to_s - "Mystuff::Testing" - end + module Testing; end + @active_record_class = class Testing::MyActiveRecord < Puppet::Indirector::ActiveRecord + self end @ar_model = mock 'ar_model' diff --git a/spec/unit/indirector/code_spec.rb b/spec/unit/indirector/code_spec.rb index 452d009f8..1c9e4d2f1 100755 --- a/spec/unit/indirector/code_spec.rb +++ b/spec/unit/indirector/code_spec.rb @@ -4,16 +4,15 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'puppet/indirector/code' describe Puppet::Indirector::Code do - before do + before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @code_class = Class.new(Puppet::Indirector::Code) do - def self.to_s - "Mystuff::Testing" - end + module Testing; end + @code_class = class Testing::MyCode < Puppet::Indirector::Code + self end @searcher = @code_class.new diff --git a/spec/unit/indirector/direct_file_server_spec.rb b/spec/unit/indirector/direct_file_server_spec.rb index 8eab4ad76..5d9af626f 100755 --- a/spec/unit/indirector/direct_file_server_spec.rb +++ b/spec/unit/indirector/direct_file_server_spec.rb @@ -8,16 +8,15 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'puppet/indirector/direct_file_server' describe Puppet::Indirector::DirectFileServer do - before :each do + before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @direct_file_class = Class.new(Puppet::Indirector::DirectFileServer) do - def self.to_s - "Testing::Mytype" - end + module Testing; end + @direct_file_class = class Testing::Mytype < Puppet::Indirector::DirectFileServer + self end @server = @direct_file_class.new diff --git a/spec/unit/indirector/exec_spec.rb b/spec/unit/indirector/exec_spec.rb index d4fb22443..5abb00ae9 100755 --- a/spec/unit/indirector/exec_spec.rb +++ b/spec/unit/indirector/exec_spec.rb @@ -5,17 +5,17 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'puppet/indirector/exec' describe Puppet::Indirector::Exec do - before do + before :all do @indirection = stub 'indirection', :name => :testing Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) - @exec_class = Class.new(Puppet::Indirector::Exec) do - def self.to_s - "Testing::Mytype" - end - + module Testing; end + @exec_class = class Testing::MyTesting < Puppet::Indirector::Exec attr_accessor :command + self end + end + before :each do @searcher = @exec_class.new @searcher.command = ["/echo"] diff --git a/spec/unit/indirector/file_server_spec.rb b/spec/unit/indirector/file_server_spec.rb index a81d50487..079eba0ae 100755 --- a/spec/unit/indirector/file_server_spec.rb +++ b/spec/unit/indirector/file_server_spec.rb @@ -10,18 +10,19 @@ require 'puppet/file_serving/configuration' describe Puppet::Indirector::FileServer do - before :each do + before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @file_server_class = Class.new(Puppet::Indirector::FileServer) do - def self.to_s - "Testing::Mytype" - end + module Testing; end + @file_server_class = class Testing::MyFileServer < Puppet::Indirector::FileServer + self end + end + before :each do @file_server = @file_server_class.new @uri = "puppet://host/my/local/file" diff --git a/spec/unit/indirector/file_spec.rb b/spec/unit/indirector/file_spec.rb index 8fd197eac..96d5b2ae7 100755 --- a/spec/unit/indirector/file_spec.rb +++ b/spec/unit/indirector/file_spec.rb @@ -5,16 +5,15 @@ require 'puppet/indirector/file' describe Puppet::Indirector::File do - before :each do + before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @file_class = Class.new(Puppet::Indirector::File) do - def self.to_s - "Testing::Mytype" - end + module Testing; end + @file_class = class Testing::MyFile < Puppet::Indirector::File + self end @searcher = @file_class.new diff --git a/spec/unit/indirector/ldap_spec.rb b/spec/unit/indirector/ldap_spec.rb index 2178a8709..ab5dab9ce 100755 --- a/spec/unit/indirector/ldap_spec.rb +++ b/spec/unit/indirector/ldap_spec.rb @@ -8,10 +8,9 @@ describe Puppet::Indirector::Ldap do before do @indirection = stub 'indirection', :name => :testing Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @ldap_class = Class.new(Puppet::Indirector::Ldap) do - def self.to_s - "Testing::Mytype" - end + module Testing; end + @ldap_class = class Testing::MyLdap < Puppet::Indirector::Ldap + self end @connection = mock 'ldap' diff --git a/spec/unit/indirector/memory_spec.rb b/spec/unit/indirector/memory_spec.rb index f9010326f..751adb1b6 100755 --- a/spec/unit/indirector/memory_spec.rb +++ b/spec/unit/indirector/memory_spec.rb @@ -14,10 +14,9 @@ describe Puppet::Indirector::Memory do @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @memory_class = Class.new(Puppet::Indirector::Memory) do - def self.to_s - "Mystuff::Testing" - end + module Testing; end + @memory_class = class Testing::MyMemory < Puppet::Indirector::Memory + self end @searcher = @memory_class.new diff --git a/spec/unit/indirector/plain_spec.rb b/spec/unit/indirector/plain_spec.rb index 54c0233f8..dfaa701bd 100755 --- a/spec/unit/indirector/plain_spec.rb +++ b/spec/unit/indirector/plain_spec.rb @@ -10,10 +10,9 @@ describe Puppet::Indirector::Plain do @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - @plain_class = Class.new(Puppet::Indirector::Plain) do - def self.to_s - "Mystuff::Testing" - end + module Testing; end + @plain_class = class Testing::MyPlain < Puppet::Indirector::Plain + self end @searcher = @plain_class.new diff --git a/spec/unit/indirector/queue_spec.rb b/spec/unit/indirector/queue_spec.rb index 49e5e1015..6f5b44b4c 100755 --- a/spec/unit/indirector/queue_spec.rb +++ b/spec/unit/indirector/queue_spec.rb @@ -31,10 +31,9 @@ describe Puppet::Indirector::Queue, :if => Puppet.features.pson? do @model = mock 'model' @indirection = stub 'indirection', :name => :my_queue, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).with(:my_queue).returns(@indirection) - @store_class = Class.new(Puppet::Indirector::Queue) do - def self.to_s - 'MyQueue::MyType' - end + module MyQueue; end + @store_class = class MyQueue::MyType < Puppet::Indirector::Queue + self end @store = @store_class.new diff --git a/spec/unit/indirector/resource_type/parser_spec.rb b/spec/unit/indirector/resource_type/parser_spec.rb index 739e58b35..f86b319f9 100755 --- a/spec/unit/indirector/resource_type/parser_spec.rb +++ b/spec/unit/indirector/resource_type/parser_spec.rb @@ -3,13 +3,15 @@ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') require 'puppet/indirector/resource_type/parser' +require 'puppet_spec/files' describe Puppet::Indirector::ResourceType::Parser do + include PuppetSpec::Files + before do @terminus = Puppet::Indirector::ResourceType::Parser.new @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo") - @krt = Puppet::Resource::TypeCollection.new(@request.environment) - @request.environment.stubs(:known_resource_types).returns @krt + @krt = @request.environment.known_resource_types end it "should be registered with the resource_type indirection" do @@ -17,16 +19,29 @@ describe Puppet::Indirector::ResourceType::Parser do end describe "when finding" do - it "should use the request's environment's list of known resource types" do - @request.environment.known_resource_types.expects(:hostclass).returns nil + it "should return any found type from the request's environment" do + type = Puppet::Resource::Type.new(:hostclass, "foo") + @request.environment.known_resource_types.add(type) - @terminus.find(@request) + @terminus.find(@request).should == type end - it "should return any found type" do - type = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) + it "should attempt to load the type if none is found in memory" do + dir = tmpdir("find_a_type") + FileUtils.mkdir_p(dir) + Puppet[:modulepath] = dir - @terminus.find(@request).should == type + # Make a new request, since we've reset the env + @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar") + + manifest_path = File.join(dir, "foo", "manifests") + FileUtils.mkdir_p(manifest_path) + + File.open(File.join(manifest_path, "bar.pp"), "w") { |f| f.puts "class foo::bar {}" } + + result = @terminus.find(@request) + result.should be_instance_of(Puppet::Resource::Type) + result.name.should == "foo::bar" end it "should return nil if no type can be found" do @@ -52,8 +67,35 @@ describe Puppet::Indirector::ResourceType::Parser do @terminus.search(@request) end - it "should fail if anyther other than '*' was provided as the search key" do - @request.key = "foo*" + it "should return all results if '*' is provided as the search string" do + @request.key = "*" + type = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) + node = @krt.add(Puppet::Resource::Type.new(:node, "bar")) + define = @krt.add(Puppet::Resource::Type.new(:definition, "baz")) + + result = @terminus.search(@request) + result.should be_include(type) + result.should be_include(node) + result.should be_include(define) + end + + it "should treat any search string not '*' as a regex" do + @request.key = "a" + foo = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) + bar = @krt.add(Puppet::Resource::Type.new(:hostclass, "bar")) + baz = @krt.add(Puppet::Resource::Type.new(:hostclass, "baz")) + + result = @terminus.search(@request) + result.should be_include(bar) + result.should be_include(baz) + result.should_not be_include(foo) + end + + it "should fail if a provided search string is not '*' and is not a valid regex" do + @request.key = "*foo*" + + # Add one instance so we don't just get an empty array" + @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) lambda { @terminus.search(@request) }.should raise_error(ArgumentError) end @@ -68,8 +110,41 @@ describe Puppet::Indirector::ResourceType::Parser do result.should be_include(define) end + it "should not return the 'main' class" do + main = @krt.add(Puppet::Resource::Type.new(:hostclass, "")) + + # So there is a return value + foo = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) + + @terminus.search(@request).should_not be_include(main) + end + it "should return nil if no types can be found" do @terminus.search(@request).should be_nil end + + it "should load all resource types from all search paths" do + dir = tmpdir("searching_in_all") + first = File.join(dir, "first") + second = File.join(dir, "second") + FileUtils.mkdir_p(first) + FileUtils.mkdir_p(second) + Puppet[:modulepath] = "#{first}:#{second}" + + # Make a new request, since we've reset the env + @request = Puppet::Indirector::Request.new(:resource_type, :search, "*") + + onepath = File.join(first, "one", "manifests") + FileUtils.mkdir_p(onepath) + twopath = File.join(first, "two", "manifests") + FileUtils.mkdir_p(twopath) + + File.open(File.join(onepath, "oneklass.pp"), "w") { |f| f.puts "class one::oneklass {}" } + File.open(File.join(twopath, "twoklass.pp"), "w") { |f| f.puts "class two::twoklass {}" } + + result = @terminus.search(@request) + result.find { |t| t.name == "one::oneklass" }.should be_instance_of(Puppet::Resource::Type) + result.find { |t| t.name == "two::twoklass" }.should be_instance_of(Puppet::Resource::Type) + end end end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index 547e68dd3..326d85f9d 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -26,19 +26,27 @@ shared_examples_for "a REST http call" do end describe Puppet::Indirector::REST do - before do + before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = stub('model', :supported_formats => %w{}, :convert_from => nil) @instance = stub('model instance', :name= => nil) @indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model) - Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + Puppet::Indirector::Indirection.expects(:instance).returns(@indirection) - @rest_class = Class.new(Puppet::Indirector::REST) do - def self.to_s - "This::Is::A::Test::Class" + module This + module Is + module A + module Test + end + end end end + @rest_class = class This::Is::A::Test::Class < Puppet::Indirector::REST + self + end + end + before :each do @response = stub('mock response', :body => 'result', :code => "200") @response.stubs(:[]).with('content-type').returns "text/plain" @response.stubs(:[]).with('content-encoding').returns nil diff --git a/spec/unit/indirector/ssl_file_spec.rb b/spec/unit/indirector/ssl_file_spec.rb index 4549127f9..ca97cf49e 100755 --- a/spec/unit/indirector/ssl_file_spec.rb +++ b/spec/unit/indirector/ssl_file_spec.rb @@ -8,15 +8,16 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'puppet/indirector/ssl_file' describe Puppet::Indirector::SslFile do - before do - @model = mock 'model' + before :all do @indirection = stub 'indirection', :name => :testing, :model => @model Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) - @file_class = Class.new(Puppet::Indirector::SslFile) do - def self.to_s - "Testing::Mytype" - end + module Testing; end + @file_class = class Testing::MyType < Puppet::Indirector::SslFile + self end + end + before :each do + @model = mock 'model' @setting = :certdir @file_class.store_in @setting diff --git a/spec/unit/indirector/yaml_spec.rb b/spec/unit/indirector/yaml_spec.rb index 35017991b..188e300d6 100755 --- a/spec/unit/indirector/yaml_spec.rb +++ b/spec/unit/indirector/yaml_spec.rb @@ -5,14 +5,15 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'puppet/indirector/yaml' describe Puppet::Indirector::Yaml, " when choosing file location" do - before :each do + before :all do @indirection = stub 'indirection', :name => :my_yaml, :register_terminus_type => nil - Puppet::Indirector::Indirection.stubs(:instance).with(:my_yaml).returns(@indirection) - @store_class = Class.new(Puppet::Indirector::Yaml) do - def self.to_s - "MyYaml::MyType" - end + Puppet::Indirector::Indirection.expects(:instance).with(:my_yaml).returns(@indirection) + module MyYaml; end + @store_class = class MyYaml::MyType < Puppet::Indirector::Yaml + self end + end + before :each do @store = @store_class.new @subject = Object.new diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index 54f5444ee..f3120e16b 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -1,8 +1,11 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'puppet_spec/files' describe Puppet::Module do + include PuppetSpec::Files + before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory @@ -267,17 +270,39 @@ describe Puppet::Module do end it "should return the path to the first found instance in its environment's module paths as its path" do + dir = tmpdir("deep_path") + first = File.join(dir, "first") + second = File.join(dir, "second") + + FileUtils.mkdir_p(first) + FileUtils.mkdir_p(second) + Puppet[:modulepath] = "#{first}:#{second}" + + modpath = File.join(first, "foo") + FileUtils.mkdir_p(modpath) + + # Make a second one, which we shouldn't find + FileUtils.mkdir_p(File.join(second, "foo")) + mod = Puppet::Module.new("foo") - env = mock 'environment' - mod.stubs(:environment).returns env + mod.path.should == modpath + end + + it "should be able to find itself in a directory other than the first directory in the module path" do + dir = tmpdir("deep_path") + first = File.join(dir, "first") + second = File.join(dir, "second") - env.expects(:modulepath).returns %w{/a /b /c} + FileUtils.mkdir_p(first) + FileUtils.mkdir_p(second) + Puppet[:modulepath] = "#{first}:#{second}" - FileTest.expects(:exist?).with("/a/foo").returns false - FileTest.expects(:exist?).with("/b/foo").returns true - FileTest.expects(:exist?).with("/c/foo").never + modpath = File.join(second, "foo") + FileUtils.mkdir_p(modpath) - mod.path.should == "/b/foo" + mod = Puppet::Module.new("foo") + mod.should be_exist + mod.path.should == modpath end it "should be considered existent if it exists in at least one module path" do diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb index 4367e25e6..9d69e99ac 100755 --- a/spec/unit/network/authconfig_spec.rb +++ b/spec/unit/network/authconfig_spec.rb @@ -12,7 +12,7 @@ describe Puppet::Network::AuthConfig do FileTest.stubs(:exists?).returns(true) File.stubs(:stat).returns(stub('stat', :ctime => :now)) - Time.stubs(:now).returns :now + Time.stubs(:now).returns Time.now @authconfig = Puppet::Network::AuthConfig.new("dummy", false) end diff --git a/spec/unit/network/rest_authconfig_spec.rb b/spec/unit/network/rest_authconfig_spec.rb index 270d1d094..e0bcb5af0 100755 --- a/spec/unit/network/rest_authconfig_spec.rb +++ b/spec/unit/network/rest_authconfig_spec.rb @@ -22,7 +22,7 @@ describe Puppet::Network::RestAuthConfig do before :each do FileTest.stubs(:exists?).returns(true) File.stubs(:stat).returns(stub('stat', :ctime => :now)) - Time.stubs(:now).returns :now + Time.stubs(:now).returns Time.now @authconfig = Puppet::Network::RestAuthConfig.new("dummy", false) @authconfig.stubs(:read) diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 153be5f60..05527e70f 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -6,6 +6,7 @@ require 'puppet/node/environment' require 'puppet/util/execution' describe Puppet::Node::Environment do + include PuppetSpec::Files after do Puppet::Node::Environment.clear end @@ -276,16 +277,11 @@ describe Puppet::Node::Environment do describe "when performing initial import" do before do - @parser = stub 'parser', :file= => nil, :string => nil, :parse => nil + @parser = stub 'parser' Puppet::Parser::Parser.stubs(:new).returns @parser @env = Puppet::Node::Environment.new("env") end - it "should create a new parser instance" do - Puppet::Parser::Parser.expects(:new).returns @parser - @env.instance_eval { perform_initial_import } - end - it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet.settings[:code] = "my code" @parser.expects(:string=).with "my code" @@ -294,25 +290,26 @@ describe Puppet::Node::Environment do end it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do - File.stubs(:expand_path).with("/my/file").returns "/my/file" - File.expects(:exist?).with("/my/file").returns true - Puppet.settings[:manifest] = "/my/file" - @parser.expects(:file=).with "/my/file" + filename = tmpfile('myfile') + File.open(filename, 'w'){|f| } + Puppet.settings[:manifest] = filename + @parser.expects(:file=).with filename @parser.expects(:parse) @env.instance_eval { perform_initial_import } end - it "should not attempt to load a manifest if none is present" do - File.stubs(:expand_path).with("/my/file").returns "/my/file" - File.expects(:exist?).with("/my/file").returns false - Puppet.settings[:manifest] = "/my/file" - @parser.expects(:file=).never - @parser.expects(:parse).never + it "should pass the manifest file to the parser even if it does not exist on disk" do + filename = tmpfile('myfile') + Puppet.settings[:code] = "" + Puppet.settings[:manifest] = filename + @parser.expects(:file=).with(filename).once + @parser.expects(:parse).once @env.instance_eval { perform_initial_import } end it "should fail helpfully if there is an error importing" do File.stubs(:exist?).returns true + @parser.expects(:file=).once @parser.expects(:parse).raises ArgumentError lambda { @env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error) end diff --git a/spec/unit/parser/ast/casestatement_spec.rb b/spec/unit/parser/ast/casestatement_spec.rb index a77c04c43..bce3ad801 100755 --- a/spec/unit/parser/ast/casestatement_spec.rb +++ b/spec/unit/parser/ast/casestatement_spec.rb @@ -13,11 +13,14 @@ describe Puppet::Parser::AST::CaseStatement do @test = stub 'test' @test.stubs(:safeevaluate).with(@scope).returns("value") - @option1 = stub 'option1', :eachopt => nil, :default? => false - @option2 = stub 'option2', :eachopt => nil, :default? => false + @option1 = Puppet::Parser::AST::CaseOpt.new({}) + @option1.stubs(:eachopt) + @option1.stubs(:default?).returns false + @option2 = Puppet::Parser::AST::CaseOpt.new({}) + @option2.stubs(:eachopt) + @option2.stubs(:default?).returns false - @options = stub 'options' - @options.stubs(:each).multiple_yields(@option1, @option2) + @options = Puppet::Parser::AST::ASTArray.new(:children => [@option1, @option2]) @casestmt = Puppet::Parser::AST::CaseStatement.new :test => @test, :options => @options end @@ -29,8 +32,6 @@ describe Puppet::Parser::AST::CaseStatement do end it "should scan each option" do - @options.expects(:each).multiple_yields(@option1, @option2) - @casestmt.evaluate(@scope) end @@ -137,12 +138,15 @@ describe Puppet::Parser::AST::CaseStatement do options = tests.collect do |result, values| values = values.collect { |v| AST::Leaf.new :value => v } - AST::CaseOpt.new( - :value => AST::ASTArray.new(:children => values), - - :statements => AST::Leaf.new(:value => result)) + AST::CaseOpt.new( + :value => AST::ASTArray.new(:children => values), + :statements => AST::Leaf.new(:value => result) + ) end - options << AST::CaseOpt.new(:value => AST::Default.new(:value => "default"), :statements => AST::Leaf.new(:value => "default")) + options << AST::CaseOpt.new( + :value => AST::Default.new(:value => "default"), + :statements => AST::Leaf.new(:value => "default") + ) ast = nil param = AST::Variable.new(:value => "testparam") diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 18f9a93b6..e4b18e14b 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -576,18 +576,16 @@ describe Puppet::Parser::Compiler do proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end - it "should tag the catalog with the name of each not-found class" do - @compiler.catalog.expects(:tag).with("notfound") + it "should raise an error if a class is not found" do @scope.expects(:find_hostclass).with("notfound").returns(nil) - @compiler.evaluate_classes(%w{notfound}, @scope) + lambda{ @compiler.evaluate_classes(%w{notfound}, @scope) }.should raise_error(Puppet::Error, /Could not find class/) end - # I wish it would fail - it "should log when it can't find class" do + + it "should raise an error when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses @compiler.topscope.stubs(:find_hostclass).with('foo').returns(nil) - Puppet.expects(:info).with('Could not find class foo for testnode') - @compiler.compile + lambda{ @compiler.compile }.should raise_error(Puppet::Error, /Could not find class foo for testnode/) end end @@ -714,18 +712,6 @@ describe Puppet::Parser::Compiler do Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{MyClass}, @scope, false) end - - it "should return the list of found classes" do - @compiler.catalog.stubs(:tag) - - @compiler.stubs(:add_resource) - @scope.stubs(:find_hostclass).with("notfound").returns(nil) - @scope.stubs(:class_scope).with(@class) - - Puppet::Parser::Resource.stubs(:new).returns(@resource) - @class.stubs :ensure_in_catalog - @compiler.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass} - end end describe "when evaluating AST nodes with no AST nodes present" do diff --git a/spec/unit/parser/functions/create_resources_spec.rb b/spec/unit/parser/functions/create_resources_spec.rb new file mode 100755 index 000000000..d4095b777 --- /dev/null +++ b/spec/unit/parser/functions/create_resources_spec.rb @@ -0,0 +1,135 @@ +require 'puppet' +require File.dirname(__FILE__) + '/../../../spec_helper' + +describe 'function for dynamically creating resources' do + + def get_scope + @topscope = Puppet::Parser::Scope.new + # This is necessary so we don't try to use the compiler to discover our parent. + @topscope.parent = nil + @scope = Puppet::Parser::Scope.new + @scope.compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("floppy", :environment => 'production')) + @scope.parent = @topscope + @compiler = @scope.compiler + end + before :each do + get_scope + Puppet::Parser::Functions.function(:create_resources) + end + + it "should exist" do + Puppet::Parser::Functions.function(:create_resources).should == "function_create_resources" + end + it 'should require two arguments' do + lambda { @scope.function_create_resources(['foo']) }.should raise_error(ArgumentError, 'create_resources(): wrong number of arguments (1; must be 2)') + end + describe 'when creating native types' do + before :each do + Puppet[:code]='notify{test:}' + get_scope + @scope.resource=Puppet::Parser::Resource.new('class', 't', :scope => @scope) + end + it 'empty hash should not cause resources to be added' do + @scope.function_create_resources(['file', {}]) + @compiler.catalog.resources.size == 1 + end + it 'should be able to add' do + @scope.function_create_resources(['file', {'/etc/foo'=>{'ensure'=>'present'}}]) + @compiler.catalog.resource(:file, "/etc/foo")['ensure'].should == 'present' + end + it 'should accept multiple types' do + type_hash = {} + type_hash['foo'] = {'message' => 'one'} + type_hash['bar'] = {'message' => 'two'} + @scope.function_create_resources(['notify', type_hash]) + @compiler.catalog.resource(:notify, "foo")['message'].should == 'one' + @compiler.catalog.resource(:notify, "bar")['message'].should == 'two' + end + it 'should fail to add non-existing type' do + lambda { @scope.function_create_resources(['foo', {}]) }.should raise_error(ArgumentError, 'could not create resource of unknown type foo') + end + it 'should be able to add edges' do + @scope.function_create_resources(['notify', {'foo'=>{'require' => 'Notify[test]'}}]) + @scope.compiler.compile + edge = @scope.compiler.catalog.to_ral.relationship_graph.edges.detect do |edge| + edge.source.title == 'test' + end + edge.source.title.should == 'test' + edge.target.title.should == 'foo' + end + end + describe 'when dynamically creating resource types' do + before :each do + Puppet[:code]= +'define foo($one){notify{$name: message => $one}} +notify{test:} +' + get_scope + @scope.resource=Puppet::Parser::Resource.new('class', 't', :scope => @scope) + Puppet::Parser::Functions.function(:create_resources) + end + it 'should be able to create defined resoure types' do + @scope.function_create_resources(['foo', {'blah'=>{'one'=>'two'}}]) + # still have to compile for this to work... + # I am not sure if this constraint ruins the tests + @scope.compiler.compile + @compiler.catalog.resource(:notify, "blah")['message'].should == 'two' + end + it 'should fail if defines are missing params' do + @scope.function_create_resources(['foo', {'blah'=>{}}]) + lambda { @scope.compiler.compile }.should raise_error(Puppet::ParseError, 'Must pass one to Foo[blah] at line 1') + end + it 'should be able to add multiple defines' do + hash = {} + hash['blah'] = {'one' => 'two'} + hash['blaz'] = {'one' => 'three'} + @scope.function_create_resources(['foo', hash]) + # still have to compile for this to work... + # I am not sure if this constraint ruins the tests + @scope.compiler.compile + @compiler.catalog.resource(:notify, "blah")['message'].should == 'two' + @compiler.catalog.resource(:notify, "blaz")['message'].should == 'three' + end + it 'should be able to add edges' do + @scope.function_create_resources(['foo', {'blah'=>{'one'=>'two', 'require' => 'Notify[test]'}}]) + @scope.compiler.compile + edge = @scope.compiler.catalog.to_ral.relationship_graph.edges.detect do |edge| + edge.source.title == 'test' + end + edge.source.title.should == 'test' + edge.target.title.should == 'blah' + @compiler.catalog.resource(:notify, "blah")['message'].should == 'two' + end + end + describe 'when creating classes' do + before :each do + Puppet[:code]= +'class bar($one){notify{test: message => $one}} +notify{tester:} +' + get_scope + @scope.resource=Puppet::Parser::Resource.new('class', 't', :scope => @scope) + Puppet::Parser::Functions.function(:create_resources) + end + it 'should be able to create classes' do + @scope.function_create_resources(['class', {'bar'=>{'one'=>'two'}}]) + @scope.compiler.compile + @compiler.catalog.resource(:notify, "test")['message'].should == 'two' + @compiler.catalog.resource(:class, "bar").should_not be_nil#['message'].should == 'two' + end + it 'should fail to create non-existing classes' do + lambda { @scope.function_create_resources(['class', {'blah'=>{'one'=>'two'}}]) }.should raise_error(ArgumentError ,'could not find hostclass blah') + end + it 'should be able to add edges' do + @scope.function_create_resources(['class', {'bar'=>{'one'=>'two', 'require' => 'Notify[tester]'}}]) + @scope.compiler.compile + edge = @scope.compiler.catalog.to_ral.relationship_graph.edges.detect do |e| + e.source.title == 'tester' + end + edge.source.title.should == 'tester' + edge.target.title.should == 'test' + #@compiler.catalog.resource(:notify, "blah")['message'].should == 'two' + end + + end +end diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb index 96df61348..bc9e22e48 100755 --- a/spec/unit/parser/lexer_spec.rb +++ b/spec/unit/parser/lexer_spec.rb @@ -676,3 +676,15 @@ describe "Puppet::Parser::Lexer in the old tests when lexing example files" do end end end + +describe "when trying to lex an non-existent file" do + include PuppetSpec::Files + + it "should return an empty list of tokens" do + lexer = Puppet::Parser::Lexer.new + lexer.file = nofile = tmpfile('lexer') + File.exists?(nofile).should == false + + lexer.fullscan.should == [[false,false]] + end +end diff --git a/spec/unit/parser/type_loader_spec.rb b/spec/unit/parser/type_loader_spec.rb index bd41adfb6..12bc1ccd6 100644 --- a/spec/unit/parser/type_loader_spec.rb +++ b/spec/unit/parser/type_loader_spec.rb @@ -93,6 +93,103 @@ describe Puppet::Parser::TypeLoader do end end + describe "when importing all" do + before do + @base = tmpdir("base") + + # Create two module path directories + @modulebase1 = File.join(@base, "first") + FileUtils.mkdir_p(@modulebase1) + @modulebase2 = File.join(@base, "second") + FileUtils.mkdir_p(@modulebase2) + + Puppet[:modulepath] = "#{@modulebase1}:#{@modulebase2}" + end + + def mk_module(basedir, name) + module_dir = File.join(basedir, name) + + # Go ahead and make our manifest directory + FileUtils.mkdir_p(File.join(module_dir, "manifests")) + + return Puppet::Module.new(name) + end + + # We have to pass the base path so that we can + # write to modules that are in the second search path + def mk_manifests(base, mod, type, files) + exts = {"ruby" => ".rb", "puppet" => ".pp"} + files.collect do |file| + name = mod.name + "::" + file.gsub("/", "::") + path = File.join(base, mod.name, "manifests", file + exts[type]) + FileUtils.mkdir_p(File.split(path)[0]) + + # write out the class + if type == "ruby" + File.open(path, "w") { |f| f.print "hostclass '#{name}' do\nend" } + else + File.open(path, "w") { |f| f.print "class #{name} {}" } + end + name + end + end + + it "should load all puppet manifests from all modules in the specified environment" do + @module1 = mk_module(@modulebase1, "one") + @module2 = mk_module(@modulebase2, "two") + + mk_manifests(@modulebase1, @module1, "puppet", %w{a b}) + mk_manifests(@modulebase2, @module2, "puppet", %w{c d}) + + @loader.import_all + + @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type) + end + + it "should load all ruby manifests from all modules in the specified environment" do + @module1 = mk_module(@modulebase1, "one") + @module2 = mk_module(@modulebase2, "two") + + mk_manifests(@modulebase1, @module1, "ruby", %w{a b}) + mk_manifests(@modulebase2, @module2, "ruby", %w{c d}) + + @loader.import_all + + @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type) + end + + it "should not load manifests from duplicate modules later in the module path" do + @module1 = mk_module(@modulebase1, "one") + + # duplicate + @module2 = mk_module(@modulebase2, "one") + + mk_manifests(@modulebase1, @module1, "puppet", %w{a}) + mk_manifests(@modulebase2, @module2, "puppet", %w{c}) + + @loader.import_all + + @loader.environment.known_resource_types.hostclass("one::c").should be_nil + end + + it "should load manifests from subdirectories" do + @module1 = mk_module(@modulebase1, "one") + + mk_manifests(@modulebase1, @module1, "puppet", %w{a a/b a/b/c}) + + @loader.import_all + + @loader.environment.known_resource_types.hostclass("one::a::b").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::a::b::c").should be_instance_of(Puppet::Resource::Type) + end + end + describe "when parsing a file" do before do @parser = Puppet::Parser::Parser.new(@loader.environment) diff --git a/spec/unit/provider/augeas/augeas_spec.rb b/spec/unit/provider/augeas/augeas_spec.rb index 9fcc85660..c65f39097 100644 --- a/spec/unit/provider/augeas/augeas_spec.rb +++ b/spec/unit/provider/augeas/augeas_spec.rb @@ -433,5 +433,59 @@ describe provider_class do @augeas.expects(:close) @provider.execute_changes.should == :executed end + + it "should handle defvar commands" do + command = "defvar myjar Jar/Jar" + context = "/foo/" + @resource.expects(:[]).times(2).returns(command).then.returns(context) + @augeas.expects(:defvar).with("myjar", "/foo/Jar/Jar").returns(true) + @augeas.expects(:save).returns(true) + @augeas.expects(:close) + @provider.execute_changes.should == :executed + end + + it "should pass through augeas variables without context" do + command = ["defvar myjar Jar/Jar","set $myjar/Binks 1"] + context = "/foo/" + @resource.expects(:[]).times(2).returns(command).then.returns(context) + @augeas.expects(:defvar).with("myjar", "/foo/Jar/Jar").returns(true) + # this is the important bit, shouldn't be /foo/$myjar/Binks + @augeas.expects(:set).with("$myjar/Binks", "1").returns(true) + @augeas.expects(:save).returns(true) + @augeas.expects(:close) + @provider.execute_changes.should == :executed + end + + it "should handle defnode commands" do + command = "defnode newjar Jar/Jar[last()+1] Binks" + context = "/foo/" + @resource.expects(:[]).times(2).returns(command).then.returns(context) + @augeas.expects(:defnode).with("newjar", "/foo/Jar/Jar[last()+1]", "Binks").returns(true) + @augeas.expects(:save).returns(true) + @augeas.expects(:close) + @provider.execute_changes.should == :executed + end + + it "should handle mv commands" do + command = "mv Jar/Jar Binks" + context = "/foo/" + @resource.expects(:[]).times(2).returns(command).then.returns(context) + @augeas.expects(:mv).with("/foo/Jar/Jar", "/foo/Binks").returns(true) + @augeas.expects(:save).returns(true) + @augeas.expects(:close) + @provider.execute_changes.should == :executed + end + + it "should handle setm commands" do + command = ["set test[1]/Jar/Jar Foo","set test[2]/Jar/Jar Bar","setm test Jar/Jar Binks"] + context = "/foo/" + @resource.expects(:[]).times(2).returns(command).then.returns(context) + @augeas.expects(:set).with("/foo/test[1]/Jar/Jar", "Foo").returns(true) + @augeas.expects(:set).with("/foo/test[2]/Jar/Jar", "Bar").returns(true) + @augeas.expects(:setm).with("/foo/test", "Jar/Jar", "Binks").returns(true) + @augeas.expects(:save).returns(true) + @augeas.expects(:close) + @provider.execute_changes.should == :executed + end end end diff --git a/spec/unit/provider/service/debian_spec.rb b/spec/unit/provider/service/debian_spec.rb index 8c9522deb..b5edf6882 100755 --- a/spec/unit/provider/service/debian_spec.rb +++ b/spec/unit/provider/service/debian_spec.rb @@ -52,8 +52,20 @@ describe provider_class do end describe "when disabling" do - it "should call update-rc.d twice" do - @provider.expects(:update_rc).twice + it "should be able to disable services with newer sysv-rc versions" do + @provider.stubs(:`).with("dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?").returns "0" + + @provider.expects(:update_rc).with(@resource[:name], "disable") + + @provider.disable + end + + it "should be able to enable services with older sysv-rc versions" do + @provider.stubs(:`).with("dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?").returns "1" + + @provider.expects(:update_rc).with("-f", @resource[:name], "remove") + @provider.expects(:update_rc).with(@resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", ".") + @provider.disable end end diff --git a/spec/unit/provider/service/smf_spec.rb b/spec/unit/provider/service/smf_spec.rb new file mode 100755 index 000000000..40e96ab23 --- /dev/null +++ b/spec/unit/provider/service/smf_spec.rb @@ -0,0 +1,137 @@ +#!/usr/bin/env ruby +# +# Unit testing for the SMF service Provider +# +# author Dominic Cleal +# +require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') + +provider_class = Puppet::Type.type(:service).provider(:smf) + +describe provider_class do + + before(:each) do + # Create a mock resource + @resource = Puppet::Type.type(:service).new( + :name => "/system/myservice", :ensure => :running, :enable => :true) + @provider = provider_class.new(@resource) + + FileTest.stubs(:file?).with('/usr/sbin/svcadm').returns true + FileTest.stubs(:executable?).with('/usr/sbin/svcadm').returns true + FileTest.stubs(:file?).with('/usr/bin/svcs').returns true + FileTest.stubs(:executable?).with('/usr/bin/svcs').returns true + end + + it "should have a restart method" do + @provider.should respond_to(:restart) + end + + it "should have a restartcmd method" do + @provider.should respond_to(:restartcmd) + end + + it "should have a start method" do + @provider.should respond_to(:start) + end + + it "should have a stop method" do + @provider.should respond_to(:stop) + end + + it "should have an enabled? method" do + @provider.should respond_to(:enabled?) + end + + it "should have an enable method" do + @provider.should respond_to(:enable) + end + + it "should have a disable method" do + @provider.should respond_to(:disable) + end + + describe "when checking status" do + it "should call the external command 'svcs /system/myservice' once" do + @provider.expects(:svcs).with('-H', '-o', 'state,nstate', "/system/myservice").returns("online\t-") + @provider.status + end + it "should return stopped if svcs can't find the service" do + @provider.stubs(:svcs).raises(Puppet::ExecutionFailure.new("no svc found")) + @provider.status.should == :stopped + end + it "should return running if online in svcs output" do + @provider.stubs(:svcs).returns("online\t-") + @provider.status.should == :running + end + it "should return stopped if disabled in svcs output" do + @provider.stubs(:svcs).returns("disabled\t-") + @provider.status.should == :stopped + end + it "should return maintenance if in maintenance in svcs output" do + @provider.stubs(:svcs).returns("maintenance\t-") + @provider.status.should == :maintenance + end + it "should return target state if transitioning in svcs output" do + @provider.stubs(:svcs).returns("online\tdisabled") + @provider.status.should == :stopped + end + it "should throw error if it's a legacy service in svcs output" do + @provider.stubs(:svcs).returns("legacy_run\t-") + lambda { @provider.status }.should raise_error(Puppet::Error, "Cannot manage legacy services through SMF") + end + end + + describe "when starting" do + it "should enable the service if it is not enabled" do + @provider.expects(:status).returns :stopped + @provider.expects(:texecute) + @provider.start + end + + it "should always execute external command 'svcadm enable /system/myservice'" do + @provider.stubs(:status).returns :running + @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :enable, "/system/myservice"], true) + @provider.start + end + + it "should execute external command 'svcadm clear /system/myservice' if in maintenance" do + @provider.stubs(:status).returns :maintenance + @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :clear, "/system/myservice"], true) + @provider.start + end + end + + describe "when starting a service with a manifest" do + before(:each) do + @resource = Puppet::Type.type(:service).new(:name => "/system/myservice", :ensure => :running, :enable => :true, :manifest => "/tmp/myservice.xml") + @provider = provider_class.new(@resource) + $CHILD_STATUS.stubs(:exitstatus).returns(1) + end + + it "should import the manifest if service is missing" do + @provider.expects(:svccfg).with(:import, "/tmp/myservice.xml") + @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :enable, "/system/myservice"], true) + @provider.start + end + + it "should handle failures if importing a manifest" do + @provider.expects(:svccfg).raises(Puppet::ExecutionFailure.new("can't svccfg import")) + lambda { @provider.start }.should raise_error(Puppet::Error, "Cannot config /system/myservice to enable it: can't svccfg import") + end + end + + describe "when stopping" do + it "should execute external command 'svcadm disable /system/myservice'" do + @provider.expects(:texecute).with(:stop, ["/usr/sbin/svcadm", :disable, "/system/myservice"], true) + @provider.stop + end + end + + describe "when restarting" do + it "should call 'svcadm restart /system/myservice'" do + @provider.expects(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, "/system/myservice"], true) + @provider.restart + end + end + +end diff --git a/spec/unit/provider/zfs/solaris_spec.rb b/spec/unit/provider/zfs/solaris_spec.rb index 4998ba6fe..84a2be9f6 100755 --- a/spec/unit/provider/zfs/solaris_spec.rb +++ b/spec/unit/provider/zfs/solaris_spec.rb @@ -27,6 +27,7 @@ describe provider_class do describe "when calling add_properties" do it "should add -o and the key=value for each properties with a value" do @resource.stubs(:[]).with(:quota).returns "" + @resource.stubs(:[]).with(:refquota).returns "" @resource.stubs(:[]).with(:mountpoint).returns "/foo" properties = @provider.add_properties properties.include?("-o").should == true diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index e9c203526..41b5554d9 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -55,12 +55,24 @@ describe Puppet::Resource::Type do double_convert.arguments.should == {"one" => nil, "two" => "foo"} end - it "should include any extra attributes" do - @type.file = "/my/file" - @type.line = 50 + 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 - double_convert.file.should == "/my/file" - double_convert.line.should == 50 + it "should not include docs if they are empty" do + @type.doc = "" + @type.to_pson["doc"].should be_nil end end diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb index a1ffb1636..86b824423 100755 --- a/spec/unit/type/exec_spec.rb +++ b/spec/unit/type/exec_spec.rb @@ -307,7 +307,7 @@ describe Puppet::Type.type(:exec) do end describe "when setting timeout" do - [-3.5, -1, 0, 0.1, 1, 10, 4294967295].each do |valid| + [0, 0.1, 1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:timeout] = valid @exec[:timeout].should == valid @@ -319,7 +319,7 @@ describe Puppet::Type.type(:exec) do end end - ['1/2', '1_000_000', '+12', '', 'foo'].each do |invalid| + ['1/2', '', 'foo', '5foo'].each do |invalid| it "should reject '#{invalid}' as invalid" do expect { @exec[:timeout] = invalid }. should raise_error Puppet::Error, /The timeout must be a number/ @@ -337,6 +337,18 @@ describe Puppet::Type.type(:exec) do sleep_exec = Puppet::Type.type(:exec).new(:name => 'sleep 1', :path => ['/bin'], :timeout => '0.2') lambda { sleep_exec.refresh }.should raise_error Puppet::Error, "Command exceeded timeout" end + + it "should convert timeout to a float" do + resource = Puppet::Type.type(:exec).new :command => "/bin/false", :timeout => "12" + resource[:timeout].should be_a(Float) + resource[:timeout].should == 12.0 + end + + it "should munge negative timeouts to 0.0" do + resource = Puppet::Type.type(:exec).new :command => "/bin/false", :timeout => "-12.0" + resource.parameter(:timeout).value.should be_a(Float) + resource.parameter(:timeout).value.should == 0.0 + end end describe "when setting tries" do diff --git a/spec/unit/util/loadedfile_spec.rb b/spec/unit/util/loadedfile_spec.rb index 23d2766d7..92daeb953 100755 --- a/spec/unit/util/loadedfile_spec.rb +++ b/spec/unit/util/loadedfile_spec.rb @@ -6,6 +6,7 @@ require 'tempfile' require 'puppet/util/loadedfile' describe Puppet::Util::LoadedFile do + include PuppetSpec::Files before(:each) do @f = Tempfile.new('loadedfile_test') @f.puts "yayness" @@ -18,6 +19,12 @@ describe Puppet::Util::LoadedFile do @fake_now = Time.now + (2 * Puppet[:filetimeout]) end + it "should accept files that don't exist" do + nofile = tmpfile('testfile') + File.exists?(nofile).should == false + lambda{ Puppet::Util::LoadedFile.new(nofile) }.should_not raise_error + end + it "should recognize when the file has not changed" do # Use fake "now" so that we can be sure changed? actually checks, without sleeping # for Puppet[:filetimeout] seconds. diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index ab54c8cd3..f118dc99b 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -20,8 +20,9 @@ describe RDoc::Parser do @parser.stubs(:scan_top_level) parser = stub 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) - parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) + parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once parser.expects(:file=).with("module/manifests/init.pp") + parser.expects(:file=).with("/dev/null/manifests/site.pp") @parser.scan end @@ -29,7 +30,7 @@ describe RDoc::Parser do it "should scan the ast for Puppet files" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) - parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) + parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @@ -39,7 +40,7 @@ describe RDoc::Parser do it "should return a PuppetTopLevel to RDoc" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) - parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) + parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) diff --git a/tasks/rake/git_workflow.rake b/tasks/rake/git_workflow.rake index 75dc8833a..56a414568 100644 --- a/tasks/rake/git_workflow.rake +++ b/tasks/rake/git_workflow.rake @@ -119,12 +119,14 @@ task :mail_patches do # If we've got more than one patch, add --compose if files.length > 1 compose = "--compose" + subject = "--subject \"#{type} #{name} against #{parent}\"" else compose = "" + subject = "" end # Now send the mail. - sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" + sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" # Finally, clean up the patches sh "rm 00*.patch" diff --git a/test/language/functions.rb b/test/language/functions.rb index 3c27f2b2b..e882b68f3 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -447,7 +447,7 @@ class TestLangFunctions < Test::Unit::TestCase include = Puppet::Parser::Functions.function(:include) - assert_raise(Puppet::ParseError, "did not throw error on missing class") do + assert_raise(Puppet::Error, "did not throw error on missing class") do scope.function_include("nosuchclass") end |
