diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb index 83b75e28f..d581bbf5d 100644 --- a/lib/puppet/file_serving/configuration/parser.rb +++ b/lib/puppet/file_serving/configuration/parser.rb @@ -1,115 +1,115 @@ require 'puppet/file_serving/configuration' require 'puppet/util/loadedfile' class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile Mount = Puppet::FileServing::Mount MODULES = 'modules' # Parse our configuration file. def parse raise("File server configuration #{self.file} does not exist") unless FileTest.exists?(self.file) raise("Cannot read file server configuration #{self.file}") unless FileTest.readable?(self.file) @mounts = {} @count = 0 File.open(self.file) { |f| mount = nil - f.each { |line| + f.each_line { |line| # Have the count increment at the top, in case we throw exceptions. @count += 1 case line when /^\s*#/; next # skip comments when /^\s*$/; next # skip blank lines when /\[([-\w]+)\]/ mount = newmount($1) when /^\s*(\w+)\s+(.+?)(\s*#.*)?$/ var = $1 value = $2 value.strip! raise(ArgumentError, "Fileserver configuration file does not use '=' as a separator") if value =~ /^=/ case var when "path" path(mount, value) when "allow" allow(mount, value) when "deny" deny(mount, value) else raise ArgumentError.new("Invalid argument '#{var}'", @count, file) end else raise ArgumentError.new("Invalid line '#{line.chomp}'", @count, file) end } } validate @mounts end private # Allow a given pattern access to a mount. def allow(mount, value) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = value.split(/\s*,\s*/).each { |val| begin mount.info "allowing #{val} access" mount.allow(val) rescue Puppet::AuthStoreError => detail raise ArgumentError.new(detail.to_s, @count, file) end } end # Deny a given pattern access to a mount. def deny(mount, value) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = value.split(/\s*,\s*/).each { |val| begin mount.info "denying #{val} access" mount.deny(val) rescue Puppet::AuthStoreError => detail raise ArgumentError.new(detail.to_s, @count, file) end } end # Create a new mount. def newmount(name) raise ArgumentError, "#{@mounts[name]} is already mounted at #{name}", @count, file if @mounts.include?(name) case name when "modules" mount = Mount::Modules.new(name) when "plugins" mount = Mount::Plugins.new(name) else mount = Mount::File.new(name) end @mounts[name] = mount mount end # Set the path for a mount. def path(mount, value) if mount.respond_to?(:path=) begin mount.path = value rescue ArgumentError => detail Puppet.err "Removing mount #{mount.name}: #{detail}" @mounts.delete(mount.name) end else Puppet.warning "The '#{mount.name}' module can not have a path. Ignoring attempt to set it" end end # Make sure all of our mounts are valid. We have to do this after the fact # because details are added over time as the file is parsed. def validate @mounts.each { |name, mount| mount.validate } end end diff --git a/lib/puppet/indirector/file_bucket_file/file.rb b/lib/puppet/indirector/file_bucket_file/file.rb index 6f6b6ff6f..231940d11 100644 --- a/lib/puppet/indirector/file_bucket_file/file.rb +++ b/lib/puppet/indirector/file_bucket_file/file.rb @@ -1,136 +1,136 @@ require 'puppet/indirector/code' require 'puppet/file_bucket/file' require 'puppet/util/checksums' require 'fileutils' module Puppet::FileBucketFile class File < Puppet::Indirector::Code include Puppet::Util::Checksums desc "Store files in a directory set based on their checksums." def initialize Puppet.settings.use(:filebucket) end def find( request ) checksum, files_original_path = request_to_checksum_and_path( request ) dir_path = path_for(request.options[:bucket_path], checksum) file_path = ::File.join(dir_path, 'contents') return nil unless ::File.exists?(file_path) return nil unless path_match(dir_path, files_original_path) if request.options[:diff_with] hash_protocol = sumtype(checksum) file2_path = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents') raise "could not find diff_with #{request.options[:diff_with]}" unless ::File.exists?(file2_path) return `diff #{file_path.inspect} #{file2_path.inspect}` else contents = IO.binread(file_path) Puppet.info "FileBucket read #{checksum}" model.new(contents) end end def head(request) checksum, files_original_path = request_to_checksum_and_path(request) dir_path = path_for(request.options[:bucket_path], checksum) ::File.exists?(::File.join(dir_path, 'contents')) and path_match(dir_path, files_original_path) end def save( request ) instance = request.instance checksum, files_original_path = request_to_checksum_and_path(request) save_to_disk(instance, files_original_path) instance.to_s end private def path_match(dir_path, files_original_path) return true unless files_original_path # if no path was provided, it's a match paths_path = ::File.join(dir_path, 'paths') return false unless ::File.exists?(paths_path) ::File.open(paths_path) do |f| - f.each do |line| + f.each_line do |line| return true if line.chomp == files_original_path end end return false end def save_to_disk( bucket_file, files_original_path ) filename = path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents') dir_path = path_for(bucket_file.bucket_path, bucket_file.checksum_data) paths_path = ::File.join(dir_path, 'paths') # If the file already exists, do nothing. if ::File.exist?(filename) verify_identical_file!(bucket_file) else # Make the directories if necessary. unless ::File.directory?(dir_path) Puppet::Util.withumask(0007) do ::FileUtils.mkdir_p(dir_path) end end Puppet.info "FileBucket adding #{bucket_file.checksum}" # Write the file to disk. Puppet::Util.withumask(0007) do ::File.open(filename, ::File::WRONLY|::File::CREAT, 0440) do |of| of.binmode of.print bucket_file.contents end ::File.open(paths_path, ::File::WRONLY|::File::CREAT, 0640) do |of| # path will be written below end end end unless path_match(dir_path, files_original_path) ::File.open(paths_path, 'a') do |f| f.puts(files_original_path) end end end def request_to_checksum_and_path( request ) checksum_type, checksum, path = request.key.split(/\//, 3) if path == '' # Treat "md5//" like "md5/" path = nil end raise "Unsupported checksum type #{checksum_type.inspect}" if checksum_type != 'md5' raise "Invalid checksum #{checksum.inspect}" if checksum !~ /^[0-9a-f]{32}$/ [checksum, path] end def path_for(bucket_path, digest, subfile = nil) bucket_path ||= Puppet[:bucketdir] dir = ::File.join(digest[0..7].split("")) basedir = ::File.join(bucket_path, dir, digest) return basedir unless subfile ::File.join(basedir, subfile) end # If conflict_check is enabled, verify that the passed text is # the same as the text in our file. def verify_identical_file!(bucket_file) disk_contents = IO.binread(path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents')) # If the contents don't match, then we've found a conflict. # Unlikely, but quite bad. if disk_contents != bucket_file.contents raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}" else Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}" end end end end diff --git a/lib/puppet/network/authconfig.rb b/lib/puppet/network/authconfig.rb index 1e486a2f9..ba6c225b9 100644 --- a/lib/puppet/network/authconfig.rb +++ b/lib/puppet/network/authconfig.rb @@ -1,174 +1,174 @@ require 'puppet/util/loadedfile' require 'puppet/network/rights' module Puppet class ConfigurationError < Puppet::Error; end class Network::AuthConfig < Puppet::Util::LoadedFile def self.main @main ||= self.new end # Just proxy the setting methods to our rights stuff [:allow, :deny].each do |method| define_method(method) do |*args| @rights.send(method, *args) end end # Here we add a little bit of semantics. They can set auth on a whole # namespace or on just a single method in the namespace. def allowed?(request) name = request.call.intern namespace = request.handler.intern method = request.method.intern read if @rights.include?(name) return @rights[name].allowed?(request.name, request.ip) elsif @rights.include?(namespace) return @rights[namespace].allowed?(request.name, request.ip) end false end # Does the file exist? Puppetmasterd does not require it, but # puppet agent does. def exists? FileTest.exists?(@file) end def initialize(file = nil, parsenow = true) @file = file || Puppet[:authconfig] raise Puppet::DevError, "No authconfig file defined" unless @file return unless self.exists? super(@file) @rights = Puppet::Network::Rights.new @configstamp = @configstatted = nil @configtimeout = 60 read if parsenow end # Read the configuration file. def read return unless FileTest.exists?(@file) if @configstamp if @configtimeout and @configstatted if Time.now - @configstatted > @configtimeout @configstatted = Time.now tmp = File.stat(@file).ctime if tmp == @configstamp return else Puppet.notice "#{tmp} vs #{@configstamp}" end else return end else Puppet.notice "#{@configtimeout} and #{@configstatted}" end end parse @configstamp = File.stat(@file).ctime @configstatted = Time.now end private def parse newrights = Puppet::Network::Rights.new begin File.open(@file) { |f| right = nil count = 1 - f.each { |line| + f.each_line { |line| case line when /^\s*#/ # skip comments count += 1 next when /^\s*$/ # skip blank lines count += 1 next when /^(?:(\[[\w.]+\])|(path)\s+((?:~\s+)?[^ ]+))\s*$/ # "namespace" or "namespace.method" or "path /path" or "path ~ regex" name = $1 name = $3 if $2 == "path" name.chomp! right = newrights.newright(name, count, @file) when /^\s*(allow|deny|method|environment|auth(?:enticated)?)\s+(.+?)(\s*#.*)?$/ parse_right_directive(right, $1, $2, count) else raise ConfigurationError, "Invalid line #{count}: #{line}" end count += 1 } } rescue Errno::EACCES => detail Puppet.err "Configuration error: Cannot read #{@file}; cannot serve" #raise Puppet::Error, "Cannot read #{@config}" rescue Errno::ENOENT => detail Puppet.err "Configuration error: '#{@file}' does not exit; cannot serve" #raise Puppet::Error, "#{@config} does not exit" #rescue FileServerError => detail # Puppet.err "FileServer error: #{detail}" end # Verify each of the rights are valid. # We let the check raise an error, so that it can raise an error # pointing to the specific problem. newrights.each { |name, right| right.valid? } @rights = newrights end def parse_right_directive(right, var, value, count) value.strip! case var when "allow" modify_right(right, :allow, value, "allowing %s access", count) when "deny" modify_right(right, :deny, value, "denying %s access", count) when "method" unless right.acl_type == :regex raise ConfigurationError, "'method' directive not allowed in namespace ACL at line #{count} of #{@config}" end modify_right(right, :restrict_method, value, "allowing 'method' %s", count) when "environment" unless right.acl_type == :regex raise ConfigurationError, "'environment' directive not allowed in namespace ACL at line #{count} of #{@config}" end modify_right(right, :restrict_environment, value, "adding environment %s", count) when /auth(?:enticated)?/ unless right.acl_type == :regex raise ConfigurationError, "'authenticated' directive not allowed in namespace ACL at line #{count} of #{@config}" end modify_right(right, :restrict_authenticated, value, "adding authentication %s", count) else raise ConfigurationError, "Invalid argument '#{var}' at line #{count}" end end def modify_right(right, method, value, msg, count) value.split(/\s*,\s*/).each do |val| begin val.strip! right.info msg % val right.send(method, val) rescue AuthStoreError => detail raise ConfigurationError, "#{detail} at line #{count} of #{@file}" end end end end end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index 8fe3da29a..5548f40fb 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -1,732 +1,732 @@ require 'puppet' require 'puppet/network/authstore' require 'webrick/httpstatus' require 'cgi' require 'delegate' require 'sync' require 'puppet/network/handler' require 'puppet/network/handler' require 'puppet/network/xmlrpc/server' require 'puppet/file_serving' require 'puppet/file_serving/metadata' require 'puppet/network/handler' class Puppet::Network::Handler AuthStoreError = Puppet::AuthStoreError class FileServerError < Puppet::Error; end class FileServer < Handler desc "The interface to Puppet's fileserving abilities." attr_accessor :local CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] # Special filserver module for puppet's module system MODULES = "modules" PLUGINS = "plugins" @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| iface.add_method("string describe(string, string)") iface.add_method("string list(string, string, boolean, array)") iface.add_method("string retrieve(string, string)") } def self.params CHECKPARAMS.dup end # If the configuration file exists, then create (if necessary) a LoadedFile # object to manage it; else, return nil. def configuration # Short-circuit the default case. return @configuration if defined?(@configuration) config_path = @passed_configuration_path || Puppet[:fileserverconfig] return nil unless FileTest.exist?(config_path) # The file exists but we don't have a LoadedFile instance for it. @configuration = Puppet::Util::LoadedFile.new(config_path) end # Create our default mounts for modules and plugins. This is duplicated code, # but I'm not really worried about that. def create_default_mounts @mounts = {} Puppet.debug "No file server configuration file; autocreating #{MODULES} mount with default permissions" mount = Mount.new(MODULES) mount.allow("*") @mounts[MODULES] = mount Puppet.debug "No file server configuration file; autocreating #{PLUGINS} mount with default permissions" mount = PluginMount.new(PLUGINS) mount.allow("*") @mounts[PLUGINS] = mount end # Describe a given file. This returns all of the manageable aspects # of that file. def describe(url, links = :follow, client = nil, clientip = nil) links = links.intern if links.is_a? String mount, path = convert(url, client, clientip) mount.debug("Describing #{url} for #{client}") if client # use the mount to resolve the path for us. return "" unless full_path = mount.file_path(path, client) metadata = Puppet::FileServing::Metadata.new(url, :path => full_path, :links => links) return "" unless metadata.exist? begin metadata.collect rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail return "" end metadata.attributes_with_tabs end # Create a new fileserving module. def initialize(hash = {}) @mounts = {} @files = {} @local = hash[:Local] @noreadconfig = true if hash[:Config] == false @passed_configuration_path = hash[:Config] if hash.include?(:Mount) @passedconfig = true raise Puppet::DevError, "Invalid mount hash #{hash[:Mount].inspect}" unless hash[:Mount].is_a?(Hash) hash[:Mount].each { |dir, name| self.mount(dir, name) if FileTest.exists?(dir) } self.mount(nil, MODULES) self.mount(nil, PLUGINS) else @passedconfig = false if configuration readconfig(false) # don't check the file the first time. else create_default_mounts end end end # List a specific directory's contents. def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil) mount, path = convert(url, client, clientip) mount.debug "Listing #{url} for #{client}" if client return "" unless mount.path_exists?(path, client) desc = mount.list(path, recurse, ignore, client) if desc.length == 0 mount.notice "Got no information on //#{mount}/#{path}" return "" end desc.collect { |sub| sub.join("\t") }.join("\n") end def local? self.local end # Is a given mount available? def mounted?(name) @mounts.include?(name) end # Mount a new directory with a name. def mount(path, name) if @mounts.include?(name) if @mounts[name] != path raise FileServerError, "#{@mounts[name].path} is already mounted at #{name}" else # it's already mounted; no problem return end end # Let the mounts do their own error-checking. @mounts[name] = Mount.new(name, path) @mounts[name].info "Mounted #{path}" @mounts[name] end # Retrieve a file from the local disk and pass it to the remote # client. def retrieve(url, links = :ignore, client = nil, clientip = nil) links = links.intern if links.is_a? String mount, path = convert(url, client, clientip) mount.info "Sending #{url} to #{client}" if client unless mount.path_exists?(path, client) mount.debug "#{mount} reported that #{path} does not exist" return "" end links = links.intern if links.is_a? String if links == :ignore and FileTest.symlink?(path) mount.debug "I think that #{path} is a symlink and we're ignoring them" return "" end str = mount.read_file(path, client) if @local return str else return CGI.escape(str) end end def umount(name) @mounts.delete(name) if @mounts.include? name end private def authcheck(file, mount, client, clientip) # If we're local, don't bother passing in information. if local? client = nil clientip = nil end unless mount.allowed?(client, clientip) mount.warning "#{client} cannot access #{file}" raise Puppet::AuthorizationError, "Cannot access #{mount}" end end # Take a URL and some client info and return a mount and relative # path pair. # def convert(url, client, clientip) readconfig url = URI.unescape(url) mount, stub = splitpath(url, client) authcheck(url, mount, client, clientip) return mount, stub end # Return the mount for the Puppet modules; allows file copying from # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. unless hostname = (client || Facter.value("hostname")) raise ArgumentError, "Could not find hostname" end env = (node = Puppet::Node.indirection.find(hostname)) ? node.environment : nil # And use the environment to look up the module. (mod = Puppet::Node::Environment.new(env).module(module_name) and mod.files?) ? @mounts[MODULES].copy(mod.name, mod.file_directory) : nil end # Read the configuration file. def readconfig(check = true) return if @noreadconfig return unless configuration return if check and ! @configuration.changed? newmounts = {} begin File.open(@configuration.file) { |f| mount = nil count = 1 - f.each { |line| + f.each_line { |line| case line when /^\s*#/; next # skip comments when /^\s*$/; next # skip blank lines when /\[([-\w]+)\]/ name = $1 raise FileServerError, "#{newmounts[name]} is already mounted as #{name} in #{@configuration.file}" if newmounts.include?(name) mount = Mount.new(name) newmounts[name] = mount when /^\s*(\w+)\s+(.+)$/ var = $1 value = $2 case var when "path" raise FileServerError.new("No mount specified for argument #{var} #{value}") unless mount if mount.name == MODULES Puppet.warning "The '#{mount.name}' module can not have a path. Ignoring attempt to set it" else begin mount.path = value rescue FileServerError => detail Puppet.err "Removing mount #{mount.name}: #{detail}" newmounts.delete(mount.name) end end when "allow" raise FileServerError.new("No mount specified for argument #{var} #{value}") unless mount value.split(/\s*,\s*/).each { |val| begin mount.info "allowing #{val} access" mount.allow(val) rescue AuthStoreError => detail puts detail.backtrace if Puppet[:trace] raise FileServerError.new( detail.to_s, count, @configuration.file) end } when "deny" raise FileServerError.new("No mount specified for argument #{var} #{value}") unless mount value.split(/\s*,\s*/).each { |val| begin mount.info "denying #{val} access" mount.deny(val) rescue AuthStoreError => detail raise FileServerError.new( detail.to_s, count, @configuration.file) end } else raise FileServerError.new("Invalid argument '#{var}'", count, @configuration.file) end else raise FileServerError.new("Invalid line '#{line.chomp}'", count, @configuration.file) end count += 1 } } rescue Errno::EACCES => detail Puppet.err "FileServer error: Cannot read #{@configuration}; cannot serve" #raise Puppet::Error, "Cannot read #{@configuration}" rescue Errno::ENOENT => detail Puppet.err "FileServer error: '#{@configuration}' does not exist; cannot serve" end unless newmounts[MODULES] Puppet.debug "No #{MODULES} mount given; autocreating with default permissions" mount = Mount.new(MODULES) mount.allow("*") newmounts[MODULES] = mount end unless newmounts[PLUGINS] Puppet.debug "No #{PLUGINS} mount given; autocreating with default permissions" mount = PluginMount.new(PLUGINS) mount.allow("*") newmounts[PLUGINS] = mount end unless newmounts[PLUGINS].valid? Puppet.debug "No path given for #{PLUGINS} mount; creating a special PluginMount" # We end up here if the user has specified access rules for # the plugins mount, without specifying a path (which means # they want to have the default behaviour for the mount, but # special access control). So we need to move all the # user-specified access controls into the new PluginMount # object... mount = PluginMount.new(PLUGINS) # Yes, you're allowed to hate me for this. mount.instance_variable_set( :@declarations, newmounts[PLUGINS].instance_variable_get(:@declarations) ) newmounts[PLUGINS] = mount end # Verify each of the mounts are valid. # We let the check raise an error, so that it can raise an error # pointing to the specific problem. newmounts.each { |name, mount| raise FileServerError, "Invalid mount #{name}" unless mount.valid? } @mounts = newmounts end # Split the path into the separate mount point and path. def splitpath(dir, client) # the dir is based on one of the mounts # so first retrieve the mount path mount = nil path = nil if dir =~ %r{/([-\w]+)} # Strip off the mount name. mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2) unless mount = modules_mount(mount_name, client) unless mount = @mounts[mount_name] raise FileServerError, "Fileserver module '#{mount_name}' not mounted" end end else raise FileServerError, "Fileserver error: Invalid path '#{dir}'" end if path.nil? or path == '' path = '/' elsif path # Remove any double slashes that might have occurred path = URI.unescape(path.gsub(/\/\//, "/")) end return mount, path end def to_s "fileserver" end # A simple class for wrapping mount points. Instances of this class # don't know about the enclosing object; they're mainly just used for # authorization. class Mount < Puppet::Network::AuthStore attr_reader :name @@syncs = {} @@files = {} Puppet::Util.logmethods(self, true) # Create a map for a specific client. def clientmap(client) { "h" => client.sub(/\..*$/, ""), "H" => client, "d" => client.sub(/[^.]+\./, "") # domain name } end # Replace % patterns as appropriate. def expand(path, client = nil) # This map should probably be moved into a method. map = nil if client map = clientmap(client) else Puppet.notice "No client; expanding '#{path}' with local host" # Else, use the local information map = localmap end path.gsub(/%(.)/) do |v| key = $1 if key == "%" "%" else map[key] || v end end end # Do we have any patterns in our path, yo? def expandable? if defined?(@expandable) @expandable else false end end # Return a fully qualified path, given a short path and # possibly a client name. def file_path(relative_path, node = nil) full_path = path(node) unless full_path p self raise ArgumentError.new("Mounts without paths are not usable") unless full_path end # If there's no relative path name, then we're serving the mount itself. return full_path unless relative_path and relative_path != "/" File.join(full_path, relative_path) end # Create out object. It must have a name. def initialize(name, path = nil) unless name =~ %r{^[-\w]+$} raise FileServerError, "Invalid name format '#{name}'" end @name = name if path self.path = path else @path = nil end @files = {} super() end def fileobj(path, links, client) obj = nil if obj = @files[file_path(path, client)] # This can only happen in local fileserving, but it's an # important one. It'd be nice if we didn't just set # the check params every time, but I'm not sure it's worth # the effort. obj[:audit] = CHECKPARAMS else obj = Puppet::Type.type(:file).new( :name => file_path(path, client), :audit => CHECKPARAMS ) @files[file_path(path, client)] = obj end if links == :manage links = :follow end # This, ah, might be completely redundant obj[:links] = links unless obj[:links] == links obj end # Read the contents of the file at the relative path given. def read_file(relpath, client) File.read(file_path(relpath, client)) end # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap unless defined?(@@localmap) @@localmap = { "h" => Facter.value("hostname"), "H" => [Facter.value("hostname"), Facter.value("domain")].join("."), "d" => Facter.value("domain") } end @@localmap end # Return the path as appropriate, expanding as necessary. def path(client = nil) if expandable? return expand(@path, client) else return @path end end # Set the path. def path=(path) # FIXME: For now, just don't validate paths with replacement # patterns in them. if path =~ /%./ # Mark that we're expandable. @expandable = true else raise FileServerError, "#{path} does not exist" unless FileTest.exists?(path) raise FileServerError, "#{path} is not a directory" unless FileTest.directory?(path) raise FileServerError, "#{path} is not readable" unless FileTest.readable?(path) @expandable = false end @path = path end # Verify that the path given exists within this mount's subtree. # def path_exists?(relpath, client = nil) File.exists?(file_path(relpath, client)) end # Return the current values for the object. def properties(obj) obj.retrieve.inject({}) { |props, ary| props[ary[0].name] = ary[1]; props } end # Retrieve a specific directory relative to a mount point. # If they pass in a client, then expand as necessary. def subdir(dir = nil, client = nil) basedir = self.path(client) dirname = if dir File.join(basedir, *dir.split("/")) else basedir end dirname end def sync(path) @@syncs[path] ||= Sync.new @@syncs[path] end def to_s "mount[#{@name}]" end # Verify our configuration is valid. This should really check to # make sure at least someone will be allowed, but, eh. def valid? if name == MODULES return @path.nil? else return ! @path.nil? end end # Return a new mount with the same properties as +self+, except # with a different name and path. def copy(name, path) result = self.clone result.path = path result.instance_variable_set(:@name, name) result end # List the contents of the relative path +relpath+ of this mount. # # +recurse+ is the number of levels to recurse into the tree, # or false to provide no recursion or true if you just want to # go for broke. # # +ignore+ is an array of filenames to ignore when traversing # the list. # # The return value of this method is a complex nest of arrays, # which describes a directory tree. Each file or directory is # represented by an array, where the first element is the path # of the file (relative to the root of the mount), and the # second element is the type. A directory is represented by an # array as well, where the first element is a "directory" array, # while the remaining elements are other file or directory # arrays. Confusing? Hell yes. As an added bonus, all names # must start with a slash, because... well, I'm fairly certain # a complete explanation would involve the words "crack pipe" # and "bad batch". # def list(relpath, recurse, ignore, client = nil) abspath = file_path(relpath, client) if FileTest.exists?(abspath) if FileTest.directory?(abspath) and recurse return reclist(abspath, recurse, ignore) else return [["/", File.stat(abspath).ftype]] end end nil end def reclist(abspath, recurse, ignore) require 'puppet/file_serving' require 'puppet/file_serving/fileset' if recurse.is_a?(Fixnum) args = { :recurse => true, :recurselimit => recurse, :links => :follow } else args = { :recurse => recurse, :links => :follow } end args[:ignore] = ignore if ignore fs = Puppet::FileServing::Fileset.new(abspath, args) ary = fs.files.collect do |file| if file == "." file = "/" else file = File.join("/", file ) end stat = fs.stat(File.join(abspath, file)) next if stat.nil? [ file, stat.ftype ] end ary.compact end end # A special mount class specifically for the plugins mount -- just # has some magic to effectively do a union mount of the 'plugins' # directory of all modules. # class PluginMount < Mount def path(client) '' end def mod_path_exists?(mod, relpath, client = nil) ! mod.plugin(relpath).nil? end def path_exists?(relpath, client = nil) !valid_modules(client).find { |mod| mod.plugin(relpath) }.nil? end def valid? true end def mod_file_path(mod, relpath, client = nil) File.join(mod, PLUGINS, relpath) end def file_path(relpath, client = nil) return nil unless mod = valid_modules(client).find { |m| m.plugin(relpath) } mod.plugin(relpath) end # create a list of files by merging all modules def list(relpath, recurse, ignore, client = nil) result = [] valid_modules(client).each do |mod| if modpath = mod.plugin(relpath) if FileTest.directory?(modpath) and recurse ary = reclist(modpath, recurse, ignore) ary ||= [] result += ary else result += [["/", File.stat(modpath).ftype]] end end end result end private def valid_modules(client) Puppet::Node::Environment.new.modules.find_all { |mod| mod.exist? } end def add_to_filetree(f, filetree) first, rest = f.split(File::SEPARATOR, 2) end end end end diff --git a/lib/puppet/provider/package/aix.rb b/lib/puppet/provider/package/aix.rb index 088099d45..803296217 100644 --- a/lib/puppet/provider/package/aix.rb +++ b/lib/puppet/provider/package/aix.rb @@ -1,128 +1,128 @@ require 'puppet/provider/package' require 'puppet/util/package' Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package do desc "Installation from the AIX software directory." # The commands we are using on an AIX box are installed standard # (except nimclient) nimclient needs the bos.sysmgt.nim.client fileset. commands :lslpp => "/usr/bin/lslpp", :installp => "/usr/sbin/installp" # AIX supports versionable packages with and without a NIM server has_feature :versionable confine :operatingsystem => [ :aix ] defaultfor :operatingsystem => :aix attr_accessor :latest_info def self.srclistcmd(source) [ command(:installp), "-L", "-d", source ] end def self.prefetch(packages) raise Puppet::Error, "The aix provider can only be used by root" if Process.euid != 0 return unless packages.detect { |name, package| package.should(:ensure) == :latest } sources = packages.collect { |name, package| package[:source] }.uniq updates = {} sources.each do |source| - execute(self.srclistcmd(source)).each do |line| + execute(self.srclistcmd(source)).each_line do |line| if line =~ /^[^#][^:]*:([^:]*):([^:]*)/ current = {} current[:name] = $1 current[:version] = $2 current[:source] = source if updates.key?(current[:name]) previous = updates[current[:name]] updates[ current[:name] ] = current unless Puppet::Util::Package.versioncmp(previous[:version], current[:version]) == 1 else updates[current[:name]] = current end end end end packages.each do |name, package| if info = updates[package[:name]] package.provider.latest_info = info[0] end end end def uninstall # Automatically process dependencies when installing/uninstalling # with the -g option to installp. installp "-gu", @resource[:name] end def install(useversion = true) unless source = @resource[:source] self.fail "A directory is required which will be used to find packages" end pkg = @resource[:name] pkg << " #{@resource.should(:ensure)}" if (! @resource.should(:ensure).is_a? Symbol) and useversion installp "-acgwXY", "-d", source, pkg end def self.pkglist(hash = {}) cmd = [command(:lslpp), "-qLc"] if name = hash[:pkgname] cmd << name end begin list = execute(cmd).scan(/^[^#][^:]*:([^:]*):([^:]*)/).collect { |n,e| { :name => n, :ensure => e, :provider => self.name } } rescue Puppet::ExecutionFailure => detail if hash[:pkgname] return nil else raise Puppet::Error, "Could not list installed Packages: #{detail}" end end if hash[:pkgname] return list.shift else return list end end def self.instances pkglist.collect do |hash| new(hash) end end def latest upd = latest_info unless upd.nil? return "#{upd[:version]}" else raise Puppet::DevError, "Tried to get latest on a missing package" if properties[:ensure] == :absent return properties[:ensure] end end def query self.class.pkglist(:pkgname => @resource[:name]) end def update self.install(false) end end diff --git a/lib/puppet/provider/package/dpkg.rb b/lib/puppet/provider/package/dpkg.rb index 878153a38..3a84d0a2f 100755 --- a/lib/puppet/provider/package/dpkg.rb +++ b/lib/puppet/provider/package/dpkg.rb @@ -1,155 +1,155 @@ require 'puppet/provider/package' Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package do desc "Package management via `dpkg`. Because this only uses `dpkg` and not `apt`, you must specify the source of any packages you want to manage." has_feature :holdable commands :dpkg => "/usr/bin/dpkg" commands :dpkg_deb => "/usr/bin/dpkg-deb" commands :dpkgquery => "/usr/bin/dpkg-query" def self.instances packages = [] # list out all of the packages cmd = "#{command(:dpkgquery)} -W --showformat '${Status} ${Package} ${Version}\\n'" Puppet.debug "Executing '#{cmd}'" execpipe(cmd) do |process| # our regex for matching dpkg output regex = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*)$} fields = [:desired, :error, :status, :name, :ensure] hash = {} # now turn each returned line into a package object - process.each { |line| + process.each_line { |line| if hash = parse_line(line) packages << new(hash) end } end packages end self::REGEX = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*)$} self::FIELDS = [:desired, :error, :status, :name, :ensure] def self.parse_line(line) if match = self::REGEX.match(line) hash = {} self::FIELDS.zip(match.captures) { |field,value| hash[field] = value } hash[:provider] = self.name if hash[:status] == 'not-installed' hash[:ensure] = :purged elsif ['config-files', 'half-installed', 'unpacked', 'half-configured'].include?(hash[:status]) hash[:ensure] = :absent end hash[:ensure] = :held if hash[:desired] == 'hold' else Puppet.warning "Failed to match dpkg-query line #{line.inspect}" return nil end hash end def install unless file = @resource[:source] raise ArgumentError, "You cannot install dpkg packages without a source" end args = [] # We always unhold when installing to remove any prior hold. self.unhold if @resource[:configfiles] == :keep args << '--force-confold' else args << '--force-confnew' end args << '-i' << file dpkg(*args) end def update self.install end # Return the version from the package. def latest output = dpkg_deb "--show", @resource[:source] matches = /^(\S+)\t(\S+)$/.match(output).captures warning "source doesn't contain named package, but #{matches[0]}" unless matches[0].match( Regexp.escape(@resource[:name]) ) matches[1] end def query packages = [] fields = [:desired, :error, :status, :name, :ensure] hash = {} # list out our specific package begin output = dpkgquery( "-W", "--showformat", '${Status} ${Package} ${Version}\\n', @resource[:name] ) rescue Puppet::ExecutionFailure # dpkg-query exits 1 if the package is not found. return {:ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok'} end hash = self.class.parse_line(output) || {:ensure => :absent, :status => 'missing', :name => @resource[:name], :error => 'ok'} if hash[:error] != "ok" raise Puppet::Error.new( "Package #{hash[:name]}, version #{hash[:ensure]} is in error state: #{hash[:error]}" ) end hash end def uninstall dpkg "-r", @resource[:name] end def purge dpkg "--purge", @resource[:name] end def hold self.install begin Tempfile.open('puppet_dpkg_set_selection') { |tmpfile| tmpfile.write("#{@resource[:name]} hold\n") tmpfile.flush execute([:dpkg, "--set-selections"], :stdinfile => tmpfile.path.to_s) } end end def unhold begin Tempfile.open('puppet_dpkg_set_selection') { |tmpfile| tmpfile.write("#{@resource[:name]} install\n") tmpfile.flush execute([:dpkg, "--set-selections"], :stdinfile => tmpfile.path.to_s) } end end end diff --git a/lib/puppet/provider/package/macports.rb b/lib/puppet/provider/package/macports.rb index 22fe6d930..2a78191b7 100755 --- a/lib/puppet/provider/package/macports.rb +++ b/lib/puppet/provider/package/macports.rb @@ -1,105 +1,105 @@ require 'puppet/provider/package' Puppet::Type.type(:package).provide :macports, :parent => Puppet::Provider::Package do desc "Package management using MacPorts on OS X. Supports MacPorts versions and revisions, but not variants. Variant preferences may be specified using [the MacPorts variants.conf file](http://guide.macports.org/chunked/internals.configuration-files.html#internals.configuration-files.variants-conf). When specifying a version in the Puppet DSL, only specify the version, not the revision. Revisions are only used internally for ensuring the latest version/revision of a port. " confine :operatingsystem => :darwin commands :port => "/opt/local/bin/port" has_feature :installable has_feature :uninstallable has_feature :upgradeable has_feature :versionable def self.parse_installed_query_line(line) regex = /(\S+)\s+@(\S+)_(\S+)\s+\(active\)/ fields = [:name, :ensure, :revision] hash_from_line(line, regex, fields) end def self.parse_info_query_line(line) regex = /(\S+)\s+(\S+)/ fields = [:version, :revision] hash_from_line(line, regex, fields) end def self.hash_from_line(line, regex, fields) hash = {} if match = regex.match(line) fields.zip(match.captures) { |field, value| hash[field] = value } hash[:provider] = self.name return hash end nil end def self.instances packages = [] - port("-q", :installed).each do |line| + port("-q", :installed).each_line do |line| if hash = parse_installed_query_line(line) packages << new(hash) end end packages end def install should = @resource.should(:ensure) if [:latest, :installed, :present].include?(should) output = port("-q", :install, @resource[:name]) else output = port("-q", :install, @resource[:name], "@#{should}") end # MacPorts now correctly exits non-zero with appropriate errors in # situations where a port cannot be found or installed. end def query return self.class.parse_installed_query_line(port("-q", :installed, @resource[:name])) end def latest # We need both the version and the revision to be confident # we've got the latest revision of a specific version # Note we're still not doing anything with variants here. info_line = port("-q", :info, "--line", "--version", "--revision", @resource[:name]) return nil if info_line == "" if newest = self.class.parse_info_query_line(info_line) current = query # We're doing some fiddling behind the scenes here to cope with updated revisions. # If we're already at the latest version/revision, then just return the version # so the current and desired values match. Otherwise return version and revision # to trigger an upgrade to the latest revision. if newest[:version] == current[:ensure] and newest[:revision] == current[:revision] return current[:ensure] else return "#{newest[:version]}_#{newest[:revision]}" end end nil end def uninstall port("-q", :uninstall, @resource[:name]) end def update if query[:name] == @resource[:name] # 'port upgrade' cannot install new ports port("-q", :upgrade, @resource[:name]) else install end end end diff --git a/lib/puppet/provider/package/openbsd.rb b/lib/puppet/provider/package/openbsd.rb index d97d571d8..3fb1fd7a3 100755 --- a/lib/puppet/provider/package/openbsd.rb +++ b/lib/puppet/provider/package/openbsd.rb @@ -1,115 +1,115 @@ require 'puppet/provider/package' # Packaging on OpenBSD. Doesn't work anywhere else that I know of. Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Package do desc "OpenBSD's form of `pkg_add` support." commands :pkginfo => "pkg_info", :pkgadd => "pkg_add", :pkgdelete => "pkg_delete" defaultfor :operatingsystem => :openbsd confine :operatingsystem => :openbsd has_feature :versionable def self.instances packages = [] begin execpipe(listcmd) do |process| # our regex for matching pkg_info output regex = /^(.*)-(\d[^-]*)[-]?(\D*)(.*)$/ fields = [:name, :ensure, :flavor ] hash = {} # now turn each returned line into a package object - process.each { |line| + process.each_line { |line| if match = regex.match(line.split[0]) fields.zip(match.captures) { |field,value| hash[field] = value } yup = nil name = hash[:name] hash[:provider] = self.name packages << new(hash) hash = {} else # Print a warning on lines we can't match, but move # on, since it should be non-fatal warning("Failed to match line #{line}") end } end return packages rescue Puppet::ExecutionFailure return nil end end def self.listcmd [command(:pkginfo), " -a"] end def install should = @resource.should(:ensure) unless @resource[:source] raise Puppet::Error, "You must specify a package source for BSD packages" end if @resource[:source][-1,1] == ::File::PATH_SEPARATOR e_vars = { :PKG_PATH => @resource[:source] } full_name = [ @resource[:name], get_version || @resource[:ensure], @resource[:flavor] ].join('-').chomp('-') else e_vars = {} full_name = @resource[:source] end Puppet::Util::Execution::withenv(e_vars) { pkgadd full_name } end def get_version execpipe([command(:pkginfo), " -I ", @resource[:name]]) do |process| # our regex for matching pkg_info output regex = /^(.*)-(\d[^-]*)[-]?(\D*)(.*)$/ fields = [ :name, :version, :flavor ] master_version = 0 - process.each do |line| + process.each_line do |line| if match = regex.match(line.split[0]) # now we return the first version, unless ensure is latest version = match.captures[1] return version unless @resource[:ensure] == "latest" master_version = version unless master_version > version end end return master_version unless master_version == 0 raise Puppet::Error, "#{version} is not available for this package" end rescue Puppet::ExecutionFailure return nil end def query hash = {} info = pkginfo @resource[:name] # Search for the version info if info =~ /Information for (inst:)?#{@resource[:name]}-(\S+)/ hash[:ensure] = $2 else return nil end hash end def uninstall pkgdelete @resource[:name] end end diff --git a/lib/puppet/provider/package/pkg.rb b/lib/puppet/provider/package/pkg.rb index 419c8411b..0b826e8f5 100644 --- a/lib/puppet/provider/package/pkg.rb +++ b/lib/puppet/provider/package/pkg.rb @@ -1,107 +1,107 @@ require 'puppet/provider/package' Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package do desc "OpenSolaris image packaging system. See pkg(5) for more information" commands :pkg => "/usr/bin/pkg" confine :operatingsystem => :solaris #defaultfor [:operatingsystem => :solaris, :kernelrelease => "5.11"] def self.instances packages = [] cmd = "#{command(:pkg)} list -H" execpipe(cmd) do |process| hash = {} # now turn each returned line into a package object - process.each { |line| + process.each_line { |line| if hash = parse_line(line) packages << new(hash) end } end packages end self::REGEX = %r{^(\S+)\s+(\S+)\s+(\S+)\s+} self::FIELDS = [:name, :version, :status] def self.parse_line(line) hash = {} if match = self::REGEX.match(line) self::FIELDS.zip(match.captures) { |field,value| hash[field] = value } hash[:provider] = self.name hash[:error] = "ok" if hash[:status] == "installed" hash[:ensure] = :present else hash[:ensure] = :absent end else Puppet.warning "Failed to match 'pkg list' line #{line.inspect}" return nil end hash end # return the version of the package # TODO deal with multiple publishers def latest version = nil pkg(:list, "-Ha", @resource[:name]).split("\n").each do |line| v = line.split[2] case v when "known" return v when "installed" version = v else Puppet.warn "unknown package state for #{@resource[:name]}: #{v}" end end version end # install the package def install pkg :install, @resource[:name] end # uninstall the package def uninstall pkg :uninstall, '-r', @resource[:name] end # update the package to the latest version available def update self.install end # list a specific package def query begin output = pkg(:list, "-H", @resource[:name]) rescue Puppet::ExecutionFailure # pkg returns 1 if the package is not found. return {:ensure => :absent, :status => 'missing', :name => @resource[:name], :error => 'ok'} end hash = self.class.parse_line(output) || {:ensure => :absent, :status => 'missing', :name => @resource[:name], :error => 'ok'} raise Puppet::Error.new( "Package #{hash[:name]}, version #{hash[:version]} is in error state: #{hash[:error]}") if hash[:error] != "ok" hash end end diff --git a/lib/puppet/provider/package/pkgutil.rb b/lib/puppet/provider/package/pkgutil.rb index a0f39a09e..ae3fd8943 100755 --- a/lib/puppet/provider/package/pkgutil.rb +++ b/lib/puppet/provider/package/pkgutil.rb @@ -1,174 +1,174 @@ # Packaging using Peter Bonivart's pkgutil program. Puppet::Type.type(:package).provide :pkgutil, :parent => :sun, :source => :sun do desc "Package management using Peter Bonivart's ``pkgutil`` command on Solaris." pkgutil_bin = "pkgutil" if FileTest.executable?("/opt/csw/bin/pkgutil") pkgutil_bin = "/opt/csw/bin/pkgutil" end confine :operatingsystem => :solaris commands :pkguti => pkgutil_bin def self.healthcheck() unless FileTest.exists?("/var/opt/csw/pkgutil/admin") Puppet.notice "It is highly recommended you create '/var/opt/csw/pkgutil/admin'." Puppet.notice "See /var/opt/csw/pkgutil" end correct_wgetopts = false [ "/opt/csw/etc/pkgutil.conf", "/etc/opt/csw/pkgutil.conf" ].each do |confpath| File.open(confpath) do |conf| - conf.each {|line| correct_wgetopts = true if line =~ /^\s*wgetopts\s*=.*(-nv|-q|--no-verbose|--quiet)/ } + conf.each_line {|line| correct_wgetopts = true if line =~ /^\s*wgetopts\s*=.*(-nv|-q|--no-verbose|--quiet)/ } end end if ! correct_wgetopts Puppet.notice "It is highly recommended that you set 'wgetopts=-nv' in your pkgutil.conf." end end def self.instances(hash = {}) healthcheck # Use the available pkg list (-a) to work out aliases aliases = {} availlist.each do |pkg| aliases[pkg[:name]] = pkg[:alias] end # The -c pkglist lists installed packages pkginsts = [] pkglist(hash).each do |pkg| pkg.delete(:avail) pkginsts << new(pkg) # Create a second instance with the alias if it's different pkgalias = aliases[pkg[:name]] if pkgalias and pkg[:name] != pkgalias apkg = pkg.dup apkg[:name] = pkgalias pkginsts << new(apkg) end end pkginsts end # Turns a pkgutil -a listing into hashes with the common alias, full # package name and available version def self.availlist output = pkguti ["-a"] list = output.split("\n").collect do |line| next if line =~ /^common\s+package/ # header of package list next if noise?(line) if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/ { :alias => $1, :name => $2, :avail => $3 } else Puppet.warning "Cannot match %s" % line end end.reject { |h| h.nil? } end # Turn our pkgutil -c listing into a bunch of hashes. # Supports :justme => packagename, which uses the optimised --single arg def self.pkglist(hash) command = ["-c"] if hash[:justme] # The --single option speeds up the execution, because it queries # the package managament system for one package only. command << "--single" command << hash[:justme] end output = pkguti(command).split("\n") if output[-1] == "Not in catalog" Puppet.warning "Package not in pkgutil catalog: %s" % hash[:justme] return nil end list = output.collect do |line| next if line =~ /installed\s+catalog/ # header of package list next if noise?(line) pkgsplit(line) end.reject { |h| h.nil? } if hash[:justme] # Single queries may have been for an alias so return the name requested if list.any? list[-1][:name] = hash[:justme] return list[-1] end else list.reject! { |h| h[:ensure] == :absent } return list end end # Identify common types of pkgutil noise as it downloads catalogs etc def self.noise?(line) true if line =~ /^#/ true if line =~ /^Checking integrity / # use_gpg true if line =~ /^gpg: / # gpg verification true if line =~ /^=+> / # catalog fetch true if line =~ /\d+:\d+:\d+ URL:/ # wget without -q false end # Split the different lines into hashes. def self.pkgsplit(line) if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/ hash = {} hash[:name] = $1 hash[:ensure] = if $2 == "notinst" :absent else $2 end hash[:avail] = $3 if hash[:avail] =~ /^SAME\s*$/ hash[:avail] = hash[:ensure] end # Use the name method, so it works with subclasses. hash[:provider] = self.name return hash else Puppet.warning "Cannot match %s" % line return nil end end def install pkguti "-y", "-i", @resource[:name] end # Retrieve the version from the current package file. def latest hash = self.class.pkglist(:justme => @resource[:name]) hash[:avail] if hash end def query if hash = self.class.pkglist(:justme => @resource[:name]) hash else {:ensure => :absent} end end def update pkguti "-y", "-u", @resource[:name] end def uninstall pkguti "-y", "-r", @resource[:name] end end diff --git a/lib/puppet/provider/package/portage.rb b/lib/puppet/provider/package/portage.rb index 30f0e4a25..cc66c02ff 100644 --- a/lib/puppet/provider/package/portage.rb +++ b/lib/puppet/provider/package/portage.rb @@ -1,122 +1,122 @@ require 'puppet/provider/package' require 'fileutils' Puppet::Type.type(:package).provide :portage, :parent => Puppet::Provider::Package do desc "Provides packaging support for Gentoo's portage system." has_feature :versionable commands :emerge => "/usr/bin/emerge", :eix => "/usr/bin/eix", :update_eix => "/usr/bin/eix-update" confine :operatingsystem => :gentoo defaultfor :operatingsystem => :gentoo def self.instances result_format = /^(\S+)\s+(\S+)\s+\[(\S+)\]\s+\[(\S+)\]\s+(\S+)\s+(.*)$/ result_fields = [:category, :name, :ensure, :version_available, :vendor, :description] version_format = "{last}{}" search_format = " [] [] \n" begin update_eix if !FileUtils.uptodate?("/var/cache/eix", %w{/usr/bin/eix /usr/portage/metadata/timestamp}) search_output = nil Puppet::Util::Execution.withenv :LASTVERSION => version_format do search_output = eix "--nocolor", "--pure-packages", "--stable", "--installed", "--format", search_format end packages = [] - search_output.each do |search_result| + search_output.each_line do |search_result| match = result_format.match(search_result) if match package = {} result_fields.zip(match.captures) do |field, value| package[field] = value unless !value or value.empty? end package[:provider] = :portage packages << new(package) end end return packages rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new(detail) end end def install should = @resource.should(:ensure) name = package_name unless should == :present or should == :latest # We must install a specific version name = "=#{name}-#{should}" end emerge name end # The common package name format. def package_name @resource[:category] ? "#{@resource[:category]}/#{@resource[:name]}" : @resource[:name] end def uninstall emerge "--unmerge", package_name end def update self.install end def query result_format = /^(\S+)\s+(\S+)\s+\[(\S*)\]\s+\[(\S+)\]\s+(\S+)\s+(.*)$/ result_fields = [:category, :name, :ensure, :version_available, :vendor, :description] version_format = "{last}{}" search_format = " [] [] \n" search_field = package_name.count('/') > 0 ? "--category-name" : "--name" search_value = package_name begin update_eix if !FileUtils.uptodate?("/var/cache/eix", %w{/usr/bin/eix /usr/portage/metadata/timestamp}) search_output = nil Puppet::Util::Execution.withenv :LASTVERSION => version_format do search_output = eix "--nocolor", "--pure-packages", "--stable", "--format", search_format, "--exact", search_field, search_value end packages = [] - search_output.each do |search_result| + search_output.each_line do |search_result| match = result_format.match(search_result) if match package = {} result_fields.zip(match.captures) do |field, value| package[field] = value unless !value or value.empty? end package[:ensure] = package[:ensure] ? package[:ensure] : :absent packages << package end end case packages.size when 0 not_found_value = "#{@resource[:category] ? @resource[:category] : ""}/#{@resource[:name]}" raise Puppet::Error.new("No package found with the specified name [#{not_found_value}]") when 1 return packages[0] else raise Puppet::Error.new("More than one package with the specified name [#{search_value}], please use the category parameter to disambiguate") end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new(detail) end end def latest self.query[:version_available] end end diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb index 0e1f9297f..ff59b6f0c 100755 --- a/lib/puppet/provider/package/rpm.rb +++ b/lib/puppet/provider/package/rpm.rb @@ -1,132 +1,132 @@ require 'puppet/provider/package' # RPM packaging. Should work anywhere that has rpm installed. Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Provider::Package do desc "RPM packaging support; should work anywhere with a working `rpm` binary." has_feature :versionable # The query format by which we identify installed packages NEVRAFORMAT = "%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}" NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch] commands :rpm => "rpm" if command('rpm') confine :true => begin rpm('--version') rescue Puppet::ExecutionFailure false else true end end def self.instances packages = [] # rpm < 4.1 don't support --nosignature output = rpm "--version" sig = "--nosignature" if output =~ /RPM version (([123].*)|(4\.0.*))/ sig = "" end # list out all of the packages begin execpipe("#{command(:rpm)} -qa #{sig} --nodigest --qf '#{NEVRAFORMAT}\n'") { |process| # now turn each returned line into a package object - process.each { |line| + process.each_line { |line| hash = nevra_to_hash(line) packages << new(hash) } } rescue Puppet::ExecutionFailure raise Puppet::Error, "Failed to list packages" end packages end # Find the fully versioned package name and the version alone. Returns # a hash with entries :instance => fully versioned package name, and # :ensure => version-release def query #NOTE: Prior to a fix for issue 1243, this method potentially returned a cached value #IF YOU CALL THIS METHOD, IT WILL CALL RPM #Use get(:property) to check if cached values are available cmd = ["-q", @resource[:name], "--nosignature", "--nodigest", "--qf", "#{NEVRAFORMAT}\n"] begin output = rpm(*cmd) rescue Puppet::ExecutionFailure return nil end # FIXME: We could actually be getting back multiple packages # for multilib @property_hash.update(self.class.nevra_to_hash(output)) @property_hash.dup end # Here we just retrieve the version from the file specified in the source. def latest unless source = @resource[:source] @resource.fail "RPMs must specify a package source" end cmd = [command(:rpm), "-q", "--qf", "#{NEVRAFORMAT}\n", "-p", "#{@resource[:source]}"] h = self.class.nevra_to_hash(execfail(cmd, Puppet::Error)) h[:ensure] end def install source = nil unless source = @resource[:source] @resource.fail "RPMs must specify a package source" end # RPM gets pissy if you try to install an already # installed package if @resource.should(:ensure) == @property_hash[:ensure] or @resource.should(:ensure) == :latest && @property_hash[:ensure] == latest return end flag = "-i" flag = "-U" if @property_hash[:ensure] and @property_hash[:ensure] != :absent rpm flag, "--oldpackage", source end def uninstall query unless get(:arch) nvr = "#{get(:name)}-#{get(:version)}-#{get(:release)}" arch = ".#{get(:arch)}" # If they specified an arch in the manifest, erase that Otherwise, # erase the arch we got back from the query. If multiple arches are # installed and only the package name is specified (without the # arch), this will uninstall all of them on successive runs of the # client, one after the other if @resource[:name][-arch.size, arch.size] == arch nvr += arch else nvr += ".#{get(:arch)}" end rpm "-e", nvr end def update self.install end def self.nevra_to_hash(line) line.chomp! hash = {} NEVRA_FIELDS.zip(line.split) { |f, v| hash[f] = v } hash[:provider] = self.name hash[:ensure] = "#{hash[:version]}-#{hash[:release]}" hash end end diff --git a/lib/puppet/provider/package/sun.rb b/lib/puppet/provider/package/sun.rb index f8c3dde0e..49aa81e6a 100755 --- a/lib/puppet/provider/package/sun.rb +++ b/lib/puppet/provider/package/sun.rb @@ -1,154 +1,154 @@ # Sun packaging. require 'puppet/provider/package' Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package do desc "Sun's packaging system. Requires that you specify the source for the packages you're managing." commands :pkginfo => "/usr/bin/pkginfo", :pkgadd => "/usr/sbin/pkgadd", :pkgrm => "/usr/sbin/pkgrm" confine :operatingsystem => :solaris defaultfor :operatingsystem => :solaris def self.instances packages = [] hash = {} names = { "PKGINST" => :name, "NAME" => nil, "CATEGORY" => :category, "ARCH" => :platform, "VERSION" => :ensure, "BASEDIR" => :root, "HOTLINE" => nil, "EMAIL" => nil, "VENDOR" => :vendor, "DESC" => :description, "PSTAMP" => nil, "INSTDATE" => nil, "STATUS" => nil, "FILES" => nil } cmd = "#{command(:pkginfo)} -l" # list out all of the packages execpipe(cmd) { |process| # we're using the long listing, so each line is a separate # piece of information - process.each { |line| + process.each_line { |line| case line when /^$/ hash[:provider] = :sun packages << new(hash) hash = {} when /\s*(\w+):\s+(.+)/ name = $1 value = $2 if names.include?(name) hash[names[name]] = value unless names[name].nil? end when /\s+\d+.+/ # nothing; we're ignoring the FILES info end } } packages end # Get info on a package, optionally specifying a device. def info2hash(device = nil) names = { "PKGINST" => :name, "NAME" => nil, "CATEGORY" => :category, "ARCH" => :platform, "VERSION" => :ensure, "BASEDIR" => :root, "HOTLINE" => nil, "EMAIL" => nil, "VSTOCK" => nil, "VENDOR" => :vendor, "DESC" => :description, "PSTAMP" => nil, "INSTDATE" => nil, "STATUS" => nil, "FILES" => nil } hash = {} cmd = "#{command(:pkginfo)} -l" cmd += " -d #{device}" if device cmd += " #{@resource[:name]}" begin # list out all of the packages execpipe(cmd) { |process| # we're using the long listing, so each line is a separate # piece of information process.readlines.each { |line| case line when /^$/ # ignore when /\s*([A-Z]+):\s+(.+)/ name = $1 value = $2 if names.include?(name) hash[names[name]] = value unless names[name].nil? end when /\s+\d+.+/ # nothing; we're ignoring the FILES info end } } return hash rescue Puppet::ExecutionFailure => detail return {:ensure => :absent} if detail.message =~ /information for "#{Regexp.escape(@resource[:name])}" was not found/ puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Unable to get information about package #{@resource[:name]} because of: #{detail}" end end def install raise Puppet::Error, "Sun packages must specify a package source" unless @resource[:source] cmd = [] cmd << "-a" << @resource[:adminfile] if @resource[:adminfile] cmd << "-r" << @resource[:responsefile] if @resource[:responsefile] cmd << "-d" << @resource[:source] cmd << "-n" << @resource[:name] pkgadd cmd end # Retrieve the version from the current package file. def latest hash = info2hash(@resource[:source]) hash[:ensure] end def query info2hash() end def uninstall command = ["-n"] command << "-a" << @resource[:adminfile] if @resource[:adminfile] command << @resource[:name] pkgrm command end # Remove the old package, and install the new one. This will probably # often fail. def update self.uninstall if (@property_hash[:ensure] || info2hash()[:ensure]) != :absent self.install end end diff --git a/lib/puppet/provider/selmodule/semodule.rb b/lib/puppet/provider/selmodule/semodule.rb index 64197156f..50ba039ec 100644 --- a/lib/puppet/provider/selmodule/semodule.rb +++ b/lib/puppet/provider/selmodule/semodule.rb @@ -1,135 +1,135 @@ Puppet::Type.type(:selmodule).provide(:semodule) do desc "Manage SELinux policy modules using the semodule binary." commands :semodule => "/usr/sbin/semodule" def create begin execoutput("#{command(:semodule)} --install #{selmod_name_to_filename}") rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not load policy module: #{detail}"; end :true end def destroy execoutput("#{command(:semodule)} --remove #{@resource[:name]}") rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not remove policy module: #{detail}"; end def exists? self.debug "Checking for module #{@resource[:name]}" execpipe("#{command(:semodule)} --list") do |out| - out.each do |line| + out.each_line do |line| if line =~ /#{@resource[:name]}\b/ return :true end end end nil end def syncversion self.debug "Checking syncversion on #{@resource[:name]}" loadver = selmodversion_loaded if(loadver) then filever = selmodversion_file if (filever == loadver) return :true end end :false end def syncversion= (dosync) execoutput("#{command(:semodule)} --upgrade #{selmod_name_to_filename}") rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not upgrade policy module: #{detail}"; end # Helper functions def execoutput (cmd) output = '' begin execpipe(cmd) do |out| output = out.readlines.join('').chomp! end rescue Puppet::ExecutionFailure raise Puppet::ExecutionFailure, output.split("\n")[0] end output end def selmod_name_to_filename if @resource[:selmodulepath] return @resource[:selmodulepath] else return "#{@resource[:selmoduledir]}/#{@resource[:name]}.pp" end end def selmod_readnext (handle) len = handle.read(4).unpack('L')[0] handle.read(len) end def selmodversion_file magic = 0xF97CFF8F filename = selmod_name_to_filename mod = File.new(filename, "r") (hdr, ver, numsec) = mod.read(12).unpack('LLL') raise Puppet::Error, "Found #{hdr} instead of magic #{magic} in #{filename}" if hdr != magic raise Puppet::Error, "Unknown policy file version #{ver} in #{filename}" if ver != 1 # Read through (and throw away) the file section offsets, and also # the magic header for the first section. mod.read((numsec + 1) * 4) ## Section 1 should be "SE Linux Module" selmod_readnext(mod) selmod_readnext(mod) # Skip past the section headers mod.read(14) # Module name selmod_readnext(mod) # At last! the version v = selmod_readnext(mod) self.debug "file version #{v}" v end def selmodversion_loaded lines = () begin execpipe("#{command(:semodule)} --list") do |output| lines = output.readlines - lines.each do |line| + lines.each_line do |line| line.chomp! bits = line.split if bits[0] == @resource[:name] self.debug "load version #{bits[1]}" return bits[1] end end end rescue Puppet::ExecutionFailure raise Puppet::ExecutionFailure, "Could not list policy modules: #{lines.join(' ').chomp!}" end nil end end diff --git a/lib/puppet/provider/service/base.rb b/lib/puppet/provider/service/base.rb index 380bd5944..2d8f559f7 100755 --- a/lib/puppet/provider/service/base.rb +++ b/lib/puppet/provider/service/base.rb @@ -1,144 +1,144 @@ Puppet::Type.type(:service).provide :base do desc "The simplest form of service support. You have to specify enough about your service for this to work; the minimum you can specify is a binary for starting the process, and this same binary will be searched for in the process table to stop the service. As with `init`-style services, it is preferable to specify start, stop, and status commands. " commands :kill => "kill" def self.instances [] end # Get the process ID for a running process. Requires the 'pattern' # parameter. def getpid @resource.fail "Either stop/status commands or a pattern must be specified" unless @resource[:pattern] ps = Facter["ps"].value @resource.fail "You must upgrade Facter to a version that includes 'ps'" unless ps and ps != "" regex = Regexp.new(@resource[:pattern]) self.debug "Executing '#{ps}'" IO.popen(ps) { |table| - table.each { |line| + table.each_line { |line| if regex.match(line) ary = line.sub(/^\s+/, '').split(/\s+/) return ary[1] end } } nil end # How to restart the process. def restart if @resource[:restart] or restartcmd ucommand(:restart) else self.stop self.start end end # There is no default command, which causes other methods to be used def restartcmd end # Check if the process is running. Prefer the 'status' parameter, # then 'statuscmd' method, then look in the process table. We give # the object the option to not return a status command, which might # happen if, for instance, it has an init script (and thus responds to # 'statuscmd') but does not have 'hasstatus' enabled. def status if @resource[:status] or statuscmd # Don't fail when the exit status is not 0. ucommand(:status, false) # Expicitly calling exitstatus to facilitate testing if $CHILD_STATUS.exitstatus == 0 return :running else return :stopped end elsif pid = self.getpid self.debug "PID is #{pid}" return :running else return :stopped end end # There is no default command, which causes other methods to be used def statuscmd end # Run the 'start' parameter command, or the specified 'startcmd'. def start ucommand(:start) end # The command used to start. Generated if the 'binary' argument # is passed. def startcmd if @resource[:binary] return @resource[:binary] else raise Puppet::Error, "Services must specify a start command or a binary" end end # Stop the service. If a 'stop' parameter is specified, it # takes precedence; otherwise checks if the object responds to # a 'stopcmd' method, and if so runs that; otherwise, looks # for the process in the process table. # This method will generally not be overridden by submodules. def stop if @resource[:stop] or stopcmd ucommand(:stop) else pid = getpid unless pid self.info "#{self.name} is not running" return false end begin output = kill pid rescue Puppet::ExecutionFailure => detail @resource.fail "Could not kill #{self.name}, PID #{pid}: #{output}" end return true end end # There is no default command, which causes other methods to be used def stopcmd end # A simple wrapper so execution failures are a bit more informative. def texecute(type, command, fof = true) begin # #565: Services generally produce no output, so squelch them. execute(command, :failonfail => fof, :squelch => true) rescue Puppet::ExecutionFailure => detail @resource.fail "Could not #{type} #{@resource.ref}: #{detail}" end nil end # Use either a specified command or the default for our provider. def ucommand(type, fof = true) if c = @resource[type] cmd = [c] else cmd = [send("#{type}cmd")].flatten end texecute(type, cmd, fof) end end diff --git a/lib/puppet/provider/service/src.rb b/lib/puppet/provider/service/src.rb index eced271d4..4243d38d2 100755 --- a/lib/puppet/provider/service/src.rb +++ b/lib/puppet/provider/service/src.rb @@ -1,86 +1,86 @@ # AIX System Resource controller (SRC) Puppet::Type.type(:service).provide :src, :parent => :base do desc "Support for AIX's System Resource controller. Services are started/stopped based on the `stopsrc` and `startsrc` commands, and some services can be refreshed with `refresh` command. Enabling and disabling services is not supported, as it requires modifications to `/etc/inittab`. Starting and stopping groups of subsystems is not yet supported. " defaultfor :operatingsystem => :aix confine :operatingsystem => :aix commands :stopsrc => "/usr/bin/stopsrc" commands :startsrc => "/usr/bin/startsrc" commands :refresh => "/usr/bin/refresh" commands :lssrc => "/usr/bin/lssrc" has_feature :refreshable def startcmd [command(:startsrc), "-s", @resource[:name]] end def stopcmd [command(:stopsrc), "-s", @resource[:name]] end def restart - execute([command(:lssrc), "-Ss", @resource[:name]]).each do |line| + execute([command(:lssrc), "-Ss", @resource[:name]]).each_line do |line| args = line.split(":") next unless args[0] == @resource[:name] # Subsystems with the -K flag can get refreshed (HUPed) # While subsystems with -S (signals) must be stopped/started method = args[11] do_refresh = case method when "-K" then :true when "-S" then :false else self.fail("Unknown service communication method #{method}") end begin if do_refresh == :true execute([command(:refresh), "-s", @resource[:name]]) else self.stop self.start end return :true rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Unable to restart service #{@resource[:name]}, error was: #{detail}" ) end end self.fail("No such service found") rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}" ) end def status - execute([command(:lssrc), "-s", @resource[:name]]).each do |line| + execute([command(:lssrc), "-s", @resource[:name]]).each_line do |line| args = line.split # This is the header line next unless args[0] == @resource[:name] # PID is the 3rd field, but inoperative subsystems # skip this so split doesn't work right state = case args[-1] when "active" then :running when "inoperative" then :stopped end Puppet.debug("Service #{@resource[:name]} is #{args[-1]}") return state end self.fail("No such service found") rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}" ) end end diff --git a/lib/puppet/provider/service/upstart.rb b/lib/puppet/provider/service/upstart.rb index 5249a914c..455120485 100755 --- a/lib/puppet/provider/service/upstart.rb +++ b/lib/puppet/provider/service/upstart.rb @@ -1,71 +1,71 @@ Puppet::Type.type(:service).provide :upstart, :parent => :init do desc "Ubuntu service management with `upstart`. This provider manages `upstart` jobs, which have replaced `initd` services on Ubuntu. For `upstart` documentation, see . " # confine to :ubuntu for now because I haven't tested on other platforms confine :operatingsystem => :ubuntu #[:ubuntu, :fedora, :debian] commands :start => "/sbin/start", :stop => "/sbin/stop", :restart => "/sbin/restart", :status_exec => "/sbin/status", :initctl => "/sbin/initctl" # upstart developer haven't implemented initctl enable/disable yet: # http://www.linuxplanet.com/linuxplanet/tutorials/7033/2/ # has_feature :enableable def self.instances instances = [] execpipe("#{command(:initctl)} list") { |process| - process.each { |line| + process.each_line { |line| # needs special handling of services such as network-interface: # initctl list: # network-interface (lo) start/running # network-interface (eth0) start/running # network-interface-security start/running name = \ if matcher = line.match(/^(network-interface)\s\(([^\)]+)\)/) "#{matcher[1]} INTERFACE=#{matcher[2]}" else line.split.first end instances << new(:name => name) } } instances end def startcmd [command(:start), @resource[:name]] end def stopcmd [command(:stop), @resource[:name]] end def restartcmd (@resource[:hasrestart] == :true) && [command(:restart), @resource[:name]] end def status # allows user override of status command if @resource[:status] ucommand(:status, false) if $?.exitstatus == 0 return :running else return :stopped end else output = status_exec(@resource[:name].split) if (! $?.nil?) && (output =~ /start\//) return :running else return :stopped end end end end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index a33ed677c..ce0b8a3b8 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -1,353 +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 # require 'puppet/provider/aixobject' require 'tempfile' require 'date' Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do desc "User management for AIX." # 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.= 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| + execute(lsgroupscmd("ALL")).each_line { |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*$/ } + f.each_line { |l| break if l =~ /^#{user}:\s*$/ } if ! f.eof? - f.each { |l| + f.each_line { |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/spec/unit/file_serving/configuration/parser_spec.rb b/spec/unit/file_serving/configuration/parser_spec.rb index 5ccfc5075..6c9aa5034 100755 --- a/spec/unit/file_serving/configuration/parser_spec.rb +++ b/spec/unit/file_serving/configuration/parser_spec.rb @@ -1,188 +1,190 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/file_serving/configuration/parser' describe Puppet::FileServing::Configuration::Parser do it "should subclass the LoadedFile class" do Puppet::FileServing::Configuration::Parser.superclass.should equal(Puppet::Util::LoadedFile) end end module FSConfigurationParserTesting def mock_file_content(content) # We want an array, but we actually want our carriage returns on all of it. lines = content.split("\n").collect { |l| l + "\n" } - @filehandle.stubs(:each).multiple_yields(*lines) + @filehandle.stubs(:each_line).multiple_yields(*lines) + @filehandle.expects(:each).never end end describe Puppet::FileServing::Configuration::Parser do before :each do @path = "/my/config.conf" FileTest.stubs(:exists?).with(@path).returns(true) FileTest.stubs(:readable?).with(@path).returns(true) @filehandle = mock 'filehandle' + @filehandle.expects(:each).never File.expects(:open).with(@path).yields(@filehandle) @parser = Puppet::FileServing::Configuration::Parser.new(@path) end describe Puppet::FileServing::Configuration::Parser, " when parsing" do include FSConfigurationParserTesting it "should allow comments" do - @filehandle.expects(:each).yields("# this is a comment\n") + @filehandle.expects(:each_line).yields("# this is a comment\n") proc { @parser.parse }.should_not raise_error end it "should allow blank lines" do - @filehandle.expects(:each).yields("\n") + @filehandle.expects(:each_line).yields("\n") proc { @parser.parse }.should_not raise_error end it "should create a new mount for each section in the configuration" do mount1 = mock 'one', :validate => true mount2 = mock 'two', :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) mock_file_content "[one]\n[two]\n" @parser.parse end # This test is almost the exact same as the previous one. it "should return a hash of the created mounts" do mount1 = mock 'one', :validate => true mount2 = mock 'two', :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) mock_file_content "[one]\n[two]\n" result = @parser.parse result["one"].should equal(mount1) result["two"].should equal(mount2) end it "should only allow mount names that are alphanumeric plus dashes" do mock_file_content "[a*b]\n" proc { @parser.parse }.should raise_error(ArgumentError) end it "should fail if the value for path/allow/deny starts with an equals sign" do mock_file_content "[one]\npath = /testing" proc { @parser.parse }.should raise_error(ArgumentError) end it "should validate each created mount" do mount1 = mock 'one' Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) mock_file_content "[one]\n" mount1.expects(:validate) @parser.parse end it "should fail if any mount does not pass validation" do mount1 = mock 'one' Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) mock_file_content "[one]\n" mount1.expects(:validate).raises RuntimeError lambda { @parser.parse }.should raise_error(RuntimeError) end end describe Puppet::FileServing::Configuration::Parser, " when parsing mount attributes" do include FSConfigurationParserTesting before do @mount = stub 'testmount', :name => "one", :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(@mount) @parser.stubs(:add_modules_mount) end it "should set the mount path to the path attribute from that section" do mock_file_content "[one]\npath /some/path\n" @mount.expects(:path=).with("/some/path") @parser.parse end it "should tell the mount to allow any allow values from the section" do mock_file_content "[one]\nallow something\n" @mount.expects(:info) @mount.expects(:allow).with("something") @parser.parse end it "should support inline comments" do mock_file_content "[one]\nallow something \# will it work?\n" @mount.expects(:info) @mount.expects(:allow).with("something") @parser.parse end it "should tell the mount to deny any deny values from the section" do mock_file_content "[one]\ndeny something\n" @mount.expects(:info) @mount.expects(:deny).with("something") @parser.parse end it "should fail on any attributes other than path, allow, and deny" do mock_file_content "[one]\ndo something\n" proc { @parser.parse }.should raise_error(ArgumentError) end end describe Puppet::FileServing::Configuration::Parser, " when parsing the modules mount" do include FSConfigurationParserTesting before do @mount = stub 'modulesmount', :name => "modules", :validate => true end it "should create an instance of the Modules Mount class" do mock_file_content "[modules]\n" Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns @mount @parser.parse end it "should warn if a path is set" do mock_file_content "[modules]\npath /some/path\n" Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns(@mount) Puppet.expects(:warning) @parser.parse end end describe Puppet::FileServing::Configuration::Parser, " when parsing the plugins mount" do include FSConfigurationParserTesting before do @mount = stub 'pluginsmount', :name => "plugins", :validate => true end it "should create an instance of the Plugins Mount class" do mock_file_content "[plugins]\n" Puppet::FileServing::Mount::Plugins.expects(:new).with("plugins").returns @mount @parser.parse end it "should warn if a path is set" do mock_file_content "[plugins]\npath /some/path\n" Puppet.expects(:warning) @parser.parse end end end diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb index ca94cc1ab..9b33679a7 100755 --- a/spec/unit/network/authconfig_spec.rb +++ b/spec/unit/network/authconfig_spec.rb @@ -1,314 +1,315 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/authconfig' describe Puppet::Network::AuthConfig do before do @rights = stubs 'rights' Puppet::Network::Rights.stubs(:new).returns(@rights) @rights.stubs(:each).returns([]) FileTest.stubs(:exists?).returns(true) File.stubs(:stat).returns(stub('stat', :ctime => :now)) Time.stubs(:now).returns Time.now @authconfig = Puppet::Network::AuthConfig.new("dummy", false) end describe "when initializing" do before :each do Puppet::Network::AuthConfig.any_instance.stubs(:read) end it "should use the authconfig default pathname if none provided" do Puppet.expects(:[]).with(:authconfig).returns("dummy") Puppet::Network::AuthConfig.new end it "should raise an error if no file is defined finally" do Puppet.stubs(:[]).with(:authconfig).returns(nil) lambda { Puppet::Network::AuthConfig.new }.should raise_error(Puppet::DevError) end it "should read and parse the file if parsenow is true" do Puppet::Network::AuthConfig.any_instance.expects(:read) Puppet::Network::AuthConfig.new("dummy", true) end end describe "when checking authorization" do before :each do @authconfig.stubs(:read) @call = stub 'call', :intern => "name" @handler = stub 'handler', :intern => "handler" @method = stub_everything 'method' @request = stub 'request', :call => @call, :handler => @handler, :method => @method, :name => "me", :ip => "1.2.3.4" end it "should attempt to read the authconfig file" do @rights.stubs(:include?) @authconfig.expects(:read) @authconfig.allowed?(@request) end it "should use a name right if it exists" do right = stub 'right' @rights.stubs(:include?).with("name").returns(true) @rights.stubs(:[]).with("name").returns(right) right.expects(:allowed?).with("me", "1.2.3.4") @authconfig.allowed?(@request) end it "should use a namespace right otherwise" do right = stub 'right' @rights.stubs(:include?).with("name").returns(false) @rights.stubs(:include?).with("handler").returns(true) @rights.stubs(:[]).with("handler").returns(right) right.expects(:allowed?).with("me", "1.2.3.4") @authconfig.allowed?(@request) end it "should return whatever the found rights returns" do right = stub 'right' @rights.stubs(:include?).with("name").returns(true) @rights.stubs(:[]).with("name").returns(right) right.stubs(:allowed?).with("me", "1.2.3.4").returns(:returned) @authconfig.allowed?(@request).should == :returned end end describe "when parsing authconfig file" do before :each do @fd = stub 'fd' + @fd.expects(:each).never File.stubs(:open).yields(@fd) @rights.stubs(:include?).returns(false) @rights.stubs(:[]) end it "should skip comments" do - @fd.stubs(:each).yields(' # comment') + @fd.stubs(:each_line).yields(' # comment') @rights.expects(:newright).never @authconfig.read end it "should increment line number even on commented lines" do - @fd.stubs(:each).multiple_yields(' # comment','[puppetca]') + @fd.stubs(:each_line).multiple_yields(' # comment','[puppetca]') @rights.expects(:newright).with('[puppetca]', 2, 'dummy') @authconfig.read end it "should skip blank lines" do - @fd.stubs(:each).yields(' ') + @fd.stubs(:each_line).yields(' ') @rights.expects(:newright).never @authconfig.read end it "should increment line number even on blank lines" do - @fd.stubs(:each).multiple_yields(' ','[puppetca]') + @fd.stubs(:each_line).multiple_yields(' ','[puppetca]') @rights.expects(:newright).with('[puppetca]', 2, 'dummy') @authconfig.read end it "should throw an error if the current namespace right already exist" do - @fd.stubs(:each).yields('[puppetca]') + @fd.stubs(:each_line).yields('[puppetca]') @rights.stubs(:include?).with("puppetca").returns(true) lambda { @authconfig.read }.should raise_error end it "should not throw an error if the current path right already exist" do - @fd.stubs(:each).yields('path /hello') + @fd.stubs(:each_line).yields('path /hello') @rights.stubs(:newright).with("/hello",1, 'dummy') @rights.stubs(:include?).with("/hello").returns(true) lambda { @authconfig.read }.should_not raise_error end it "should create a new right for found namespaces" do - @fd.stubs(:each).yields('[puppetca]') + @fd.stubs(:each_line).yields('[puppetca]') @rights.expects(:newright).with("[puppetca]", 1, 'dummy') @authconfig.read end it "should create a new right for each found namespace line" do - @fd.stubs(:each).multiple_yields('[puppetca]', '[fileserver]') + @fd.stubs(:each_line).multiple_yields('[puppetca]', '[fileserver]') @rights.expects(:newright).with("[puppetca]", 1, 'dummy') @rights.expects(:newright).with("[fileserver]", 2, 'dummy') @authconfig.read end it "should create a new right for each found path line" do - @fd.stubs(:each).multiple_yields('path /certificates') + @fd.stubs(:each_line).multiple_yields('path /certificates') @rights.expects(:newright).with("/certificates", 1, 'dummy') @authconfig.read end it "should create a new right for each found regex line" do - @fd.stubs(:each).multiple_yields('path ~ .rb$') + @fd.stubs(:each_line).multiple_yields('path ~ .rb$') @rights.expects(:newright).with("~ .rb$", 1, 'dummy') @authconfig.read end it "should strip whitespace around ACE" do acl = stub 'acl', :info - @fd.stubs(:each).multiple_yields('[puppetca]', ' allow 127.0.0.1 , 172.16.10.0 ') + @fd.stubs(:each_line).multiple_yields('[puppetca]', ' allow 127.0.0.1 , 172.16.10.0 ') @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) acl.expects(:allow).with('127.0.0.1') acl.expects(:allow).with('172.16.10.0') @authconfig.read end it "should allow ACE inline comments" do acl = stub 'acl', :info - @fd.stubs(:each).multiple_yields('[puppetca]', ' allow 127.0.0.1 # will it work?') + @fd.stubs(:each_line).multiple_yields('[puppetca]', ' allow 127.0.0.1 # will it work?') @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) acl.expects(:allow).with('127.0.0.1') @authconfig.read end it "should create an allow ACE on each subsequent allow" do acl = stub 'acl', :info - @fd.stubs(:each).multiple_yields('[puppetca]', 'allow 127.0.0.1') + @fd.stubs(:each_line).multiple_yields('[puppetca]', 'allow 127.0.0.1') @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) acl.expects(:allow).with('127.0.0.1') @authconfig.read end it "should create a deny ACE on each subsequent deny" do acl = stub 'acl', :info - @fd.stubs(:each).multiple_yields('[puppetca]', 'deny 127.0.0.1') + @fd.stubs(:each_line).multiple_yields('[puppetca]', 'deny 127.0.0.1') @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) acl.expects(:deny).with('127.0.0.1') @authconfig.read end it "should inform the current ACL if we get the 'method' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('path /certificates', 'method search,find') + @fd.stubs(:each_line).multiple_yields('path /certificates', 'method search,find') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_method).with('search') acl.expects(:restrict_method).with('find') @authconfig.read end it "should raise an error if the 'method' directive is used in a right different than a path/regex one" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('[puppetca]', 'method search,find') + @fd.stubs(:each_line).multiple_yields('[puppetca]', 'method search,find') @rights.stubs(:newright).with("puppetca", 1, 'dummy').returns(acl) lambda { @authconfig.read }.should raise_error end it "should inform the current ACL if we get the 'environment' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('path /certificates', 'environment production,development') + @fd.stubs(:each_line).multiple_yields('path /certificates', 'environment production,development') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_environment).with('production') acl.expects(:restrict_environment).with('development') @authconfig.read end it "should raise an error if the 'environment' directive is used in a right different than a path/regex one" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('[puppetca]', 'environment env') + @fd.stubs(:each_line).multiple_yields('[puppetca]', 'environment env') @rights.stubs(:newright).with("puppetca", 1, 'dummy').returns(acl) lambda { @authconfig.read }.should raise_error end it "should inform the current ACL if we get the 'auth' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('path /certificates', 'auth yes') + @fd.stubs(:each_line).multiple_yields('path /certificates', 'auth yes') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_authenticated).with('yes') @authconfig.read end it "should also allow the longest 'authenticated' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('path /certificates', 'authenticated yes') + @fd.stubs(:each_line).multiple_yields('path /certificates', 'authenticated yes') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_authenticated).with('yes') @authconfig.read end it "should raise an error if the 'auth' directive is used in a right different than a path/regex one" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) - @fd.stubs(:each).multiple_yields('[puppetca]', 'auth yes') + @fd.stubs(:each_line).multiple_yields('[puppetca]', 'auth yes') @rights.stubs(:newright).with("puppetca", 1, 'dummy').returns(acl) lambda { @authconfig.read }.should raise_error end end end diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb index e64146056..76ec91a05 100755 --- a/spec/unit/provider/package/dpkg_spec.rb +++ b/spec/unit/provider/package/dpkg_spec.rb @@ -1,224 +1,226 @@ #!/usr/bin/env rspec require 'spec_helper' provider = Puppet::Type.type(:package).provider(:dpkg) describe provider do before do @resource = stub 'resource', :[] => "asdf" @provider = provider.new(@resource) @provider.expects(:execute).never # forbid "manual" executions @fakeresult = "install ok installed asdf 1.0\n" end it "should have documentation" do provider.doc.should be_instance_of(String) end describe "when listing all instances" do before do provider.stubs(:command).with(:dpkgquery).returns "myquery" end it "should use dpkg-query" do provider.expects(:command).with(:dpkgquery).returns "myquery" provider.expects(:execpipe).with("myquery -W --showformat '${Status} ${Package} ${Version}\\n'").returns @fakeresult provider.instances end it "should create and return an instance with each parsed line from dpkg-query" do pipe = mock 'pipe' - pipe.expects(:each).yields @fakeresult + pipe.expects(:each).never + pipe.expects(:each_line).yields @fakeresult provider.expects(:execpipe).yields pipe asdf = mock 'pkg1' provider.expects(:new).with(:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg).returns asdf provider.instances.should == [asdf] end it "should warn on and ignore any lines it does not understand" do pipe = mock 'pipe' - pipe.expects(:each).yields "foobar" + pipe.expects(:each).never + pipe.expects(:each_line).yields "foobar" provider.expects(:execpipe).yields pipe Puppet.expects(:warning) provider.expects(:new).never provider.instances.should == [] end end describe "when querying the current state" do it "should use dpkg-query" do @provider.expects(:dpkgquery).with("-W", "--showformat",'${Status} ${Package} ${Version}\\n', "asdf").returns @fakeresult @provider.query end it "should consider the package purged if dpkg-query fails" do @provider.expects(:dpkgquery).raises Puppet::ExecutionFailure.new("eh") @provider.query[:ensure].should == :purged end it "should return a hash of the found status with the desired state, error state, status, name, and 'ensure'" do @provider.expects(:dpkgquery).returns @fakeresult @provider.query.should == {:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg} end it "should consider the package absent if the dpkg-query result cannot be interpreted" do @provider.expects(:dpkgquery).returns "somebaddata" @provider.query[:ensure].should == :absent end it "should fail if an error is discovered" do @provider.expects(:dpkgquery).returns @fakeresult.sub("ok", "error") lambda { @provider.query }.should raise_error(Puppet::Error) end it "should consider the package purged if it is marked 'not-installed'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "not-installed") @provider.query[:ensure].should == :purged end it "should consider the package absent if it is marked 'config-files'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "config-files") @provider.query[:ensure].should == :absent end it "should consider the package absent if it is marked 'half-installed'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-installed") @provider.query[:ensure].should == :absent end it "should consider the package absent if it is marked 'unpacked'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "unpacked") @provider.query[:ensure].should == :absent end it "should consider the package absent if it is marked 'half-configured'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-configured") @provider.query[:ensure].should == :absent end it "should consider the package held if its state is 'hold'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("install", "hold") @provider.query[:ensure].should == :held end end it "should be able to install" do @provider.should respond_to(:install) end describe "when installing" do before do @resource.stubs(:[]).with(:source).returns "mypkg" end it "should fail to install if no source is specified in the resource" do @resource.expects(:[]).with(:source).returns nil lambda { @provider.install }.should raise_error(ArgumentError) end it "should use 'dpkg -i' to install the package" do @resource.expects(:[]).with(:source).returns "mypackagefile" @provider.expects(:unhold) @provider.expects(:dpkg).with { |*command| command[-1] == "mypackagefile" and command[-2] == "-i" } @provider.install end it "should keep old config files if told to do so" do @resource.expects(:[]).with(:configfiles).returns :keep @provider.expects(:unhold) @provider.expects(:dpkg).with { |*command| command[0] == "--force-confold" } @provider.install end it "should replace old config files if told to do so" do @resource.expects(:[]).with(:configfiles).returns :replace @provider.expects(:unhold) @provider.expects(:dpkg).with { |*command| command[0] == "--force-confnew" } @provider.install end it "should ensure any hold is removed" do @provider.expects(:unhold).once @provider.expects(:dpkg) @provider.install end end describe "when holding or unholding" do before do @tempfile = stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" @tempfile.stubs(:write) Tempfile.stubs(:new).returns @tempfile end it "should install first if holding" do @provider.stubs(:execute) @provider.expects(:install).once @provider.hold end it "should execute dpkg --set-selections when holding" do @provider.stubs(:install) @provider.expects(:execute).with([:dpkg, '--set-selections'], {:stdinfile => @tempfile.path}).once @provider.hold end it "should execute dpkg --set-selections when unholding" do @provider.stubs(:install) @provider.expects(:execute).with([:dpkg, '--set-selections'], {:stdinfile => @tempfile.path}).once @provider.hold end end it "should use :install to update" do @provider.expects(:install) @provider.update end describe "when determining latest available version" do it "should return the version found by dpkg-deb" do @resource.expects(:[]).with(:source).returns "myfile" @provider.expects(:dpkg_deb).with { |*command| command[-1] == "myfile" }.returns "asdf\t1.0" @provider.latest.should == "1.0" end it "should warn if the package file contains a different package" do @provider.expects(:dpkg_deb).returns("foo\tversion") @provider.expects(:warning) @provider.latest end it "should cope with names containing ++" do @resource = stub 'resource', :[] => "asdf++" @provider = provider.new(@resource) @provider.expects(:dpkg_deb).returns "asdf++\t1.0" @provider.latest.should == "1.0" end end it "should use 'dpkg -r' to uninstall" do @provider.expects(:dpkg).with("-r", "asdf") @provider.uninstall end it "should use 'dpkg --purge' to purge" do @provider.expects(:dpkg).with("--purge", "asdf") @provider.purge end end diff --git a/spec/unit/provider/selmodule_spec.rb b/spec/unit/provider/selmodule_spec.rb index 67196667f..2ec6fc2db 100755 --- a/spec/unit/provider/selmodule_spec.rb +++ b/spec/unit/provider/selmodule_spec.rb @@ -1,66 +1,66 @@ #!/usr/bin/env rspec # Note: This unit test depends on having a sample SELinux policy file # in the same directory as this test called selmodule-example.pp # with version 1.5.0. The provided selmodule-example.pp is the first # 256 bytes taken from /usr/share/selinux/targeted/nagios.pp on Fedora 9 require 'spec_helper' provider_class = Puppet::Type.type(:selmodule).provider(:semodule) describe provider_class do before :each do @resource = stub("resource", :name => "foo") @resource.stubs(:[]).returns "foo" @provider = provider_class.new(@resource) end describe "exists? method" do it "should find a module if it is already loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" - @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields ["bar\t1.2.3\n", "foo\t4.4.4\n", "bang\t1.0.0\n"] + @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields "bar\t1.2.3\nfoo\t4.4.4\nbang\t1.0.0\n" @provider.exists?.should == :true end it "should return nil if not loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" - @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields ["bar\t1.2.3\n", "bang\t1.0.0\n"] + @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields "bar\t1.2.3\nbang\t1.0.0\n" @provider.exists?.should be_nil end it "should return nil if no modules are loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" - @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields [] + @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields "" @provider.exists?.should be_nil end end describe "selmodversion_file" do it "should return 1.5.0 for the example policy file" do @provider.expects(:selmod_name_to_filename).returns "#{File.dirname(__FILE__)}/selmodule-example.pp" @provider.selmodversion_file.should == "1.5.0" end end describe "syncversion" do it "should return :true if loaded and file modules are in sync" do @provider.expects(:selmodversion_loaded).returns "1.5.0" @provider.expects(:selmodversion_file).returns "1.5.0" @provider.syncversion.should == :true end it "should return :false if loaded and file modules are not in sync" do @provider.expects(:selmodversion_loaded).returns "1.4.0" @provider.expects(:selmodversion_file).returns "1.5.0" @provider.syncversion.should == :false end it "should return before checking file version if no loaded policy" do @provider.expects(:selmodversion_loaded).returns nil @provider.syncversion.should == :false end end end