diff --git a/lib/puppet/provider/group/groupadd.rb b/lib/puppet/provider/group/groupadd.rb index 82ed4c0c7..bcc08d9f7 100644 --- a/lib/puppet/provider/group/groupadd.rb +++ b/lib/puppet/provider/group/groupadd.rb @@ -1,29 +1,32 @@ require 'puppet/provider/nameservice/objectadd' Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "Group management via `groupadd` and its ilk. The default for most platforms " commands :add => "groupadd", :delete => "groupdel", :modify => "groupmod" + has_feature :system_groups + verify :gid, "GID must be an integer" do |value| value.is_a? Integer end def addcmd cmd = [command(:add)] if gid = @resource.should(:gid) unless gid == :absent cmd << flag(:gid) << gid end end cmd << "-o" if @resource.allowdupe? + cmd << "-r" if @resource.system? cmd << @resource[:name] cmd end end diff --git a/lib/puppet/provider/package/darwinport.rb b/lib/puppet/provider/package/darwinport.rb deleted file mode 100755 index c5f9ba28f..000000000 --- a/lib/puppet/provider/package/darwinport.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'puppet/provider/package' - -Puppet::Type.type(:package).provide :darwinport, :parent => Puppet::Provider::Package do - desc "Package management using DarwinPorts on OS X." - - confine :operatingsystem => :darwin - commands :port => "/opt/local/bin/port" - - def self.eachpkgashash - # list out all of the packages - open("| #{command(:port)} list installed") { |process| - regex = %r{(\S+)\s+@(\S+)\s+(\S+)} - fields = [:name, :ensure, :location] - hash = {} - - # now turn each returned line into a package object - process.each { |line| - hash.clear - - if match = regex.match(line) - fields.zip(match.captures) { |field,value| - hash[field] = value - } - - hash.delete :location - hash[:provider] = self.name - yield hash.dup - else - raise Puppet::DevError, - "Failed to match dpkg line #{line}" - end - } - } - end - - def self.instances - packages = [] - - eachpkgashash do |hash| - packages << new(hash) - end - - packages - end - - def install - should = @resource.should(:ensure) - - # Seems like you can always say 'upgrade' - output = port "upgrade", @resource[:name] - if output =~ /^Error: No port/ - raise Puppet::ExecutionFailure, "Could not find package #{@resource[:name]}" - end - end - - def query - version = nil - self.class.eachpkgashash do |hash| - return hash if hash[:name] == @resource[:name] - end - - nil - end - - def latest - info = port :search, "^#{@resource[:name]}$" - - if $CHILD_STATUS != 0 or info =~ /^Error/ - return nil - end - - ary = info.split(/\s+/) - version = ary[2].sub(/^@/, '') - - version - end - - def uninstall - port :uninstall, @resource[:name] - end - - def update - install - end -end - diff --git a/lib/puppet/provider/package/macports.rb b/lib/puppet/provider/package/macports.rb new file mode 100755 index 000000000..c43eb72f3 --- /dev/null +++ b/lib/puppet/provider/package/macports.rb @@ -0,0 +1,106 @@ +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| + 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/rails/inventory_node.rb b/lib/puppet/rails/inventory_node.rb index 52f8621a4..da7e61040 100644 --- a/lib/puppet/rails/inventory_node.rb +++ b/lib/puppet/rails/inventory_node.rb @@ -1,25 +1,30 @@ require 'puppet/rails/inventory_fact' class Puppet::Rails::InventoryNode < ::ActiveRecord::Base has_many :facts, :class_name => "Puppet::Rails::InventoryFact", :foreign_key => :node_id, :dependent => :delete_all + if Puppet::Util.activerecord_version >= 3.0 + # Prevents "DEPRECATION WARNING: Base.named_scope has been deprecated, please use Base.scope instead" + ActiveRecord::NamedScope::ClassMethods.module_eval { alias :named_scope :scope } + end + named_scope :has_fact_with_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value = ?", name, value], :joins => :facts } } named_scope :has_fact_without_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value != ?", name, value], :joins => :facts } } def facts_to_hash facts.inject({}) do |fact_hash,fact| fact_hash.merge(fact.name => fact.value) end end end diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb index 6dda7957c..6ebec51fe 100755 --- a/lib/puppet/type/file/source.rb +++ b/lib/puppet/type/file/source.rb @@ -1,182 +1,183 @@ require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' module Puppet # Copy files from a local or remote source. This state *only* does any work # when the remote file is an actual file; in that case, this state copies # the file down. If the remote file is a dir or a link or whatever, then # this state, during retrieval, modifies the appropriate other states # so that things get taken care of appropriately. Puppet::Type.type(:file).newparam(:source) do include Puppet::Util::Diff attr_accessor :source, :local desc "Copy a file over the current file. Uses `checksum` to determine when a file should be copied. Valid values are either fully qualified paths to files, or URIs. Currently supported URI types are *puppet* and *file*. This is one of the primary mechanisms for getting content into applications that Puppet does not directly support and is very useful for those configuration files that don't change much across sytems. For instance: class sendmail { file { \"/etc/mail/sendmail.cf\": source => \"puppet://server/modules/module_name/sendmail.cf\" } } You can also leave out the server name, in which case `puppet agent` will fill in the name of its configuration server and `puppet apply` will use the local filesystem. This makes it easy to use the same configuration in both local and centralized forms. Currently, only the `puppet` scheme is supported for source URL's. Puppet will connect to the file server running on `server` to retrieve the contents of the file. If the `server` part is empty, the behavior of the command-line interpreter (`puppet apply`) and the client demon (`puppet agent`) differs slightly: `apply` will look such a file up on the module path on the local host, whereas `agent` will connect to the puppet server that it received the manifest from. See the [fileserver configuration documentation](http://projects.puppetlabs.com/projects/puppet/wiki/File_Serving_Configuration) for information on how to configure and use file services within Puppet. If you specify multiple file sources for a file, then the first source that exists will be used. This allows you to specify what amount to search paths for files: file { \"/path/to/my/file\": source => [ \"/modules/nfs/files/file.$host\", \"/modules/nfs/files/file.$operatingsystem\", \"/modules/nfs/files/file\" ] } This will use the first found file as the source. You cannot currently copy links using this mechanism; set `links` to `follow` if any remote sources are links. " validate do |sources| sources = [sources] unless sources.is_a?(Array) sources.each do |source| begin uri = URI.parse(URI.escape(source)) rescue => detail self.fail "Could not understand source #{source}: #{detail}" end self.fail "Cannot use URLs of type '#{uri.scheme}' as source for fileserving" unless uri.scheme.nil? or %w{file puppet}.include?(uri.scheme) end end munge do |sources| sources = [sources] unless sources.is_a?(Array) sources.collect { |source| source.sub(/\/$/, '') } end def change_to_s(currentvalue, newvalue) # newvalue = "{md5}#{@metadata.checksum}" if @resource.property(:ensure).retrieve == :absent return "creating from source #{metadata.source} with contents #{metadata.checksum}" else return "replacing from source #{metadata.source} with contents #{metadata.checksum}" end end def checksum metadata && metadata.checksum end # Look up (if necessary) and return remote content. cached_attr(:content) do raise Puppet::DevError, "No source for content was stored with the metadata" unless metadata.source unless tmp = Puppet::FileServing::Content.find(metadata.source) fail "Could not find any content at %s" % metadata.source end tmp.content end # Copy the values from the source to the resource. Yay. def copy_source_values devfail "Somehow got asked to copy source values without any metadata" unless metadata # Take each of the stats and set them as states on the local file # if a value has not already been provided. [:owner, :mode, :group, :checksum].each do |metadata_method| param_name = (metadata_method == :checksum) ? :content : metadata_method next if metadata_method == :owner and !Puppet.features.root? next if metadata_method == :checksum and metadata.ftype == "directory" + next if metadata_method == :checksum and metadata.ftype == "link" and metadata.links == :manage if resource[param_name].nil? or resource[param_name] == :absent resource[param_name] = metadata.send(metadata_method) end end if resource[:ensure] == :absent # We know all we need to elsif metadata.ftype != "link" resource[:ensure] = metadata.ftype elsif @resource[:links] == :follow resource[:ensure] = :present else resource[:ensure] = "link" resource[:target] = metadata.destination end end def found? ! (metadata.nil? or metadata.ftype.nil?) end # Provide, and retrieve if necessary, the metadata for this file. Fail # if we can't find data about this host, and fail if there are any # problems in our query. cached_attr(:metadata) do return nil unless value result = nil value.each do |source| begin if data = Puppet::FileServing::Metadata.find(source) result = data result.source = source break end rescue => detail fail detail, "Could not retrieve file metadata for #{source}: #{detail}" end end fail "Could not retrieve information from source(s) #{value.join(", ")}" unless result result end def local? found? and uri and (uri.scheme || "file") == "file" end def full_path URI.unescape(uri.path) if found? and uri end def server (uri and uri.host) or Puppet.settings[:server] end def port (uri and uri.port) or Puppet.settings[:masterport] end private def uri @uri ||= URI.parse(URI.escape(metadata.source)) end end end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index cde1cfd65..066bd49df 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -1,99 +1,109 @@ - require 'etc' require 'facter' module Puppet newtype(:group) do @doc = "Manage groups. On most platforms this can only create groups. Group membership must be managed on individual users. On some platforms such as OS X, group membership is managed as an attribute of the group, not the user record. Providers must have the feature 'manages_members' to manage the 'members' property of a group record." feature :manages_members, "For directories where membership is an attribute of groups not users." + feature :system_groups, + "The provider allows you to create system groups with lower GIDs." + ensurable do desc "Create or remove the group." newvalue(:present) do provider.create end newvalue(:absent) do provider.delete end end newproperty(:gid) do desc "The group ID. Must be specified numerically. If not specified, a number will be picked, which can result in ID differences across systems and thus is not recommended. The GID is picked according to local system standards." def retrieve provider.gid end def sync if self.should == :absent raise Puppet::DevError, "GID cannot be deleted" else provider.gid = self.should end end munge do |gid| case gid when String if gid =~ /^[-0-9]+$/ gid = Integer(gid) else self.fail "Invalid GID #{gid}" end when Symbol unless gid == :absent self.devfail "Invalid GID #{gid}" end end return gid end end newproperty(:members, :array_matching => :all, :required_features => :manages_members) do desc "The members of the group. For directory services where group membership is stored in the group objects, not the users." def change_to_s(currentvalue, newvalue) currentvalue = currentvalue.join(",") if currentvalue != :absent newvalue = newvalue.join(",") super(currentvalue, newvalue) end end newparam(:auth_membership) do desc "whether the provider is authoritative for group membership." defaultto true end newparam(:name) do desc "The group name. While naming limitations vary by system, it is advisable to keep the name to the degenerate limitations, which is a maximum of 8 characters beginning with a letter." isnamevar end newparam(:allowdupe, :boolean => true) do desc "Whether to allow duplicate GIDs. This option does not work on FreeBSD (contract to the `pw` man page)." newvalues(:true, :false) defaultto false end + + newparam(:system, :boolean => true) do + desc "Whether the group is a system group with lower GID." + + newvalues(:true, :false) + + defaultto false + end end end diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index 0d09c3d5d..3658e2837 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -1,191 +1,199 @@ # This is our main way of managing processes right now. # # a service is distinct from a process in that services # can only be managed through the interface of an init script # which is why they have a search path for initscripts and such module Puppet newtype(:service) do @doc = "Manage running services. Service support unfortunately varies - widely by platform -- some platforms have very little if any + widely by platform --- some platforms have very little if any concept of a running service, and some have a very codified and powerful concept. Puppet's service support will generally be able - to make up for any inherent shortcomings (e.g., if there is no + to do the right thing regardless (e.g., if there is no 'status' command, then Puppet will look in the process table for a command matching the service name), but the more information you - can provide the better behaviour you will get. Or, you can just - use a platform that has very good service support. + can provide, the better behaviour you will get. In particular, any + virtual services that don't have a predictable entry in the process table + (for example, `network` on Red Hat/CentOS systems) will manifest odd + behavior on restarts if you don't specify `hasstatus` or a `status` + command. Note that if a `service` receives an event from another resource, the service will get restarted. The actual command to restart the - service depends on the platform. You can provide a special command - for restarting with the `restart` attribute." + service depends on the platform. You can provide an explicit command + for restarting with the `restart` attribute, or use the init script's + restart command with the `hasrestart` attribute; if you do neither, + the service's stop and start commands will be used." feature :refreshable, "The provider can restart the service.", :methods => [:restart] feature :enableable, "The provider can enable and disable the service", :methods => [:disable, :enable, :enabled?] feature :controllable, "The provider uses a control variable." newproperty(:enable, :required_features => :enableable) do desc "Whether a service should be enabled to start at boot. This property behaves quite differently depending on the platform; wherever possible, it relies on local tools to enable or disable a given service." newvalue(:true, :event => :service_enabled) do provider.enable end newvalue(:false, :event => :service_disabled) do provider.disable end def retrieve provider.enabled? end end # Handle whether the service should actually be running right now. newproperty(:ensure) do desc "Whether a service should be running." newvalue(:stopped, :event => :service_stopped) do provider.stop end newvalue(:running, :event => :service_started) do provider.start end aliasvalue(:false, :stopped) aliasvalue(:true, :running) def retrieve provider.status end def sync event = super() if property = @resource.property(:enable) val = property.retrieve property.sync unless property.safe_insync?(val) end event end end newparam(:binary) do desc "The path to the daemon. This is only used for systems that do not support init scripts. This binary will be used to start the service if no `start` parameter is provided." end newparam(:hasstatus) do desc "Declare the the service's init script has a functional status command. Based on testing, it was found that a large number of init scripts on different platforms do not support any kind of status command; thus, you must specify manually whether the service you are running has such a - command (or you can specify a specific command using the - `status` parameter). - - If you do not specify anything, then the service name will be - looked for in the process table." + command. Alternately, you can provide a specific command using the + `status` attribute. + + If you specify neither of these, then Puppet will look for the + service name in the process table. Be aware that 'virtual' init + scripts such as networking will respond poorly to refresh events + (via notify and subscribe relationships) if you don't override + this default behavior." newvalues(:true, :false) end newparam(:name) do desc "The name of the service to run. This name is used to find the service in whatever service subsystem it is in." isnamevar end newparam(:path) do desc "The search path for finding init scripts. Multiple values should be separated by colons or provided as an array." munge do |value| value = [value] unless value.is_a?(Array) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] # It affects stand-alone blocks, too. paths = value.flatten.collect { |p| x = p.split(":") }.flatten end defaultto { provider.class.defpath if provider.class.respond_to?(:defpath) } end newparam(:pattern) do desc "The pattern to search for in the process table. This is used for stopping services on platforms that do not support init scripts, and is also used for determining service status on those service whose init scripts do not include a status command. If this is left unspecified and is needed to check the status of a service, then the service name will be used instead. The pattern can be a simple string or any legal Ruby pattern." defaultto { @resource[:binary] || @resource[:name] } end newparam(:restart) do desc "Specify a *restart* command manually. If left unspecified, the service will be stopped and then started." end newparam(:start) do desc "Specify a *start* command manually. Most service subsystems support a `start` command, so this will not need to be specified." end newparam(:status) do desc "Specify a *status* command manually. This command must return 0 if the service is running and a nonzero value otherwise. Ideally, these return codes should conform to [the LSB's specification for init script status actions](http://refspecs.freestandards.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html), but puppet only considers the difference between 0 and nonzero to be relevant. If left unspecified, the status method will be determined automatically, usually by looking for the service in the process table." end newparam(:stop) do desc "Specify a *stop* command manually." end newparam(:control) do desc "The control variable used to manage services (originally for HP-UX). Defaults to the upcased service name plus `START` replacing dots with underscores, for those providers that support the `controllable` feature." defaultto { resource.name.gsub(".","_").upcase + "_START" if resource.provider.controllable? } end newparam :hasrestart do desc "Specify that an init script has a `restart` option. Otherwise, the init script's `stop` and `start` methods are used." newvalues(:true, :false) end newparam(:manifest) do desc "Specify a command to config a service, or a path to a manifest to do so." end # Basically just a synonym for restarting. Used to respond # to events. def refresh # Only restart if we're actually running if (@parameters[:ensure] || newattr(:ensure)).retrieve == :running provider.restart else debug "Skipping restart; service is not running" end end end end diff --git a/spec/unit/indirector/facts/inventory_active_record_spec.rb b/spec/unit/indirector/facts/inventory_active_record_spec.rb index 9558abde2..022150c76 100644 --- a/spec/unit/indirector/facts/inventory_active_record_spec.rb +++ b/spec/unit/indirector/facts/inventory_active_record_spec.rb @@ -1,163 +1,167 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' -require 'sqlite3' rescue nil +begin + require 'sqlite3' +rescue LoadError +end require 'tempfile' require 'puppet/rails' describe "Puppet::Node::Facts::InventoryActiveRecord", :if => (Puppet.features.rails? and defined? SQLite3) do let(:terminus) { Puppet::Node::Facts::InventoryActiveRecord.new } before :all do require 'puppet/indirector/facts/inventory_active_record' @dbfile = Tempfile.new("testdb") @dbfile.close end after :all do Puppet::Node::Facts.indirection.reset_terminus_class @dbfile.unlink end before :each do Puppet::Node::Facts.terminus_class = :inventory_active_record Puppet[:dbadapter] = 'sqlite3' Puppet[:dblocation] = @dbfile.path Puppet[:railslog] = "/dev/null" Puppet::Rails.init end after :each do Puppet::Rails.teardown + ActiveRecord::Base.remove_connection end describe "#save" do it "should use an existing node if possible" do node = Puppet::Rails::InventoryNode.new(:name => "foo", :timestamp => Time.now) node.save Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin").save Puppet::Rails::InventoryNode.count.should == 1 Puppet::Rails::InventoryNode.first.should == node end it "should create a new node if one can't be found" do # This test isn't valid if there are nodes to begin with Puppet::Rails::InventoryNode.count.should == 0 Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin").save Puppet::Rails::InventoryNode.count.should == 1 Puppet::Rails::InventoryNode.first.name.should == "foo" end it "should save the facts" do Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin").save Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should =~ [["uptime_days","60"],["kernel","Darwin"]] end it "should remove the previous facts for an existing node" do Puppet::Node::Facts.new("foo", "uptime_days" => "30", "kernel" => "Darwin").save bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "35", "kernel" => "Linux") foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "is_virtual" => "false") bar_facts.save foo_facts.save Puppet::Node::Facts.find("bar").should == bar_facts Puppet::Node::Facts.find("foo").should == foo_facts Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should_not include(["uptime_days", "30"], ["kernel", "Darwin"]) end end describe "#find" do before do @foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") @bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "30", "kernel" => "Linux") @foo_facts.save @bar_facts.save end it "should identify facts by node name" do Puppet::Node::Facts.find("foo").should == @foo_facts end it "should return nil if no node instance can be found" do Puppet::Node::Facts.find("non-existent node").should == nil end end describe "#search" do def search_request(conditions) Puppet::Indirector::Request.new(:facts, :search, nil, conditions) end before :each do @now = Time.now @foo = Puppet::Node::Facts.new("foo", "fact1" => "value1", "fact2" => "value2", "uptime_days" => "30") @bar = Puppet::Node::Facts.new("bar", "fact1" => "value1", "uptime_days" => "60") @baz = Puppet::Node::Facts.new("baz", "fact1" => "value2", "fact2" => "value1", "uptime_days" => "90") @bat = Puppet::Node::Facts.new("bat") @foo.timestamp = @now - 3600*1 @bar.timestamp = @now - 3600*3 @baz.timestamp = @now - 3600*5 @bat.timestamp = @now - 3600*7 @foo.save @bar.save @baz.save @bat.save end it "should return node names that match 'equal' constraints" do request = search_request('facts.fact1.eq' => 'value1', 'facts.fact2.eq' => 'value2') terminus.search(request).should == ["foo"] end it "should return node names that match 'not equal' constraints" do request = search_request('facts.fact1.ne' => 'value2') terminus.search(request).should == ["bar","foo"] end it "should return node names that match strict inequality constraints" do request = search_request('facts.uptime_days.gt' => '20', 'facts.uptime_days.lt' => '70') terminus.search(request).should == ["bar","foo"] end it "should return node names that match non-strict inequality constraints" do request = search_request('facts.uptime_days.ge' => '30', 'facts.uptime_days.le' => '60') terminus.search(request).should == ["bar","foo"] end it "should return node names whose facts are within a given timeframe" do request = search_request('meta.timestamp.ge' => @now - 3600*5, 'meta.timestamp.le' => @now - 3600*1) terminus.search(request).should == ["bar","baz","foo"] end it "should return node names whose facts are from a specific time" do request = search_request('meta.timestamp.eq' => @now - 3600*3) terminus.search(request).should == ["bar"] end it "should return node names whose facts are not from a specific time" do request = search_request('meta.timestamp.ne' => @now - 3600*1) terminus.search(request).should == ["bar","bat","baz"] end it "should perform strict searches on nodes by timestamp" do request = search_request('meta.timestamp.gt' => @now - 3600*5, 'meta.timestamp.lt' => @now - 3600*1) terminus.search(request).should == ["bar"] end it "should search nodes based on both facts and timestamp values" do request = search_request('facts.uptime_days.gt' => '45', 'meta.timestamp.lt' => @now - 3600*4) terminus.search(request).should == ["baz"] end end end diff --git a/spec/unit/provider/group/groupadd_spec.rb b/spec/unit/provider/group/groupadd_spec.rb index 33d9acd98..65cc887e4 100755 --- a/spec/unit/provider/group/groupadd_spec.rb +++ b/spec/unit/provider/group/groupadd_spec.rb @@ -1,31 +1,41 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' provider_class = Puppet::Type.type(:group).provider(:groupadd) describe provider_class do before do @resource = stub("resource", :name => "mygroup") @provider = provider_class.new(@resource) end - # #1360 it "should add -o when allowdupe is enabled and the group is being created" do @resource.stubs(:should).returns "fakeval" @resource.stubs(:[]).returns "fakeval" + @resource.stubs(:system?).returns true @resource.expects(:allowdupe?).returns true @provider.expects(:execute).with { |args| args.include?("-o") } @provider.create end it "should add -o when allowdupe is enabled and the gid is being modified" do @resource.stubs(:should).returns "fakeval" @resource.stubs(:[]).returns "fakeval" @resource.expects(:allowdupe?).returns true @provider.expects(:execute).with { |args| args.include?("-o") } @provider.gid = 150 end + + it "should add -r when system is enabled and the group is being created" do + @resource.stubs(:should).returns "fakeval" + @resource.stubs(:[]).returns "fakeval" + @resource.expects(:system?).returns true + @resource.stubs(:allowdupe?).returns true + @provider.expects(:execute).with { |args| args.include?("-r") } + + @provider.create + end end diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb new file mode 100755 index 000000000..7d1acd537 --- /dev/null +++ b/spec/unit/provider/package/macports_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +provider_class = Puppet::Type.type(:package).provider(:macports) + +describe provider_class do + let :resource_name do + "foo" + end + + let :resource do + Puppet::Type.type(:package).new(:name => resource_name, :provider => :macports) + end + + let :provider do + prov = resource.provider + prov.expects(:execute).never + prov + end + + let :current_hash do + {:name => resource_name, :ensure => "1.2.3", :revision => "1", :provider => :macports} + end + + describe "provider features" do + subject { provider } + + it { should be_installable } + it { should be_uninstallable } + it { should be_upgradeable } + it { should be_versionable } + end + + describe "when listing all instances" do + it "should call port -q installed" do + provider_class.expects(:port).with("-q", :installed).returns("") + provider_class.instances + end + + it "should create instances from active ports" do + provider_class.expects(:port).returns("foo @1.234.5_2 (active)") + provider_class.instances.size.should == 1 + end + + it "should ignore ports that aren't activated" do + provider_class.expects(:port).returns("foo @1.234.5_2") + provider_class.instances.size.should == 0 + end + end + + describe "when installing" do + it "should not specify a version when ensure is set to latest" do + resource[:ensure] = :latest + provider.expects(:port).with { |flag, method, name, version| + version.should be_nil + } + provider.install + end + + it "should not specify a version when ensure is set to present" do + resource[:ensure] = :present + provider.expects(:port).with { |flag, method, name, version| + version.should be_nil + } + provider.install + end + + it "should specify a version when ensure is set to a version" do + resource[:ensure] = "1.2.3" + provider.expects(:port).with { |flag, method, name, version| + version.should be + } + provider.install + end + end + + describe "when querying for the latest version" do + let :new_info_line do + "1.2.3 2" + end + let :infoargs do + ["-q", :info, "--line", "--version", "--revision", resource_name] + end + + it "should return nil when the package cannot be found" do + resource[:name] = resource_name + provider.expects(:port).returns("") + provider.latest.should == nil + end + + it "should return the current version if the installed port has the same revision" do + current_hash[:revision] = "2" + provider.expects(:port).with(*infoargs).returns(new_info_line) + provider.expects(:query).returns(current_hash) + provider.latest.should == current_hash[:ensure] + end + + it "should return the new version_revision if the installed port has a lower revision" do + current_hash[:revision] = "1" + provider.expects(:port).with(*infoargs).returns(new_info_line) + provider.expects(:query).returns(current_hash) + provider.latest.should == "1.2.3_2" + end + end + + describe "when updating a port" do + it "should execute port upgrade if the port is installed" do + resource[:name] = resource_name + resource[:ensure] = :present + provider.expects(:query).returns(current_hash) + provider.expects(:port).with("-q", :upgrade, resource_name) + provider.update + end + + it "should execute port install if the port is not installed" do + resource[:name] = resource_name + resource[:ensure] = :present + provider.expects(:query).returns("") + provider.expects(:port).with("-q", :install, resource_name) + provider.update + end + end +end diff --git a/spec/unit/type/file/source_spec.rb b/spec/unit/type/file/source_spec.rb index 00cc2f235..6e04fa2a5 100755 --- a/spec/unit/type/file/source_spec.rb +++ b/spec/unit/type/file/source_spec.rb @@ -1,272 +1,273 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } source = Puppet::Type.type(:file).attrclass(:source) describe Puppet::Type.type(:file).attrclass(:source) do before do # Wow that's a messy interface to the resource. @resource = stub 'resource', :[]= => nil, :property => nil, :catalog => stub("catalog", :dependent_data_expired? => false), :line => 0, :file => '' end it "should be a subclass of Parameter" do source.superclass.must == Puppet::Parameter end describe "when initializing" do it "should fail if the set values are not URLs" do s = source.new(:resource => @resource) URI.expects(:parse).with('foo').raises RuntimeError lambda { s.value = %w{foo} }.must raise_error(Puppet::Error) end it "should fail if the URI is not a local file, file URI, or puppet URI" do s = source.new(:resource => @resource) lambda { s.value = %w{http://foo/bar} }.must raise_error(Puppet::Error) end end it "should have a method for retrieving its metadata" do source.new(:resource => @resource).must respond_to(:metadata) end it "should have a method for setting its metadata" do source.new(:resource => @resource).must respond_to(:metadata=) end describe "when returning the metadata" do before do @metadata = stub 'metadata', :source= => nil end it "should return already-available metadata" do @source = source.new(:resource => @resource) @source.metadata = "foo" @source.metadata.should == "foo" end it "should return nil if no @should value is set and no metadata is available" do @source = source.new(:resource => @resource) @source.metadata.should be_nil end it "should collect its metadata using the Metadata class if it is not already set" do @source = source.new(:resource => @resource, :value => "/foo/bar") Puppet::FileServing::Metadata.expects(:find).with("/foo/bar").returns @metadata @source.metadata end it "should use the metadata from the first found source" do metadata = stub 'metadata', :source= => nil @source = source.new(:resource => @resource, :value => ["/foo/bar", "/fee/booz"]) Puppet::FileServing::Metadata.expects(:find).with("/foo/bar").returns nil Puppet::FileServing::Metadata.expects(:find).with("/fee/booz").returns metadata @source.metadata.should equal(metadata) end it "should store the found source as the metadata's source" do metadata = mock 'metadata' @source = source.new(:resource => @resource, :value => "/foo/bar") Puppet::FileServing::Metadata.expects(:find).with("/foo/bar").returns metadata metadata.expects(:source=).with("/foo/bar") @source.metadata end it "should fail intelligently if an exception is encountered while querying for metadata" do @source = source.new(:resource => @resource, :value => "/foo/bar") Puppet::FileServing::Metadata.expects(:find).with("/foo/bar").raises RuntimeError @source.expects(:fail).raises ArgumentError lambda { @source.metadata }.should raise_error(ArgumentError) end it "should fail if no specified sources can be found" do @source = source.new(:resource => @resource, :value => "/foo/bar") Puppet::FileServing::Metadata.expects(:find).with("/foo/bar").returns nil @source.expects(:fail).raises RuntimeError lambda { @source.metadata }.should raise_error(RuntimeError) end it "should expire the metadata appropriately" do expirer = stub 'expired', :dependent_data_expired? => true metadata = stub 'metadata', :source= => nil Puppet::FileServing::Metadata.expects(:find).with("/fee/booz").returns metadata @source = source.new(:resource => @resource, :value => ["/fee/booz"]) @source.metadata = "foo" @source.stubs(:expirer).returns expirer @source.metadata.should_not == "foo" end end it "should have a method for setting the desired values on the resource" do source.new(:resource => @resource).must respond_to(:copy_source_values) end describe "when copying the source values" do before do @resource = Puppet::Type.type(:file).new :path => "/foo/bar" @source = source.new(:resource => @resource) @metadata = stub 'metadata', :owner => 100, :group => 200, :mode => 123, :checksum => "{md5}asdfasdf", :ftype => "file" @source.stubs(:metadata).returns @metadata end it "should fail if there is no metadata" do @source.stubs(:metadata).returns nil @source.expects(:devfail).raises ArgumentError lambda { @source.copy_source_values }.should raise_error(ArgumentError) end it "should set :ensure to the file type" do @metadata.stubs(:ftype).returns "file" @source.copy_source_values @resource[:ensure].must == :file end it "should not set 'ensure' if it is already set to 'absent'" do @metadata.stubs(:ftype).returns "file" @resource[:ensure] = :absent @source.copy_source_values @resource[:ensure].must == :absent end describe "and the source is a file" do before do @metadata.stubs(:ftype).returns "file" end it "should copy the metadata's owner, group, checksum, and mode to the resource if they are not set on the resource" do Puppet.features.expects(:root?).returns true @source.copy_source_values @resource[:owner].must == 100 @resource[:group].must == 200 @resource[:mode].must == "173" # Metadata calls it checksum, we call it content. @resource[:content].must == @metadata.checksum end it "should not copy the metadata's owner to the resource if it is already set" do @resource[:owner] = 1 @resource[:group] = 2 @resource[:mode] = 3 @resource[:content] = "foobar" @source.copy_source_values @resource[:owner].must == 1 @resource[:group].must == 2 @resource[:mode].must == "3" @resource[:content].should_not == @metadata.checksum end describe "and puppet is not running as root" do it "should not try to set the owner" do Puppet.features.expects(:root?).returns false @source.copy_source_values @resource[:owner].should be_nil end end end describe "and the source is a link" do it "should set the target to the link destination" do @metadata.stubs(:ftype).returns "link" + @metadata.stubs(:links).returns "manage" @resource.stubs(:[]) @resource.stubs(:[]=) @metadata.expects(:destination).returns "/path/to/symlink" @resource.expects(:[]=).with(:target, "/path/to/symlink") @source.copy_source_values end end end it "should have a local? method" do source.new(:resource => @resource).must be_respond_to(:local?) end context "when accessing source properties" do before(:each) do @source = source.new(:resource => @resource) @metadata = stub_everything @source.stubs(:metadata).returns(@metadata) end describe "for local sources" do before(:each) do @metadata.stubs(:ftype).returns "file" @metadata.stubs(:source).returns("file:///path/to/source") end it "should be local" do @source.must be_local end it "should be local if there is no scheme" do @metadata.stubs(:source).returns("/path/to/source") @source.must be_local end it "should be able to return the metadata source full path" do @source.full_path.should == "/path/to/source" end end describe "for remote sources" do before(:each) do @metadata.stubs(:ftype).returns "file" @metadata.stubs(:source).returns("puppet://server:8192/path/to/source") end it "should not be local" do @source.should_not be_local end it "should be able to return the metadata source full path" do @source.full_path.should == "/path/to/source" end it "should be able to return the source server" do @source.server.should == "server" end it "should be able to return the source port" do @source.port.should == 8192 end describe "which don't specify server or port" do before(:each) do @metadata.stubs(:source).returns("puppet:///path/to/source") end it "should return the default source server" do Puppet.settings.expects(:[]).with(:server).returns("myserver") @source.server.should == "myserver" end it "should return the default source port" do Puppet.settings.expects(:[]).with(:masterport).returns(1234) @source.port.should == 1234 end end end end end diff --git a/spec/unit/type/group_spec.rb b/spec/unit/type/group_spec.rb index b41ce71a0..e373dac6e 100755 --- a/spec/unit/type/group_spec.rb +++ b/spec/unit/type/group_spec.rb @@ -1,57 +1,64 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:group) do before do ENV["PATH"] += File::PATH_SEPARATOR + "/usr/sbin" unless ENV["PATH"].split(File::PATH_SEPARATOR).include?("/usr/sbin") @class = Puppet::Type.type(:group) end it "should have a default provider" do @class.defaultprovider.should_not be_nil end it "should have a default provider inheriting from Puppet::Provider" do @class.defaultprovider.ancestors.should be_include(Puppet::Provider) end + it "should have a system_groups feature" do + @class.provider_feature(:system_groups).should_not be_nil + end + describe "when validating attributes" do [:name, :allowdupe].each do |param| it "should have a #{param} parameter" do @class.attrtype(param).should == :param end end [:ensure, :gid].each do |param| it "should have a #{param} property" do @class.attrtype(param).should == :property end end it "should convert gids provided as strings into integers" do @class.new(:name => "foo", :gid => "15")[:gid].should == 15 end it "should accepts gids provided as integers" do @class.new(:name => "foo", :gid => 15)[:gid].should == 15 end end - # #1407 - we need to declare the allowdupe param as boolean. it "should have a boolean method for determining if duplicates are allowed" do @class.new(:name => "foo").methods.should be_include("allowdupe?") end + it "should have a boolean method for determining if system groups are allowed" do + @class.new(:name => "foo").methods.should be_include("system?") + end + it "should call 'create' to create the group" do group = @class.new(:name => "foo", :ensure => :present) group.provider.expects(:create) group.parameter(:ensure).sync end it "should call 'delete' to remove the group" do group = @class.new(:name => "foo", :ensure => :absent) group.provider.expects(:delete) group.parameter(:ensure).sync end end