diff --git a/lib/puppet/configuration.rb b/lib/puppet/configuration.rb index 074e656e0..f67be5999 100644 --- a/lib/puppet/configuration.rb +++ b/lib/puppet/configuration.rb @@ -1,171 +1,168 @@ # The majority of the system configuration parameters are set in this file. module Puppet # If we're running the standalone puppet process as a non-root user, # use basedirs that are in the user's home directory. conf = nil var = nil if self.name == "puppet" and Puppet::SUIDManager.uid != 0 conf = File.expand_path("~/.puppet") var = File.expand_path("~/.puppet/var") else # Else, use system-wide directories. conf = "/etc/puppet" var = "/var/puppet" end self.setdefaults(:puppet, :confdir => [conf, "The main Puppet configuration directory."], :vardir => [var, "Where Puppet stores dynamic and growing data."] ) if self.name == "puppetmasterd" - self.setdefaults(:puppetmasterd, - :logdir => {:default => "$vardir/log", - :mode => 0750, - :owner => "$user", - :group => "$group", - :desc => "The Puppet log directory." - } - ) + logopts = {:default => "$vardir/log", + :mode => 0750, + :owner => "$user", + :group => "$group", + :desc => "The Puppet log directory." + } else - self.setdefaults(:puppet, - :logdir => ["$vardir/log", "The Puppet log directory."] - ) + logopts = ["$vardir/log", "The Puppet log directory."] end + setdefaults(:puppet, :logdir => logopts) self.setdefaults(:puppet, :trace => [false, "Whether to print stack traces on some errors"], :autoflush => [false, "Whether log files should always flush to disk."], :syslogfacility => ["daemon", "What syslog facility to use when logging to syslog. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up."], :statedir => { :default => "$vardir/state", :mode => 01777, :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => "$vardir/run", :mode => 01777, :desc => "Where Puppet PID files are kept." }, :lockdir => { :default => "$vardir/locks", :mode => 01777, :desc => "Where lock files are kept." }, :statefile => { :default => "$statedir/state.yaml", :mode => 0660, :desc => "Where puppetd and puppetmasterd store state associated with the running configuration. In the case of puppetmasterd, this file reflects the state discovered through interacting with clients." }, :ssldir => { :default => "$confdir/ssl", :mode => 0771, :owner => "root", :desc => "Where SSL certificates are kept." }, :genconfig => [false, "Whether to just print a configuration to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :genmanifest => [false, "Whether to just print a manifest to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :configprint => ["", "Print the value of a specific configuration parameter. If a parameter is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'. This feature is only available in Puppet versions higher than 0.18.4."], :color => ["ansi", "Whether to use colors when logging to the console. Valid values are ``ansi`` (equivalent to ``true``), ``html`` (mostly used during testing with TextMate), and ``false``, which produces no color."], :mkusers => [false, "Whether to create the necessary user and group that puppetd will run as."], :path => {:default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process.", :hook => proc do |value| ENV["PATH"] = value unless value == "none" end } ) # Define the config default. self.setdefaults(self.name, :config => ["$confdir/#{self.name}.conf", "The configuration file for #{self.name}."] ) self.setdefaults("puppetmasterd", :user => ["puppet", "The user puppetmasterd should run as."], :group => ["puppet", "The group puppetmasterd should run as."], :manifestdir => ["$confdir/manifests", "Where puppetmasterd looks for its manifests."], :manifest => ["$manifestdir/site.pp", "The entry-point manifest for puppetmasterd."], :masterlog => { :default => "$logdir/puppetmaster.log", :owner => "$user", :group => "$group", :mode => 0660, :desc => "Where puppetmasterd logs. This is generally not used, since syslog is the default log destination." }, :masterhttplog => { :default => "$logdir/masterhttp.log", :owner => "$user", :group => "$group", :mode => 0660, :create => true, :desc => "Where the puppetmasterd web server logs." }, :masterport => [8140, "Which port puppetmasterd listens on."], :parseonly => [false, "Just check the syntax of the manifests."], :node_name => ["cert", "How the puppetmaster determines the client's identity and sets the 'hostname' fact for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)"] ) self.setdefaults("puppetd", :localconfig => { :default => "$confdir/localconfig", :owner => "root", :mode => 0660, :desc => "Where puppetd caches the local configuration. An extension indicating the cache format is added automatically."}, :classfile => { :default => "$confdir/classes.txt", :owner => "root", :mode => 0644, :desc => "The file in which puppetd stores a list of the classes associated with the retrieved configuratiion. Can be loaded in the separate ``puppet`` executable using the ``--loadclasses`` option."}, :puppetdlog => { :default => "$logdir/puppetd.log", :owner => "root", :mode => 0640, :desc => "The log file for puppetd. This is generally not used." }, :httplog => { :default => "$logdir/http.log", :owner => "root", :mode => 0640, :desc => "Where the puppetd web server logs." }, :server => ["puppet", "The server to which server puppetd should connect"], :ignoreschedules => [false, "Boolean; whether puppetd should ignore schedules. This is useful for initial puppetd runs."], :puppetport => [8139, "Which port puppetd listens on."], :noop => [false, "Whether puppetd should be run in noop mode."], :runinterval => [1800, # 30 minutes "How often puppetd applies the client configuration; in seconds"] ) end # $Id$ diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb index ea54c2dcf..841d8585f 100644 --- a/lib/puppet/parser/collector.rb +++ b/lib/puppet/parser/collector.rb @@ -1,153 +1,162 @@ # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::Collector attr_accessor :type, :scope, :vquery, :rquery, :form, :resources # Collect exported objects. def collect_exported require 'puppet/rails' - Puppet.info "collecting %s" % @type # First get everything from the export table. Just reuse our # collect_virtual method but tell it to use 'exported? for the test. resources = collect_virtual(true) count = 0 + unless @scope.host + raise Puppet::DevError, "Cannot collect resources for a nil host" + end + # We're going to collect objects from rails, but we don't want any # objects from this host. host = Puppet::Rails::Host.find_by_name(@scope.host) args = {} if host args[:conditions] = "host_id != #{host.id}" else #Puppet.info "Host %s is uninitialized" % @scope.host end - Puppet.info "collecting %s" % @type # Now look them up in the rails db. When we support attribute comparison # and such, we'll need to vary the conditions, but this works with no # attributes, anyway. time = Puppet::Util.thinmark do - Puppet::Rails::Resource.find_all_by_type_and_exported(@type, true, + Puppet::Rails::Resource.find_all_by_restype_and_exported(@type, true, args ).each do |obj| - if existing = @scope.findresource(obj.type, obj.title) - # See if we exported it; if so, just move on - if @scope.host == obj.host.name - next - else - # Next see if we've already collected this resource - if existing.rails_id == obj.id - # This is the one we've already collected - next - else - raise Puppet::ParseError, - "Exported resource %s[%s] cannot override local resource" % - [obj.type, obj.title] - end - end + if resource = export_resource(obj) + count += 1 + resources << resource end - count += 1 - begin - resource = obj.to_resource(self.scope) - - # XXX Because the scopes don't expect objects to return values, - # we have to manually add our objects to the scope. This is - # uber-lame. - scope.setresource(resource) - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - raise - end - resource.exported = false - - resources << resource end end - scope.debug("Collected %s %s resources in %.2f seconds" % - [count, @type, time]) + scope.debug("Collected %s %s resource%s in %.2f seconds" % + [count, @type, count == 1 ? "" : "s", time]) return resources end def collect_resources unless @resources.is_a?(Array) @resources = [@resources] end method = "collect_#{form.to_s}_resources" send(method) end def collect_exported_resources raise Puppet::ParseError, "realize() is not yet implemented for exported resources" end def collect_virtual_resources @resources.collect do |ref| if res = @scope.findresource(ref.to_s) res else raise Puppet::ParseError, "Could not find resource %s" % ref end end.each do |res| res.virtual = false end end # Collect just virtual objects, from our local configuration. def collect_virtual(exported = false) if exported method = :exported? else method = :virtual? end scope.resources.find_all do |resource| resource.type == @type and resource.send(method) and match?(resource) end end # Call the collection method, mark all of the returned objects as non-virtual, # and then delete this object from the list of collections to evaluate. def evaluate if self.resources return collect_resources end method = "collect_#{@form.to_s}" objects = send(method).each do |obj| obj.virtual = false end # And then remove us from the list of collections, since we've # now been evaluated. @scope.collections.delete(self) objects end def initialize(scope, type, equery, vquery, form) @scope = scope @type = type @equery = equery @vquery = vquery @form = form @tests = [] end # Does the resource match our tests? We don't yet support tests, # so it's always true at the moment. def match?(resource) if self.vquery return self.vquery.call(resource) else return true end end + + def export_resource(obj) + if existing = @scope.findresource(obj.restype, obj.title) + # See if we exported it; if so, just move on + if @scope.host == obj.host.name + return nil + else + # Next see if we've already collected this resource + if existing.rails_id == obj.id + # This is the one we've already collected + return nil + else + raise Puppet::ParseError, + "Exported resource %s cannot override local resource" % + [obj.ref] + end + end + end + + begin + resource = obj.to_resource(self.scope) + + # XXX Because the scopes don't expect objects to return values, + # we have to manually add our objects to the scope. This is + # über-lame. + scope.setresource(resource) + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + raise + end + resource.exported = false + + return resource + end end # $Id$ diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index 7ea85dded..857b5fa39 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,348 +1,340 @@ # A resource that we're managing. This handles making sure that only subclasses # can set parameters. class Puppet::Parser::Resource require 'puppet/parser/resource/param' require 'puppet/parser/resource/reference' ResParam = Struct.new :name, :value, :source, :line, :file include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging attr_accessor :source, :line, :file, :scope, :rails_id attr_accessor :virtual, :override, :params, :translated attr_reader :exported attr_writer :tags # Proxy a few methods to our @ref object. [:builtin?, :type, :title].each do |method| define_method(method) do @ref.send(method) end end # Set up some boolean test methods [:exported, :translated, :override].each do |method| newmeth = (method.to_s + "?").intern define_method(newmeth) do self.send(method) end end def [](param) param = symbolize(param) if param == :title return self.title end if @params.has_key?(param) @params[param].value else nil end end # Add default values from our definition. def adddefaults defaults = scope.lookupdefaults(self.type) defaults.each do |name, param| unless @params.include?(param.name) self.debug "Adding default for %s" % param.name @params[param.name] = param end end end # Add any metaparams defined in our scope. This actually adds any metaparams # from any parent scope, and there's currently no way to turn that off. def addmetaparams Puppet::Type.eachmetaparam do |name| next if self[name] if val = scope.lookupvar(name.to_s, false) unless val == :undefined set Param.new(:name => name, :value => val, :source => scope.source) end end end end # Add any overrides for this object. def addoverrides overrides = scope.lookupoverrides(self) overrides.each do |over| self.merge(over) end overrides.clear end def builtin=(bool) @ref.builtin = bool end # Retrieve the associated definition and evaluate it. def evaluate if klass = @ref.definedtype finish() scope.deleteresource(self) return klass.evaluate(:scope => scope, :type => self.type, :name => self.title, :arguments => self.to_hash, :scope => self.scope, :exported => self.exported ) elsif builtin? devfail "Cannot evaluate a builtin type" else self.fail "Cannot find definition %s" % self.type end - - -# if builtin? -# devfail "Cannot evaluate a builtin type" -# end -# -# unless klass = scope.finddefine(self.type) -# self.fail "Cannot find definition %s" % self.type -# end -# -# finish() -# -# scope.deleteresource(self) -# -# return klass.evaluate(:scope => scope, -# :type => self.type, -# :name => self.title, -# :arguments => self.to_hash, -# :scope => self.scope, -# :exported => self.exported -# ) ensure @evaluated = true end def exported=(value) if value @virtual = true @exported = value else @exported = value end end def evaluated? if defined? @evaluated and @evaluated true else false end end # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish addoverrides() adddefaults() addmetaparams() end def initialize(options) options = symbolize_options(options) # Collect the options necessary to make the reference. refopts = [:type, :title].inject({}) do |hash, param| hash[param] = options[param] options.delete(param) hash end @params = {} tmpparams = nil if tmpparams = options[:params] options.delete(:params) end # Now set the rest of the options. set_options(options) @ref = Reference.new(refopts) requiredopts(:scope, :source) @ref.scope = self.scope if tmpparams tmpparams.each do |param| # We use the method here, because it does type-checking. set(param) end end end # Merge an override resource in. def merge(resource) # Some of these might fail, but they'll fail in the way we want. resource.params.each do |name, param| set(param) end end # This *significantly* reduces the number of calls to Puppet.[]. def paramcheck? unless defined? @@paramcheck @@paramcheck = Puppet[:paramcheck] end @@paramcheck end # Verify that all passed parameters are valid. This throws an error if there's # a problem, so we don't have to worry about the return value. def paramcheck(param) # Now make sure it's a valid argument to our class. These checks # are organized in order of commonhood -- most types, it's a valid argument # and paramcheck is enabled. if @ref.typeclass.validattr?(param) true elsif (param == "name" or param == "title") # always allow these true elsif paramcheck? self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % [param.inspect, @ref.type] end end # A temporary occasion, until I get paths in the scopes figured out. def path to_s end # Return the short version of our name. def ref @ref.to_s end # You have to pass a Resource::Param to this. def set(param) # Because definitions are now parse-time, I can paramcheck immediately. paramcheck(param.name) if current = @params[param.name] # XXX Should we ignore any settings that have the same values? if param.source.child_of?(current.source) # Replace it, keeping all of its info. @params[param.name] = param else if Puppet[:trace] puts caller end fail Puppet::ParseError, "Parameter %s is already set on %s by %s" % [param.name, self.to_s, param.source] end else if self.source == param.source or param.source.child_of?(self.source) @params[param.name] = param else fail Puppet::ParseError, "Only subclasses can set parameters" end end end - # Store our object as a Rails object. We need the host object we're storing it - # with. - def store(host) + #def tags + # unless defined? @tags + # @tags = scope.tags + # @tags << self.type + # end + # @tags + #end + + def to_hash + @params.inject({}) do |hash, ary| + param = ary[1] + hash[param.name] = param.value + hash + end + end + + # Turn our parser resource into a Rails resource. + def to_rails(host) args = {} #FIXME: support files/lines, etc. #%w{type title tags file line exported}.each do |param| %w{type title exported}.each do |param| if value = self.send(param) args[param] = value end end # 'type' isn't a valid column name, so we have to use something else. args = symbolize_options(args) args[:restype] = args[:type] args.delete(:type) # Let's see if the object exists if obj = host.resources.find_by_restype_and_title(self.type, self.title) # We exist args.each do |param, value| obj[param] = value end else # Else create it anew obj = host.resources.build(args) end + if l = self.line + obj.line = l + end + # Either way, now add our parameters + exists = {} + obj.param_names.each do |pn| exists[pn.name] = pn end @params.each do |name, param| - param.store(obj) + param.to_rails(obj) + exists.delete(name.to_s) if exists.include?(name.to_s) end - return obj - end - - #def tags - # unless defined? @tags - # @tags = scope.tags - # @tags << self.type - # end - # @tags - #end - - def to_hash - @params.inject({}) do |hash, ary| - param = ary[1] - hash[param.name] = param.value - hash + unless exists.empty? + obj.save + exists.each do |name, param| + obj.param_names.delete(param) + end end + + return obj end def to_s self.ref end # Translate our object to a transportable object. def to_trans unless builtin? devfail "Tried to translate a non-builtin resource" end return nil if virtual? # Now convert to a transobject obj = Puppet::TransObject.new(@ref.title, @ref.type) to_hash.each do |p, v| if v.is_a?(Reference) v = v.to_ref elsif v.is_a?(Array) v = v.collect { |av| if av.is_a?(Reference) av = av.to_ref end av } end obj[p.to_s] = v end obj.file = self.file obj.line = self.line #obj.tags = self.tags return obj end def virtual? self.virtual end end # $Id$ diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb index 2e5d78034..6f24f1486 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -1,42 +1,61 @@ # The parameters we stick in Resources. class Puppet::Parser::Resource::Param attr_accessor :name, :value, :source, :line, :file include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper def initialize(hash) set_options(hash) requiredopts(:name, :value, :source) @name = symbolize(@name) end def inspect "#<#{self.class} @name => #{self.name}, @value => #{self.value}, @source => #{self.source.type}>" end # Store this parameter in a Rails db. - def store(resource) - args = {} - #[:name, :value, :line, :file].each do |var| - [:name, :value].each do |var| - args[var] = self.send(var) + def to_rails(res) + values = value.is_a?(Array) ? value : [value] + + unless pn = res.param_names.find_by_name(self.name.to_s) + # We're creating it anew. + pn = res.param_names.build(:name => self.name.to_s) + end + + if l = self.line + pn.line = Integer(l) + end + + exists = {} + pn.param_values.each { |pv| exists[pv.value] = pv } + values.each do |value| + unless pn.param_values.find_by_value(value) + pn.param_values.build(:value => value) + end + # Mark that this is still valid. + if exists.include?(value) + exists.delete(value) + end end - args[:name] = args[:name].to_s - args[:name].each do |name| - pn = resource.param_names.find_or_create_by_name(name) - args[:value].each do |value| - pv = pn.param_values.find_or_create_by_value(value) + + # And remove any existing values that are not in the current value list. + unless exists.empty? + # We have to save the current state else the deletion somehow deletes + # our new values. + pn.save + exists.each do |value, obj| + pn.param_values.delete(obj) end end - obj = resource.param_names.find_by_name(args[:name], :include => :param_values) - return obj + return pn end def to_s "%s => %s" % [self.name, self.value] end end # $Id$ diff --git a/lib/puppet/rails.rb b/lib/puppet/rails.rb index b3795f4e0..925593434 100644 --- a/lib/puppet/rails.rb +++ b/lib/puppet/rails.rb @@ -1,150 +1,145 @@ # Load the appropriate libraries, or set a class indicating they aren't available require 'facter' require 'puppet' module Puppet::Rails Puppet.config.setdefaults(:puppetmaster, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :mode => 0600, :owner => "$user", :group => "$group", :desc => "The database cache for client configurations. Used for querying within the language." }, :dbadapter => [ "sqlite3", "The type of database to use." ], :dbmigrate => [ false, "Whether to automatically migrate the database." ], :dbname => [ "puppet", "The name of the database to use." ], :dbserver => [ "localhost", "The database server for Client caching. Only used when networked databases are used."], :dbuser => [ "puppet", "The database user for Client caching. Only used when networked databases are used."], :dbpassword => [ "puppet", "The database password for Client caching. Only used when networked databases are used."], - :railslog => {:default => "/tmp/puppetpuppetrails.log", + :railslog => {:default => "$logdir/rails.log", :mode => 0600, :owner => "$user", :group => "$group", :desc => "Where Rails-specific logs are sent" } ) def self.clear end - def self.teardown - unless defined? ActiveRecord::Base - raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" - end - Puppet.config.use(:puppetmaster) + # The arguments for initializing the database connection. + def self.database_arguments + args = {:adapter => Puppet[:dbadapter]} - args = {:adapter => Puppet[:dbadapter]} case Puppet[:dbadapter] - when "sqlite3": - args[:dbfile] = Puppet[:dblocation] - - when "mysql": - args[:host] = Puppet[:dbserver] - args[:username] = Puppet[:dbuser] - args[:password] = Puppet[:dbpassword] - args[:database] = Puppet[:dbname] - end - - begin - ActiveRecord::Base.establish_connection(args) - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - raise Puppet::Error, "Could not connect to database: %s" % detail - end - - ActiveRecord::Base.connection.tables.each do |t| - ActiveRecord::Base.connection.drop_table t + when "sqlite3": + args[:dbfile] = Puppet[:dblocation] + when "mysql": + args[:host] = Puppet[:dbserver] + args[:username] = Puppet[:dbuser] + args[:password] = Puppet[:dbpassword] + args[:database] = Puppet[:dbname] end - - @inited = false + args end # Set up our database connection. It'd be nice to have a "use" system # that could make callbacks. def self.init unless Puppet.features.rails? raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" end # This global init does not work for testing, because we remove # the state dir on every test. #unless (defined? @inited and @inited) or defined? Test::Unit::TestCase - unless (defined? @inited and @inited) + unless ActiveRecord::Base.connected? Puppet.config.use(:puppet) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) - args = {:adapter => Puppet[:dbadapter]} - - case Puppet[:dbadapter] - when "sqlite3": - args[:database] = Puppet[:dblocation] - #unless FileTest.exists?(Puppet[:dblocation]) - # Puppet.config.use(:puppet) - # Puppet.config.write(:dblocation) do |f| - # f.print "" - # end - #end - - when "mysql": - args[:host] = Puppet[:dbserver] - args[:username] = Puppet[:dbuser] - args[:password] = Puppet[:dbpassword] - args[:database] = Puppet[:dbname] - end begin - ActiveRecord::Base.establish_connection(args) + ActiveRecord::Base.establish_connection(database_arguments()) rescue => detail if Puppet[:trace] puts detail.backtrace end raise Puppet::Error, "Could not connect to database: %s" % detail end - unless ActiveRecord::Base.connection.tables.include?("resources") - require 'puppet/rails/database/schema' - Puppet::Rails::Schema.init - end - @inited = true end - ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) + + unless ActiveRecord::Base.connection.tables.include?("resources") + require 'puppet/rails/database/schema' + Puppet::Rails::Schema.init + end if Puppet[:dbmigrate] - dbdir = nil - $:.each { |d| - tmp = File.join(d, "puppet/rails/database") - if FileTest.directory?(tmp) - dbdir = tmp - break - end - } + migrate() + end + + # For now, we have to use :puppet, too, since non-puppetmasterd processes + # (including testing) put the logdir in :puppet, not in :puppetmasterd. + Puppet.config.use(:puppetmaster, :puppet) - unless dbdir - raise Puppet::Error, "Could not find Puppet::Rails database dir" + # This has to come after we create the logdir with the :use above. + ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) + end + + # Migrate to the latest db schema. + def self.migrate + dbdir = nil + $:.each { |d| + tmp = File.join(d, "puppet/rails/database") + if FileTest.directory?(tmp) + dbdir = tmp + break end + } - begin - ActiveRecord::Migrator.migrate(dbdir) - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - raise Puppet::Error, "Could not initialize database: %s" % detail + unless dbdir + raise Puppet::Error, "Could not find Puppet::Rails database dir" + end + + begin + ActiveRecord::Migrator.migrate(dbdir) + rescue => detail + if Puppet[:trace] + puts detail.backtrace end + raise Puppet::Error, "Could not migrate database: %s" % detail + end + end + + # Tear down the database. Mostly only used during testing. + def self.teardown + unless Puppet.features.rails? + raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" end + Puppet.config.use(:puppetmaster) - ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) + + begin + ActiveRecord::Base.establish_connection(database_arguments()) + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + raise Puppet::Error, "Could not connect to database: %s" % detail + end + + ActiveRecord::Base.connection.tables.each do |t| + ActiveRecord::Base.connection.drop_table t + end end end if Puppet.features.rails? require 'puppet/rails/host' end # $Id$ diff --git a/lib/puppet/rails/database/schema.rb b/lib/puppet/rails/database/schema.rb index 546ad73fb..322e6f839 100644 --- a/lib/puppet/rails/database/schema.rb +++ b/lib/puppet/rails/database/schema.rb @@ -1,73 +1,78 @@ class Puppet::Rails::Schema def self.init oldout = nil - Puppet::Util.benchmark(:notice, "Initialized database") do + Puppet::Util.benchmark(Puppet, :notice, "Initialized database") do # We want to rewrite stdout, so we don't get migration messages. oldout = $stdout $stdout = File.open("/dev/null", "w") ActiveRecord::Schema.define do create_table :resources do |t| t.column :title, :string, :null => false - t.column :restype, :string + t.column :restype, :string, :null => false t.column :host_id, :integer t.column :source_file_id, :integer t.column :exported, :boolean + t.column :line, :integer end create_table :source_files do |t| t.column :filename, :string t.column :path, :string end create_table :puppet_classes do |t| t.column :name, :string t.column :host_id, :integer t.column :source_file_id, :integer end create_table :hosts do |t| t.column :name, :string, :null => false t.column :ip, :string t.column :connect, :date #Use updated_at to automatically add timestamp on save. t.column :updated_at, :date t.column :source_file_id, :integer end create_table :fact_names do |t| t.column :name, :string, :null => false t.column :host_id, :integer, :null => false end create_table :fact_values do |t| t.column :value, :string, :null => false t.column :fact_name_id, :integer, :null => false end create_table :param_values do |t| t.column :value, :string, :null => false t.column :param_name_id, :integer, :null => false end create_table :param_names do |t| t.column :name, :string, :null => false t.column :resource_id, :integer + t.column :line, :integer end create_table :tags do |t| t.column :name, :string end create_table :taggings do |t| t.column :tag_id, :integer t.column :taggable_id, :integer t.column :taggable_type, :string end end + $stdout.close + $stdout = oldout + oldout = nil end ensure - $stdout = oldout + $stdout = oldout if oldout end end # $Id$ diff --git a/lib/puppet/rails/fact_name.rb b/lib/puppet/rails/fact_name.rb index 886618ecb..85c951f87 100644 --- a/lib/puppet/rails/fact_name.rb +++ b/lib/puppet/rails/fact_name.rb @@ -1,3 +1,3 @@ class Puppet::Rails::FactName < ActiveRecord::Base - has_many :fact_values + has_many :fact_values, :dependent => :destroy end diff --git a/lib/puppet/rails/fact_value.rb b/lib/puppet/rails/fact_value.rb index 4da74b713..09be5d265 100644 --- a/lib/puppet/rails/fact_value.rb +++ b/lib/puppet/rails/fact_value.rb @@ -1,3 +1,3 @@ class Puppet::Rails::FactValue < ActiveRecord::Base - belongs_to :fact_names + belongs_to :fact_name end diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index fd0642722..a46fa92a5 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -1,127 +1,92 @@ require 'puppet/rails/resource' class Puppet::Rails::Host < ActiveRecord::Base has_many :fact_values, :through => :fact_names - has_many :fact_names + has_many :fact_names, :dependent => :destroy belongs_to :puppet_classes has_many :source_files - has_many :resources, :include => [ :param_names, :param_values ] + has_many :resources, + :include => [ :param_names, :param_values ], + :dependent => :destroy acts_as_taggable def facts(name) if fv = self.fact_values.find(:first, :conditions => "fact_names.name = '#{name}'") return fv.value else return nil end end # If the host already exists, get rid of its objects def self.clean(host) if obj = self.find_by_name(host) obj.rails_objects.clear return obj else return nil end end # Store our host in the database. def self.store(hash) unless hash[:name] raise ArgumentError, "You must specify the hostname for storage" end - create = true - args = {} if hash[:facts].include?("ipaddress") args[:ip] = hash[:facts]["ipaddress"] end - host = nil - Puppet::Util.benchmark(:info, "Found/created host") do - host = self.find_or_create_by_name(hash[:facts]["hostname"], args) + unless host = find_by_name(hash[:facts]["hostname"]) + host = new(:name => hash[:facts]["hostname"]) end - Puppet::Util.benchmark(:info, "Converted facts") do - hash[:facts].each do |name, value| - if create - fn = host.fact_names.find_or_create_by_name(name) - fv = fn.fact_values.find_or_create_by_value(value) - else - fn = host.fact_names.find_by_name(name) || host.fact_names.new(:name => name) - unless fv = fn.fact_values.find_by_value(value) - fn.fact_values << fn.fact_values.new(:value => value) - end - end - host.fact_names << fn + # Store the facts into the + hash[:facts].each do |name, value| + fn = host.fact_names.find_by_name(name) || host.fact_names.build(:name => name) + unless fn.fact_values.find_by_value(value) + fn.fact_values.build(:value => value) end end unless hash[:resources] raise ArgumentError, "You must pass resources" end - Puppet::Util.benchmark(:info, "Converted resources") do - hash[:resources].each do |resource| - resargs = resource.to_hash.stringify_keys - - if create - res = host.resources.find_or_create_by_restype_and_title(resource[:type], resource[:title]) - else - unless res = host.resources.find_by_restype_and_title(resource[:type], resource[:title]) - res = host.resources.new(:restype => resource[:type], :title => resource[:title]) - host.resources << res - end - end - - resargs.each do |param, value| - if create - pn = res.param_names.find_or_create_by_name(param) - pv = pn.param_values.find_or_create_by_value(value) - else - unless pn = res.param_names.find_by_name(param) - pn = res.param_names.new(:name => param) - end - unless pn.param_values.find_by_value(value) - pn.param_values << pn.param_values.new(:value => value) - end - end - res.param_names << pn - end - end + resources = [] + hash[:resources].each do |resource| + resources << resource.to_rails(host) end - Puppet::Util.benchmark(:info, "Saved host to database") do - host.save - end + host.save return host end # Add all of our RailsObjects def addobjects(objects) objects.each do |tobj| params = {} tobj.each do |p,v| params[p] = v end args = {:ptype => tobj.type, :name => tobj.name} [:tags, :file, :line].each do |param| if val = tobj.send(param) args[param] = val end end robj = rails_objects.build(args) robj.addparams(params) if tobj.collectable robj.toggle(:collectable) end end end end # $Id$ diff --git a/lib/puppet/rails/param_name.rb b/lib/puppet/rails/param_name.rb index 928838f5c..dba6960da 100644 --- a/lib/puppet/rails/param_name.rb +++ b/lib/puppet/rails/param_name.rb @@ -1,13 +1,17 @@ class Puppet::Rails::ParamName < ActiveRecord::Base - has_many :param_values - belongs_to :resources + has_many :param_values, :dependent => :destroy + belongs_to :resource def to_resourceparam(source) hash = {} hash[:name] = self.name.to_sym hash[:source] = source - hash[:value] = self.param_values.find(:first).value + hash[:value] = self.param_values.find(:all).collect { |v| v.value } + if hash[:value].empty? + hash[:value] = nil + end Puppet::Parser::Resource::Param.new hash end end +# $Id$ diff --git a/lib/puppet/rails/param_value.rb b/lib/puppet/rails/param_value.rb index b01add4a7..d988559af 100644 --- a/lib/puppet/rails/param_value.rb +++ b/lib/puppet/rails/param_value.rb @@ -1,5 +1,5 @@ class Puppet::Rails::ParamValue < ActiveRecord::Base - belongs_to :param_names - + belongs_to :param_name end +# $Id$ diff --git a/lib/puppet/rails/resource.rb b/lib/puppet/rails/resource.rb index 423b227ad..c0a7fbd8c 100644 --- a/lib/puppet/rails/resource.rb +++ b/lib/puppet/rails/resource.rb @@ -1,44 +1,69 @@ require 'puppet' require 'puppet/rails/lib/init' require 'puppet/rails/param_name' class Puppet::Rails::Resource < ActiveRecord::Base has_many :param_values, :through => :param_names - has_many :param_names + has_many :param_names, :dependent => :destroy has_many :source_files - belongs_to :hosts + belongs_to :host acts_as_taggable + def [](param) + return super || parameter(param) + end + + def parameter(param) + if pn = param_names.find_by_name(param) + if pv = pn.param_values.find(:first) + return pv.value + else + return nil + end + end + end + def parameters hash = {} self.param_values.find(:all).each do |pvalue| - pname = self.param_names.find(:first) - hash.store(pname.name, pvalue.value) + pname = pvalue.param_name.name + hash.store(pname, pvalue.value) end return hash end + def ref + "%s[%s]" % [self[:restype], self[:title]] + end + # Convert our object to a resource. Do not retain whether the object - # is collectable, though, since that would cause it to get stripped + # is exported, though, since that would cause it to get stripped # from the configuration. def to_resource(scope) hash = self.attributes hash["type"] = hash["restype"] hash.delete("restype") + + # FIXME At some point, we're going to want to retain this information + # for logging and auditing. hash.delete("host_id") + hash.delete("source_file_id") hash.delete("id") hash.each do |p, v| hash.delete(p) if v.nil? end hash[:scope] = scope hash[:source] = scope.source obj = Puppet::Parser::Resource.new(hash) self.param_names.each do |pname| obj.set(pname.to_resourceparam(scope.source)) end + # Store the ID, so we can check if we're re-collecting the same resource. + obj.rails_id = self.id + return obj end end diff --git a/test/language/collector.rb b/test/language/collector.rb index 0292fac3e..41ce12f66 100755 --- a/test/language/collector.rb +++ b/test/language/collector.rb @@ -1,302 +1,308 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet/rails' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestCollector < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting Parser = Puppet::Parser AST = Parser::AST def setup super Puppet[:trace] = false @interp, @scope, @source = mkclassframing end # Test just collecting a specific resource. This is used by the 'realize' # function, and it's much faster than iterating over all of the resources. def test_collect_resource # Make a couple of virtual resources one = mkresource(:type => "file", :title => "/tmp/virtual1", :virtual => true, :params => {:owner => "root"}) two = mkresource(:type => "file", :title => "/tmp/virtual2", :virtual => true, :params => {:owner => "root"}) @scope.setresource one @scope.setresource two # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :virtual) end # Now set the resource in the collector assert_nothing_raised do coll.resources = one.ref end # Now run the collector assert_nothing_raised do coll.evaluate end # And make sure the resource is no longer virtual assert(! one.virtual?, "Resource is still virtual") # But the other still is assert(two.virtual?, "Resource got realized") end def test_virtual # Make a virtual resource virtual = mkresource(:type => "file", :title => "/tmp/virtual", :virtual => true, :params => {:owner => "root"}) @scope.setresource virtual # And a non-virtual real = mkresource(:type => "file", :title => "/tmp/real", :params => {:owner => "root"}) @scope.setresource real # Now make a collector coll = nil # Make a fake query code = proc do |res| true end assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, code, :virtual) end # Set it in our scope @scope.newcollection(coll) # Make sure it's in the collections assert_equal([coll], @scope.collections) # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_virtual end assert_equal([virtual], ret) # Now make sure evaluate does the right thing. assert_nothing_raised do ret = coll.evaluate end # Make sure it got deleted from the collection list assert_equal([], @scope.collections) # And make sure our virtual object is no longer virtual assert(! virtual.virtual?, "Virtual object did not get realized") # Now make a new collector of a different type and make sure it # finds nothing. assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "exec", nil, nil, :virtual) end # Remark this as virtual virtual.virtual = true assert_nothing_raised do ret = coll.evaluate end assert_equal([], ret) end - if defined? ActiveRecord::Base + if Puppet.features.rails? def test_collect_exported railsinit + + # Set a hostname + @scope.host = Facter.value(:hostname) + # make an exported resource exported = mkresource(:type => "file", :title => "/tmp/exported", :exported => true, :params => {:owner => "root"}) @scope.setresource exported assert(exported.exported?, "Object was not marked exported") assert(exported.virtual?, "Object was not marked virtual") # And a non-exported real = mkresource(:type => "file", :title => "/tmp/real", :params => {:owner => "root"}) @scope.setresource real # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :exported) end # Set it in our scope @scope.newcollection(coll) # Make sure it's in the collections assert_equal([coll], @scope.collections) # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_exported end assert_equal([exported], ret) # Now make sure evaluate does the right thing. assert_nothing_raised do ret = coll.evaluate end # Make sure it got deleted from the collection list assert_equal([], @scope.collections) # And make sure our exported object is no longer exported assert(! exported.virtual?, "Virtual object did not get realized") # But it should still be marked exported. assert(exported.exported?, "Resource got un-exported") # Now make a new collector of a different type and make sure it # finds nothing. assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "exec", nil, nil, :exported) end # Remark this as virtual exported.virtual = true assert_nothing_raised do ret = coll.evaluate end assert_equal([], ret) # Now create a whole new scope and make sure we can actually retrieve # the resource from the database, not just from the scope. # First create a host object and store our resource in it. - # Now collect our facts - facts = {} - Facter.each do |fact, value| facts[fact] = value end - - - # Now try storing our crap - resources = [] - resources << exported - host = Puppet::Rails::Host.store( - :resources => resources, - :facts => facts, - :name => facts["hostname"] - ) + + # Now collect our facts + facts = {} + Facter.each do |fact, value| facts[fact] = value end + + # Now try storing our crap + # Remark this as exported + exported.exported = true + host = Puppet::Rails::Host.store( + :resources => [exported], + :facts => facts, + :name => facts["hostname"] + ) assert(host, "did not get rails host") host.save # And make sure it's in there - newres = host.resources.find_by_title("/tmp/exported") + newres = host.resources.find_by_restype_and_title_and_exported("file", "/tmp/exported", true) assert(newres, "Did not find resource in db") interp, scope, source = mkclassframing + scope.host = "two" # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :exported) end # Set it in our scope scope.newcollection(coll) # Make sure it's in the collections assert_equal([coll], scope.collections) # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_exported end assert_equal(["/tmp/exported"], ret.collect { |f| f.title }) # Make sure we can evaluate the same collection multiple times and # that later collections do nothing assert_nothing_raised do ret = coll.evaluate end # Make sure it got deleted from the collection list assert_equal([], scope.collections) end def test_collection_conflicts railsinit # First make a railshost we can conflict with host = Puppet::Rails::Host.new(:name => "myhost") - host.resources.build(:title => "/tmp/conflicttest", :type => "PuppetFile", + host.resources.build(:title => "/tmp/conflicttest", :restype => "file", :exported => true) host.save # Now make a normal resource normal = mkresource(:type => "file", :title => "/tmp/conflicttest", :params => {:owner => "root"}) @scope.setresource normal + @scope.host = "otherhost" # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :exported) end # And try to collect the virtual resources. assert_raise(Puppet::ParseError) do ret = coll.collect_exported end end # Make sure we do not collect resources from the host we're on def test_no_resources_from_me railsinit # Make our configuration host = Puppet::Rails::Host.new(:name => "myhost") host.resources.build(:title => "/tmp/hosttest", :type => "PuppetFile", :exported => true) host.save @scope.host = "myhost" # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :exported) end # And make sure we get nada back ret = nil assert_nothing_raised do ret = coll.collect_exported end assert(ret.empty?, "Found exports from our own host") end end end # $Id$ diff --git a/test/language/resource.rb b/test/language/resource.rb index eb8d2e9aa..872d7b39f 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -1,403 +1,450 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestResource < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting Parser = Puppet::Parser AST = Parser::AST def setup super Puppet[:trace] = false @interp, @scope, @source = mkclassframing end def test_initialize args = {:type => "resource", :title => "testing", :source => @source, :scope => @scope} # Check our arg requirements args.each do |name, value| try = args.dup try.delete(name) assert_raise(Puppet::DevError) do Parser::Resource.new(try) end end args[:params] = paramify @source, :one => "yay", :three => "rah" res = nil assert_nothing_raised do res = Parser::Resource.new(args) end # Make sure it got the parameters correctly. assert_equal("yay", res[:one]) assert_equal("rah", res[:three]) assert_equal({:one => "yay", :three => "rah"}, res.to_hash) end def test_override res = mkresource # Now verify we can't override with any random class assert_raise(Puppet::ParseError) do res.set paramify(@scope.findclass("other"), "one" => "boo").shift end # And that we can with a subclass assert_nothing_raised do res.set paramify(@scope.findclass("sub1"), "one" => "boo").shift end # And that a different subclass can override a different parameter assert_nothing_raised do res.set paramify(@scope.findclass("sub2"), "three" => "boo").shift end # But not the same one assert_raise(Puppet::ParseError) do res.set paramify(@scope.findclass("sub2"), "one" => "something").shift end end def test_merge # Start with the normal one res = mkresource # Now create a resource from a different scope other = mkresource :source => other, :params => {"one" => "boo"} # Make sure we can't merge it assert_raise(Puppet::ParseError) do res.merge(other) end # Make one from a subscope other = mkresource :source => "sub1", :params => {"one" => "boo"} # Make sure it merges assert_nothing_raised do res.merge(other) end assert_equal("boo", res["one"]) end def test_paramcheck # First make a builtin resource res = nil assert_nothing_raised do res = Parser::Resource.new :type => "file", :title => tempfile(), :source => @source, :scope => @scope end %w{path group source schedule subscribe}.each do |param| assert_nothing_raised("Param %s was considered invalid" % param) do res.paramcheck(param) end end %w{this bad noness}.each do |param| assert_raise(Puppet::ParseError, "%s was considered valid" % param) do res.paramcheck(param) end end # Now create a defined resource assert_nothing_raised do res = Parser::Resource.new :type => "resource", :title => "yay", :source => @source, :scope => @scope end %w{one two three schedule subscribe}.each do |param| assert_nothing_raised("Param %s was considered invalid" % param) do res.paramcheck(param) end end %w{this bad noness}.each do |param| assert_raise(Puppet::ParseError, "%s was considered valid" % param) do res.paramcheck(param) end end end def test_to_trans # First try translating a builtin resource res = Parser::Resource.new :type => "file", :title => "/tmp", :source => @source, :scope => @scope, :params => paramify(@source, :owner => "nobody", :mode => "644") obj = nil assert_nothing_raised do obj = res.to_trans end assert_instance_of(Puppet::TransObject, obj) assert_equal(obj.type, res.type) assert_equal(obj.name, res.title) # TransObjects use strings, resources use symbols hash = obj.to_hash.inject({}) { |h,a| h[a[0].intern] = a[1]; h } assert_equal(hash, res.to_hash) end def test_adddefaults # Set some defaults at the top level top = {:one => "fun", :two => "shoe"} @scope.setdefaults("resource", paramify(@source, top)) # Make a resource at that level res = Parser::Resource.new :type => "resource", :title => "yay", :source => @source, :scope => @scope # Add the defaults assert_nothing_raised do res.adddefaults end # And make sure we got them top.each do |p, v| assert_equal(v, res[p]) end # Now got a bit lower other = @scope.newscope # And create a resource lowerres = Parser::Resource.new :type => "resource", :title => "funtest", :source => @source, :scope => other assert_nothing_raised do lowerres.adddefaults end # And check top.each do |p, v| assert_equal(v, lowerres[p]) end # Now add some of our own defaults lower = {:one => "shun", :three => "free"} other.setdefaults("resource", paramify(@source, lower)) otherres = Parser::Resource.new :type => "resource", :title => "yaytest", :source => @source, :scope => other should = top.dup # Make sure the lower defaults beat the higher ones. lower.each do |p, v| should[p] = v end otherres.adddefaults should.each do |p,v| assert_equal(v, otherres[p]) end end def test_evaluate # Make a definition that we know will, um, do something @interp.newdefine "evaltest", :arguments => [%w{one}, ["two", stringobj("755")]], :code => resourcedef("file", "/tmp", "owner" => varref("one"), "mode" => varref("two")) res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => @source, :scope => @scope, :params => paramify(@source, :one => "nobody") # Now try evaluating ret = nil assert_nothing_raised do ret = res.evaluate end # Make sure we can find our object now result = @scope.findresource("file[/tmp]") # Now make sure we got the code we expected. assert_instance_of(Puppet::Parser::Resource, result) assert_equal("file", result.type) assert_equal("/tmp", result.title) assert_equal("nobody", result["owner"]) assert_equal("755", result["mode"]) # And that we cannot find the old resource assert_nil(@scope.findresource("evaltest[yay]"), "Evaluated resource was not deleted") end def test_addoverrides # First create an override for an object that doesn't yet exist over1 = mkresource :source => "sub1", :params => {:one => "yay"} assert_nothing_raised do @scope.setoverride(over1) end assert(over1.override, "Override was not marked so") # Now make the resource res = mkresource :source => "base", :params => {:one => "rah", :three => "foo"} # And add it to our scope @scope.setresource(res) # And make sure over1 has not yet taken affect assert_equal("foo", res[:three], "Lost value") # Now add an immediately binding override over2 = mkresource :source => "sub1", :params => {:three => "yay"} assert_nothing_raised do @scope.setoverride(over2) end # And make sure it worked assert_equal("yay", res[:three], "Override 2 was ignored") # Now add our late-binding override assert_nothing_raised do res.addoverrides end # And make sure they're still around assert_equal("yay", res[:one], "Override 1 lost") assert_equal("yay", res[:three], "Override 2 lost") # And finally, make sure that there are no remaining overrides assert_nothing_raised do res.addoverrides end end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => @source, :scope => @scope assert_equal("evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) end def test_addmetaparams mkevaltest @interp res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => @source, :scope => @scope, :params => paramify(@source, :tag => "yay") assert_nil(res[:schedule], "Got schedule already") assert_nothing_raised do res.addmetaparams end @scope.setvar("schedule", "daily") # This is so we can test that it won't override already-set metaparams @scope.setvar("tag", "funtest") assert_nothing_raised do res.addmetaparams end assert_equal("daily", res[:schedule], "Did not get metaparam") assert_equal("yay", res[:tag], "Overrode explicitly-set metaparam") assert_nil(res[:noop], "Got invalid metaparam") end def test_reference_conversion # First try it as a normal string ref = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref1") # Now create an obj that uses it res = mkresource :type => "file", :title => "/tmp/resource", :params => {:require => ref} trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"]) assert_equal(["file", "/tmp/ref1"], trans["require"]) # Now try it when using an array of references. two = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref2") res = mkresource :type => "file", :title => "/tmp/resource2", :params => {:require => [ref, two]} trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"][0]) trans["require"].each do |val| assert_instance_of(Array, val) assert_equal("file", val[0]) assert(val[1] =~ /\/tmp\/ref[0-9]/, "Was %s instead of the file name" % val[1]) end end # This is a bit of a weird one -- the user should not actually know # that components exist, so we want references to act like they're not # builtin def test_components_are_not_builtin ref = Parser::Resource::Reference.new(:type => "component", :title => "yay") assert_nil(ref.builtintype, "Component was considered builtin") end - if defined? ActiveRecord::Base - def test_store - railsinit - res = mkresource :type => "file", :title => "/tmp/testing", - :source => @source, :scope => @scope, - :params => {:owner => "root", :mode => "755"} - - # We also need a Rails Host to store under - host = Puppet::Rails::Host.new(:name => Facter.hostname) + if Puppet.features.rails? + # Compare a parser resource to a rails resource. + def compare_resources(host, res) obj = nil assert_nothing_raised do - obj = res.store(host) + obj = res.to_rails(host) end assert_instance_of(Puppet::Rails::Resource, obj) assert_nothing_raised do Puppet::Util.benchmark(:info, "Saved host") do host.save end end + # Make sure we find our object and only our object + count = 0 + Puppet::Rails::Resource.find(:all).each do |obj| + count += 1 + [:title, :restype, :line, :exported].each do |param| + if param == :restype + method = :type + else + method = param + end + assert_equal(res.send(method), obj[param], + "attribute %s is incorrect" % param) + end + end + assert_equal(1, count, "Got too many resources") # Now make sure we can find it again assert_nothing_raised do - obj = Puppet::Rails::Resource.find_by_host_id_and_title( - host.id, res.title + obj = Puppet::Rails::Resource.find_by_restype_and_title( + res.type, res.title ) end assert_instance_of(Puppet::Rails::Resource, obj) # Make sure we get the parameters back - obj.parameters.each do |param| - assert_equal(res[param[:name]], param[:value], - "%s was different" % param[:name]) + params = [obj.param_names.collect { |p| p.name }, + res.to_hash.keys].flatten.collect { |n| n.to_s }.uniq + + params.each do |name| + param = obj.param_names.find_by_name(name) + if res[name] + assert(param, "resource did not keep %s" % name) + else + assert(! param, "resource did not delete %s" % name) + end + values = param.param_values.collect { |pv| pv.value } + should = res[param.name] + should = [should] unless should.is_a?(Array) + assert_equal(should, values, + "%s was different" % param.name) end end + + def test_to_rails + railsinit + res = mkresource :type => "file", :title => "/tmp/testing", + :source => @source, :scope => @scope, + :params => {:owner => "root", :source => ["/tmp/A", "/tmp/B"], + :mode => "755"} + + res.line = 50 + + # We also need a Rails Host to store under + host = Puppet::Rails::Host.new(:name => Facter.hostname) + + compare_resources(host, res) + + # Now make some changes to our resource. + res = mkresource :type => "file", :title => "/tmp/testing", + :source => @source, :scope => @scope, + :params => {:owner => "bin", :source => ["/tmp/A", "/tmp/C"], + :check => "checksum"} + + res.line = 75 + res.exported = true + + compare_resources(host, res) + end end end # $Id$ diff --git a/test/lib/puppettest/railstesting.rb b/test/lib/puppettest/railstesting.rb index 1d2d94863..e2ce7ef3f 100644 --- a/test/lib/puppettest/railstesting.rb +++ b/test/lib/puppettest/railstesting.rb @@ -1,46 +1,49 @@ module PuppetTest::RailsTesting Parser = Puppet::Parser AST = Puppet::Parser::AST include PuppetTest::ParserTesting def railsinit Puppet::Rails.init + cleanup do + ActiveRecord::Base.clear_active_connections! + end end def railsteardown if Puppet[:dbadapter] != "sqlite3" Puppet::Rails.teardown end end def railsresource(type = "file", title = "/tmp/testing", params = {}) railsteardown railsinit # We need a host for resources #host = Puppet::Rails::Host.new(:name => Facter.value("hostname")) # Now build a resource resources = [] resources << mkresource(:restype => type, :title => title, :exported => true, :params => params) # Now collect our facts facts = Facter.to_hash # Now try storing our crap host = nil assert_nothing_raised { host = Puppet::Rails::Host.store( :resources => resources, :facts => facts, :name => facts["hostname"] ) } # Now save the whole thing host.save end end # $Id$ diff --git a/test/rails/rails.rb b/test/rails/rails.rb index 7ffeaccac..69e1fd7b8 100755 --- a/test/rails/rails.rb +++ b/test/rails/rails.rb @@ -1,105 +1,121 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/rails' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestRails < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting - def test_includerails - assert_nothing_raised { - require 'puppet/rails' - } - end - - # Don't do any tests w/out this class - if Puppet.features.rails? def setup super railsinit end def teardown - super railsteardown + super end + def test_includerails + assert_nothing_raised { + require 'puppet/rails' + } + end + + # Don't do any tests w/out this class + if Puppet.features.rails? def test_hostcache - railsinit @interp, @scope, @source = mkclassframing # First make some objects resources = [] 10.times { |i| # Make a file resources << mkresource(:type => "file", :title => "/tmp/file#{i.to_s}", :params => {:owner => "user#{i}"}) # And an exec, so we're checking multiple types resources << mkresource(:type => "exec", :title => "/bin/echo file#{i.to_s}", - :params => {}) + :params => {:user => "user#{i}"}) } # Now collect our facts - facts = Facter.to_hash + facts = {"hostname" => Facter.value(:hostname), "test1" => "funtest"} # Now try storing our crap host = nil assert_nothing_raised { host = Puppet::Rails::Host.store( :resources => resources, :facts => facts, :name => facts["hostname"], :classes => ["one", "two::three", "four"] ) } assert(host, "Did not create host") host = nil assert_nothing_raised { host = Puppet::Rails::Host.find_by_name(facts["hostname"]) } assert(host, "Could not find host object") assert(host.resources, "No objects on host") assert_equal(facts["hostname"], host.facts("hostname"), "Did not retrieve facts") count = 0 host.resources.each do |resource| assert_equal(host, resource.host) count += 1 i = nil if resource[:title] =~ /file([0-9]+)/ i = $1 else raise "Got weird resource %s" % resource.inspect end - assert(resource[:type] != "", "Did not get a type from the resource") - if resource[:type] != "PuppetExec" - assert_equal("user#{i}", resource.parameters["owner"]) + assert(resource[:restype] != "", "Did not get a type from the resource") + case resource["restype"] + when "file": + assert_equal("user#{i}", resource.parameter("owner"), + "got no owner for %s" % resource.ref) + when "exec": + assert_equal("user#{i}", resource.parameter("user"), + "got no user for %s" % resource.ref) + else + raise "Unknown type %s" % resource[:restype].inspect end end assert_equal(20, count, "Did not get enough resources") + + host = nil + assert_nothing_raised { + host = Puppet::Rails::Host.store( + :resources => resources, + :facts => facts, + :name => facts["hostname"], + :classes => ["one", "two::three", "four"] + ) + } end else $stderr.puts "Install Rails for Rails and Caching tests" end end # $Id$ diff --git a/test/rails/railsresource.rb b/test/rails/railsresource.rb index 36df881ca..302dd99fb 100755 --- a/test/rails/railsresource.rb +++ b/test/rails/railsresource.rb @@ -1,68 +1,100 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/rails' require 'puppettest' require 'puppettest/railstesting' require 'puppettest/resourcetesting' # Don't do any tests w/out this class -if defined? ActiveRecord::Base +if Puppet.features.rails? class TestRailsResource < Test::Unit::TestCase include PuppetTest::RailsTesting include PuppetTest::ResourceTesting - - # Create a resource param from a rails parameter - def test_to_resource + + def setup + super railsinit - + end + + def teardown + railsteardown + super + end + + def mktest_resource # We need a host for resources host = Puppet::Rails::Host.new(:name => "myhost") # Now build a resource resource = host.resources.create( :title => "/tmp/to_resource", :restype => "file", :exported => true) - - # For some reason the child class doesn't exist until after the resource is created. - # Probably an issue with the dynamic class generation. - resource.save - + # Now add some params - {"owner" => "root", "mode" => "644"}.each do |param, value| + params.each do |param, value| pn = resource.param_names.find_or_create_by_name(param) pv = pn.param_values.find_or_create_by_value(value) resource.param_names << pn end - # Now save the whole thing host.save + return resource + end + + def params + {"owner" => "root", "mode" => "644"} + end + + # Create a resource param from a rails parameter + def test_to_resource + resource = mktest_resource # We need a scope interp, scope, source = mkclassframing # Find the new resource and include all it's parameters. resource = Puppet::Rails::Resource.find_by_id(resource.id, :include => [ :param_names, :param_values ]) # Now, try to convert our resource to a real resource res = nil assert_nothing_raised do res = resource.to_resource(scope) end assert_instance_of(Puppet::Parser::Resource, res) assert_equal("root", res[:owner]) assert_equal("644", res[:mode]) assert_equal("/tmp/to_resource", res.title) assert_equal(source, res.source) end + + def test_parameters + resource = mktest_resource + + setparams = nil + assert_nothing_raised do + setparams = resource.parameters + end + assert_equal(params, setparams, + "Did not get the right answer from #parameters") + end + + # Make sure we can retrieve individual parameters by name. + def test_parameter + resource = mktest_resource + + params.each do |p,v| + assert_equal(v, resource.parameter(p), "%s is not correct" % p) + end + end end else $stderr.puts "Install Rails for Rails and Caching tests" end # $Id$