diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-10-10 20:59:10 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-10-10 20:59:10 +0000 |
| commit | ed89572efd487b595aed943cd6b7a2920003b49d (patch) | |
| tree | 15483007621113784c73f1b5b5e5832d58512e34 /lib | |
| parent | b4fd8d18ed58bbfb947d5f348f28141c92c22f3a (diff) | |
| download | puppet-ed89572efd487b595aed943cd6b7a2920003b49d.tar.gz puppet-ed89572efd487b595aed943cd6b7a2920003b49d.tar.xz puppet-ed89572efd487b595aed943cd6b7a2920003b49d.zip | |
Committing the metatype branch -- this is just splitting the type.rb code into multiple files for readability
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1762 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/puppet/metatype/attributes.rb | 703 | ||||
| -rw-r--r-- | lib/puppet/metatype/closure.rb | 110 | ||||
| -rw-r--r-- | lib/puppet/metatype/container.rb | 116 | ||||
| -rw-r--r-- | lib/puppet/metatype/evaluation.rb | 126 | ||||
| -rw-r--r-- | lib/puppet/metatype/instances.rb | 257 | ||||
| -rw-r--r-- | lib/puppet/metatype/manager.rb | 153 | ||||
| -rw-r--r-- | lib/puppet/metatype/metaparams.rb | 351 | ||||
| -rw-r--r-- | lib/puppet/metatype/providers.rb | 183 | ||||
| -rw-r--r-- | lib/puppet/metatype/relationships.rb | 215 | ||||
| -rw-r--r-- | lib/puppet/metatype/schedules.rb | 39 | ||||
| -rw-r--r-- | lib/puppet/metatype/tags.rb | 39 | ||||
| -rw-r--r-- | lib/puppet/type.rb | 2287 | ||||
| -rw-r--r-- | lib/puppet/util/variables.rb | 39 |
13 files changed, 2350 insertions, 2268 deletions
diff --git a/lib/puppet/metatype/attributes.rb b/lib/puppet/metatype/attributes.rb new file mode 100644 index 000000000..e7ee6b591 --- /dev/null +++ b/lib/puppet/metatype/attributes.rb @@ -0,0 +1,703 @@ +require 'puppet' +require 'puppet/type' + +# see the bottom of the file for the rest of the inclusions + +class Puppet::Type + + class << self + include Puppet::Util::ClassGen + attr_reader :states + end + + # All parameters, in the appropriate order. The namevar comes first, + # then the states, then the params and metaparams in the order they + # were specified in the files. + def self.allattrs + # now get all of the arguments, in a specific order + # Cache this, since it gets called so many times + namevar = self.namevar + + order = [namevar] + order << [self.states.collect { |state| state.name }, + self.parameters, + self.metaparams].flatten.reject { |param| + # we don't want our namevar in there multiple times + param == namevar + } + + order.flatten! + + return order + end + + # Find the class associated with any given attribute. + def self.attrclass(name) + @attrclasses ||= {} + + # We cache the value, since this method gets called such a huge number + # of times (as in, hundreds of thousands in a given run). + unless @attrclasses.include?(name) + @attrclasses[name] = case self.attrtype(name) + when :state: @validstates[name] + when :meta: @@metaparamhash[name] + when :param: @paramhash[name] + end + end + @attrclasses[name] + end + + # What type of parameter are we dealing with? Cache the results, because + # this method gets called so many times. + def self.attrtype(attr) + @attrtypes ||= {} + unless @attrtypes.include?(attr) + @attrtypes[attr] = case + when @validstates.include?(attr): :state + when @@metaparamhash.include?(attr): :meta + when @paramhash.include?(attr): :param + else + raise Puppet::DevError, + "Invalid attribute '%s' for class '%s'" % + [attr, self.name] + end + end + + @attrtypes[attr] + end + + # Copy an existing class parameter. This allows other types to avoid + # duplicating a parameter definition, and is mostly used by subclasses + # of the File class. + def self.copyparam(klass, name) + param = klass.attrclass(name) + + unless param + raise Puppet::DevError, "Class %s has no param %s" % [klass, name] + end + @parameters << param + @parameters.each { |p| @paramhash[name] = p } + + if param.isnamevar? + @namevar = param.name + end + end + + # A similar function but one that yields the name, type, and class. + # This is mainly so that setdefaults doesn't call quite so many functions. + def self.eachattr(*ary) + # now get all of the arguments, in a specific order + # Cache this, since it gets called so many times + + if ary.empty? + ary = nil + end + self.states.each { |state| + yield(state, :state) if ary.nil? or ary.include?(state.name) + } + + @parameters.each { |param| + yield(param, :param) if ary.nil? or ary.include?(param.name) + } + + @@metaparams.each { |param| + yield(param, :meta) if ary.nil? or ary.include?(param.name) + } + end + + def self.eachmetaparam + @@metaparams.each { |p| yield p.name } + end + + # Create the 'ensure' class. This is a separate method so other types + # can easily call it and create their own 'ensure' values. + def self.ensurable(&block) + if block_given? + self.newstate(:ensure, :parent => Puppet::State::Ensure, &block) + else + self.newstate(:ensure, :parent => Puppet::State::Ensure) do + self.defaultvalues + end + end + end + + # Should we add the 'ensure' state to this class? + def self.ensurable? + # If the class has all three of these methods defined, then it's + # ensurable. + #ens = [:create, :destroy].inject { |set, method| + ens = [:exists?, :create, :destroy].inject { |set, method| + set &&= self.public_method_defined?(method) + } + + #puts "%s ensurability: %s" % [self.name, ens] + + return ens + end + + # Find the metaparameter class associated with a given metaparameter name. + def self.metaparamclass(name) + @@metaparamhash[symbolize(name)] + end + + # Create a new metaparam. Requires a block and a name, stores it in the + # @parameters array, and does some basic checking on it. + def self.newmetaparam(name, &block) + @@metaparams ||= [] + @@metaparamhash ||= {} + name = symbolize(name) + + param = genclass(name, + :parent => Puppet::Parameter, + :prefix => "MetaParam", + :hash => @@metaparamhash, + :array => @@metaparams, + &block + ) + + param.ismetaparameter + + return param + end + + # Find the namevar + def self.namevar + unless defined? @namevar + params = @parameters.find_all { |param| + param.isnamevar? or param.name == :name + } + + if params.length > 1 + raise Puppet::DevError, "Found multiple namevars for %s" % self.name + elsif params.length == 1 + @namevar = params[0].name + else + raise Puppet::DevError, "No namevar for %s" % self.name + end + end + @namevar + end + + # Create a new parameter. Requires a block and a name, stores it in the + # @parameters array, and does some basic checking on it. + def self.newparam(name, options = {}, &block) + param = genclass(name, + :parent => options[:parent] || Puppet::Parameter, + :attributes => { :element => self }, + :block => block, + :prefix => "Parameter", + :array => @parameters, + :hash => @paramhash + ) + + # These might be enabled later. +# define_method(name) do +# @parameters[name].value +# end +# +# define_method(name.to_s + "=") do |value| +# newparam(param, value) +# end + + if param.isnamevar? + @namevar = param.name + end + + return param + end + + # Create a new state. The first parameter must be the name of the state; + # this is how users will refer to the state when creating new instances. + # The second parameter is a hash of options; the options are: + # * <tt>:parent</tt>: The parent class for the state. Defaults to Puppet::State. + # * <tt>:retrieve</tt>: The method to call on the provider or @parent object (if + # the provider is not set) to retrieve the current value. + def self.newstate(name, options = {}, &block) + name = symbolize(name) + + # This is here for types that might still have the old method of defining + # a parent class. + unless options.is_a? Hash + raise Puppet::DevError, + "Options must be a hash, not %s" % options.inspect + end + + if @validstates.include?(name) + raise Puppet::DevError, "Class %s already has a state named %s" % + [self.name, name] + end + + # We have to create our own, new block here because we want to define + # an initial :retrieve method, if told to, and then eval the passed + # block if available. + s = genclass(name, + :parent => options[:parent] || Puppet::State, + :hash => @validstates + ) do + # If they've passed a retrieve method, then override the retrieve + # method on the class. + if options[:retrieve] + define_method(:retrieve) do + instance_variable_set( + "@is", provider.send(options[:retrieve]) + ) + end + end + + if block + class_eval(&block) + end + end + + # If it's the 'ensure' state, always put it first. + if name == :ensure + @states.unshift s + else + @states << s + end + +# define_method(name) do +# @states[name].should +# end +# +# define_method(name.to_s + "=") do |value| +# newstate(name, :should => value) +# end + + return s + end + + # Return the parameter names + def self.parameters + return [] unless defined? @parameters + @parameters.collect { |klass| klass.name } + end + + # Find the parameter class associated with a given parameter name. + def self.paramclass(name) + @paramhash[name] + end + + # Return the state class associated with a name + def self.statebyname(name) + @validstates[name] + end + + # does the name reflect a valid state? + def self.validstate?(name) + name = name.intern if name.is_a? String + if @validstates.include?(name) + return @validstates[name] + else + return false + end + end + + # Return the list of validstates + def self.validstates + return {} unless defined? @states + + return @validstates.keys + end + + # does the name reflect a valid parameter? + def self.validparameter?(name) + unless defined? @parameters + raise Puppet::DevError, "Class %s has not defined parameters" % self + end + if @paramhash.include?(name) or @@metaparamhash.include?(name) + return true + else + return false + end + end + + def self.validattr?(name) + name = symbolize(name) + @validattrs ||= {} + + unless @validattrs.include?(name) + if self.validstate?(name) or self.validparameter?(name) or self.metaparam?(name) + @validattrs[name] = true + else + @validattrs[name] = false + end + end + + @validattrs[name] + end + + # fix any namevar => param translations + def argclean(oldhash) + # This duplication is here because it might be a transobject. + hash = oldhash.dup.to_hash + + if hash.include?(:parent) + hash.delete(:parent) + end + namevar = self.class.namevar + + # Do a simple translation for those cases where they've passed :name + # but that's not our namevar + if hash.include? :name and namevar != :name + if hash.include? namevar + raise ArgumentError, "Cannot provide both name and %s" % namevar + end + hash[namevar] = hash[:name] + hash.delete(:name) + end + + # Make sure we have a name, one way or another + unless hash.include? namevar + if defined? @title and @title + hash[namevar] = @title + else + raise Puppet::Error, + "Was not passed a namevar or title" + end + end + + return hash + end + + # Is the specified parameter set? + def attrset?(type, attr) + case type + when :state: return @states.include?(attr) + when :param: return @parameters.include?(attr) + when :meta: return @metaparams.include?(attr) + else + self.devfail "Invalid set type %s" % [type] + end + end + + # Allow an outside party to specify the 'is' value for a state. The + # arguments are an array because you can't use parens with 'is=' calls. + # Most classes won't use this. + def is=(ary) + param, value = ary + if param.is_a?(String) + param = param.intern + end + if self.class.validstate?(param) + unless @states.include?(param) + self.newstate(param) + end + @states[param].is = value + else + self[param] = value + end + end + + # abstract accessing parameters and states, and normalize + # access to always be symbols, not strings + # This returns a value, not an object. It returns the 'is' + # value, but you can also specifically return 'is' and 'should' + # values using 'object.is(:state)' or 'object.should(:state)'. + def [](name) + if name.is_a?(String) + name = name.intern + end + + if name == :name + name = self.class.namevar + end + case self.class.attrtype(name) + when :state + if @states.include?(name) + return @states[name].is + else + return nil + end + when :meta + if @metaparams.include?(name) + return @metaparams[name].value + else + if default = self.class.metaparamclass(name).default + return default + else + return nil + end + end + when :param + if @parameters.include?(name) + return @parameters[name].value + else + if default = self.class.paramclass(name).default + return default + else + return nil + end + end + else + raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect]) + end + end + + # Abstract setting parameters and states, and normalize + # access to always be symbols, not strings. This sets the 'should' + # value on states, and otherwise just sets the appropriate parameter. + def []=(name,value) + if name.is_a?(String) + name = name.intern + end + + if name == :name + name = self.class.namevar + end + if value.nil? + raise Puppet::Error.new("Got nil value for %s" % name) + end + + case self.class.attrtype(name) + when :state + if value.is_a?(Puppet::State) + self.debug "'%s' got handed a state for '%s'" % [self,name] + @states[name] = value + else + if @states.include?(name) + @states[name].should = value + else + # newstate returns true if it successfully created the state, + # false otherwise; I just don't know what to do with that + # fact. + unless newstate(name, :should => value) + #self.info "%s failed" % name + end + end + end + when :meta + self.newmetaparam(self.class.metaparamclass(name), value) + when :param + klass = self.class.attrclass(name) + # if they've got a method to handle the parameter, then do it that way + self.newparam(klass, value) + else + raise Puppet::Error, "Invalid parameter %s" % [name] + end + end + + # remove a state from the object; useful in testing or in cleanup + # when an error has been encountered + def delete(attr) + case attr + when Puppet::Type + if @children.include?(attr) + @children.delete(attr) + end + else + if @states.has_key?(attr) + @states.delete(attr) + elsif @parameters.has_key?(attr) + @parameters.delete(attr) + elsif @metaparams.has_key?(attr) + @metaparams.delete(attr) + else + raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}") + end + end + end + + # iterate across the existing states + def eachstate + # states() is a private method + states().each { |state| + yield state + } + end + + # retrieve the 'is' value for a specified state + def is(state) + if @states.include?(state) + return @states[state].is + else + return nil + end + end + + # retrieve the 'should' value for a specified state + def should(state) + if @states.include?(state) + return @states[state].should + else + return nil + end + end + + # Create a new parameter. + def newparam(klass, value = nil) + newattr(:param, klass, value) + end + + # Create a new parameter or metaparameter. We'll leave the calling + # method to store it appropriately. + def newmetaparam(klass, value = nil) + newattr(:meta, klass, value) + end + + # The base function that the others wrap. + def newattr(type, klass, value = nil) + # This should probably be a bit, um, different, but... + if type == :state + return newstate(klass) + end + param = klass.new + param.parent = self + + unless value.nil? + param.value = value + end + + case type + when :meta + @metaparams[klass.name] = param + when :param + @parameters[klass.name] = param + else + self.devfail("Invalid param type %s" % type) + end + + return param + end + + # create a new state + def newstate(name, hash = {}) + stateklass = nil + if name.is_a?(Class) + stateklass = name + name = stateklass.name + else + stateklass = self.class.validstate?(name) + unless stateklass + self.fail("Invalid state %s" % name) + end + end + if @states.include?(name) + hash.each { |var,value| + @states[name].send(var.to_s + "=", value) + } + else + #Puppet.warning "Creating state %s for %s" % + # [stateklass.name,self.name] + begin + hash[:parent] = self + # make sure the state doesn't have any errors + newstate = stateklass.new(hash) + @states[name] = newstate + return newstate + rescue Puppet::Error => detail + # the state failed, so just ignore it + self.warning "State %s failed: %s" % + [name, detail] + return false + rescue Puppet::DevError => detail + # the state failed, so just ignore it + self.err "State %s failed: %s" % + [name, detail] + return false + rescue => detail + # the state failed, so just ignore it + self.err "State %s failed: %s (%s)" % + [name, detail, detail.class] + return false + end + end + end + + # return the value of a parameter + def parameter(name) + unless name.is_a? Symbol + name = name.intern + end + return @parameters[name].value + end + + # Is the named state defined? + def statedefined?(name) + unless name.is_a? Symbol + name = name.intern + end + return @states.include?(name) + end + + # return an actual type by name; to return the value, use 'inst[name]' + # FIXME this method should go away + def state(name) + unless name.is_a? Symbol + name = name.intern + end + return @states[name] + end + +# def set(name, value) +# send(name.to_s + "=", value) +# end +# +# def get(name) +# send(name) +# end + + # For any parameters or states that have defaults and have not yet been + # set, set them now. + def setdefaults(*ary) + self.class.eachattr(*ary) { |klass, type| + # not many attributes will have defaults defined, so we short-circuit + # those away + next unless klass.method_defined?(:default) + next if self.attrset?(type, klass.name) + + obj = self.newattr(type, klass) + value = obj.default + unless value.nil? + #self.debug "defaulting %s to %s" % [obj.name, obj.default] + obj.value = value + else + #self.debug "No default for %s" % obj.name + # "obj" is a Parameter. + self.delete(obj.name) + end + } + + end + + # Meta-parameter methods: These methods deal with the results + # of specifying metaparameters + + def self.metaparams + @@metaparams.collect { |param| param.name } + end + + # Is the parameter in question a meta-parameter? + def self.metaparam?(param) + param = symbolize(param) + @@metaparamhash.include?(param) + end + + # Documentation methods + def self.paramdoc(param) + @paramhash[param].doc + end + def self.metaparamdoc(metaparam) + @@metaparamhash[metaparam].doc + end + + private + + def states + #debug "%s has %s states" % [self,@states.length] + tmpstates = [] + self.class.states.each { |state| + if @states.include?(state.name) + tmpstates.push(@states[state.name]) + end + } + unless tmpstates.length == @states.length + self.devfail( + "Something went very wrong with tmpstates creation" + ) + end + return tmpstates + end +end + +# $Id$ diff --git a/lib/puppet/metatype/closure.rb b/lib/puppet/metatype/closure.rb new file mode 100644 index 000000000..8892222d5 --- /dev/null +++ b/lib/puppet/metatype/closure.rb @@ -0,0 +1,110 @@ +class Puppet::Type + attr_writer :implicit + + def self.implicitcreate(hash) + unless hash.include?(:implicit) + hash[:implicit] = true + end + if obj = self.create(hash) + obj.implicit = true + + return obj + else + return nil + end + end + + # Is this type's name isomorphic with the object? That is, if the + # name conflicts, does it necessarily mean that the objects conflict? + # Defaults to true. + def self.isomorphic? + if defined? @isomorphic + return @isomorphic + else + return true + end + end + + def implicit? + if defined? @implicit and @implicit + return true + else + return false + end + end + + # is the instance a managed instance? A 'yes' here means that + # the instance was created from the language, vs. being created + # in order resolve other questions, such as finding a package + # in a list + def managed? + # Once an object is managed, it always stays managed; but an object + # that is listed as unmanaged might become managed later in the process, + # so we have to check that every time + if defined? @managed and @managed + return @managed + else + @managed = false + states.each { |state| + if state.should and ! state.class.unmanaged + @managed = true + break + end + } + return @managed + end + end + + # Merge new information with an existing object, checking for conflicts + # and such. This allows for two specifications of the same object and + # the same values, but it's pretty limited right now. The result of merging + # states is very different from the result of merging parameters or metaparams. + # This is currently unused. + def merge(hash) + hash.each { |param, value| + if param.is_a?(String) + param = param.intern + end + + # Of course names are the same, duh. + next if param == :name or param == self.class.namevar + + unless value.is_a?(Array) + value = [value] + end + + if @states.include?(param) and oldvals = @states[param].shouldorig + unless oldvals.is_a?(Array) + oldvals = [oldvals] + end + # If the values are exactly the same, order and everything, + # then it's okay. + if oldvals == value + return true + end + # take the intersection + newvals = oldvals & value + if newvals.empty? + self.fail "No common values for %s on %s(%s)" % + [param, self.class.name, self.title] + elsif newvals.length > 1 + self.fail "Too many values for %s on %s(%s)" % + [param, self.class.name, self.title] + else + self.debug "Reduced old values %s and new values %s to %s" % + [oldvals.inspect, value.inspect, newvals.inspect] + @states[param].should = newvals + #self.should = newvals + return true + end + else + self[param] = value + end + } + + # Set the defaults again, just in case. + self.setdefaults + end +end + +# $Id$ diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb new file mode 100644 index 000000000..3f970b7d9 --- /dev/null +++ b/lib/puppet/metatype/container.rb @@ -0,0 +1,116 @@ +class Puppet::Type + attr_accessor :children + + # this is a retarded hack method to get around the difference between + # component children and file children + def self.depthfirst? + if defined? @depthfirst + return @depthfirst + else + return false + end + end + + def parent=(parent) + if self.parentof?(parent) + devfail "%s[%s] is already the parent of %s[%s]" % + [self.class.name, self.title, parent.class.name, parent.title] + end + @parent = parent + end + + # Add a hook for testing for recursion. + def parentof?(child) + if (self == child) + debug "parent is equal to child" + return true + elsif defined? @parent and @parent.parentof?(child) + debug "My parent is parent of child" + return true + elsif @children.include?(child) + debug "child is already in children array" + return true + else + return false + end + end + + def push(*childs) + unless defined? @children + @children = [] + end + childs.each { |child| + # Make sure we don't have any loops here. + if parentof?(child) + devfail "Already the parent of %s[%s]" % [child.class.name, child.title] + end + unless child.is_a?(Puppet::Element) + self.debug "Got object of type %s" % child.class + self.devfail( + "Containers can only contain Puppet::Elements, not %s" % + child.class + ) + end + @children.push(child) + child.parent = self + } + end + + # Remove an object. The argument determines whether the object's + # subscriptions get eliminated, too. + def remove(rmdeps = true) + # Our children remove themselves from our @children array (else the object + # we called this on at the top would not be removed), so we duplicate the + # array and iterate over that. If we don't do this, only half of the + # objects get removed. + @children.dup.each { |child| + child.remove(rmdeps) + } + + @children.clear + + # This is hackish (mmm, cut and paste), but it works for now, and it's + # better than warnings. + [@states, @parameters, @metaparams].each do |hash| + hash.each do |name, obj| + obj.remove + end + + hash.clear + end + + if rmdeps + Puppet::Event::Subscription.dependencies(self).each { |dep| + #info "Deleting dependency %s" % dep + #begin + # self.unsubscribe(dep) + #rescue + # # ignore failed unsubscribes + #end + dep.delete + } + Puppet::Event::Subscription.subscribers(self).each { |dep| + #info "Unsubscribing from %s" % dep + begin + dep.unsubscribe(self) + rescue + # ignore failed unsubscribes + end + } + end + self.class.delete(self) + + if defined? @parent and @parent + @parent.delete(self) + @parent = nil + end + + # Remove the reference to the provider. + if self.provider + @provider.clear + @provider = nil + end + end +end + +# $Id$ diff --git a/lib/puppet/metatype/evaluation.rb b/lib/puppet/metatype/evaluation.rb new file mode 100644 index 000000000..1ba9dee73 --- /dev/null +++ b/lib/puppet/metatype/evaluation.rb @@ -0,0 +1,126 @@ +class Puppet::Type + # retrieve the current value of all contained states + def retrieve + # it's important to use the method here, as it follows the order + # in which they're defined in the object + states().each { |state| + state.retrieve + } + end + + # Retrieve the changes associated with all of the states. + def statechanges + # If we are changing the existence of the object, then none of + # the other states matter. + changes = [] + if @states.include?(:ensure) and ! @states[:ensure].insync? + #self.info "ensuring %s from %s" % + # [@states[:ensure].should, @states[:ensure].is] + changes = [Puppet::StateChange.new(@states[:ensure])] + # Else, if the 'ensure' state is correctly absent, then do + # nothing + elsif @states.include?(:ensure) and @states[:ensure].is == :absent + #self.info "Object is correctly absent" + return [] + else + #if @states.include?(:ensure) + # self.info "ensure: Is: %s, Should: %s" % + # [@states[:ensure].is, @states[:ensure].should] + #else + # self.info "no ensure state" + #end + changes = states().find_all { |state| + ! state.insync? + }.collect { |state| + Puppet::StateChange.new(state) + } + end + + if Puppet[:debug] and changes.length > 0 + self.debug("Changing " + changes.collect { |ch| + ch.state.name + }.join(",") + ) + end + + changes + end + + # this method is responsible for collecting state changes + # we always descend into the children before we evaluate our current + # states + # this returns any changes resulting from testing, thus 'collect' + # rather than 'each' + def evaluate + now = Time.now + + #Puppet.err "Evaluating %s" % self.path.join(":") + unless defined? @evalcount + self.err "No evalcount defined on '%s' of type '%s'" % + [self.title,self.class] + @evalcount = 0 + end + @evalcount += 1 + + changes = [] + + # this only operates on states, not states + children + # it's important that we call retrieve() on the type instance, + # not directly on the state, because it allows the type to override + # the method, like pfile does + self.retrieve + + # states() is a private method, returning an ordered list + unless self.class.depthfirst? + changes += statechanges() + end + + changes << @children.collect { |child| + ch = child.evaluate + child.cache(:checked, now) + ch + } + + if self.class.depthfirst? + changes += statechanges() + end + + changes.flatten! + + # now record how many changes we've resulted in + if changes.length > 0 + self.debug "%s change(s)" % + [changes.length] + end + self.cache(:checked, now) + return changes.flatten + end + + # if all contained objects are in sync, then we're in sync + # FIXME I don't think this is used on the type instances any more, + # it's really only used for testing + def insync? + insync = true + + if state = @states[:ensure] + if state.insync? and state.should == :absent + return true + end + end + + states.each { |state| + unless state.insync? + state.debug("Not in sync: %s vs %s" % + [state.is.inspect, state.should.inspect]) + insync = false + #else + # state.debug("In sync") + end + } + + #self.debug("%s sync status is %s" % [self,insync]) + return insync + end +end + +# $Id$ diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb new file mode 100644 index 000000000..9e953a1dd --- /dev/null +++ b/lib/puppet/metatype/instances.rb @@ -0,0 +1,257 @@ +class Puppet::Type + # Make 'new' private, so people have to use create instead. + class << self + private :new + end + + # retrieve a named instance of the current type + def self.[](name) + if @objects.has_key?(name) + return @objects[name] + elsif @aliases.has_key?(name) + return @aliases[name] + else + return nil + end + end + + # add an instance by name to the class list of instances + def self.[]=(name,object) + newobj = nil + if object.is_a?(Puppet::Type) + newobj = object + else + raise Puppet::DevError, "must pass a Puppet::Type object" + end + + if exobj = @objects.has_key?(name) and self.isomorphic? + msg = "Object '%s[%s]' already exists" % + [name, newobj.class.name] + + if exobj.file and exobj.line + msg += ("in file %s at line %s" % + [object.file, object.line]) + end + if object.file and object.line + msg += ("and cannot be redefined in file %s at line %s" % + [object.file, object.line]) + end + error = Puppet::Error.new(msg) + else + #Puppet.info("adding %s of type %s to class list" % + # [name,object.class]) + @objects[name] = newobj + end + end + + # Create an alias. We keep these in a separate hash so that we don't encounter + # the objects multiple times when iterating over them. + def self.alias(name, obj) + if @objects.include?(name) + unless @objects[name] == obj + raise Puppet::Error.new( + "Cannot create alias %s: object already exists" % + [name] + ) + end + end + + if @aliases.include?(name) + unless @aliases[name] == obj + raise Puppet::Error.new( + "Object %s already has alias %s" % + [@aliases[name].name, name] + ) + end + end + + @aliases[name] = obj + end + + # remove all of the instances of a single type + def self.clear + if defined? @objects + @objects.each do |name, obj| + obj.remove(true) + end + @objects.clear + end + if defined? @aliases + @aliases.clear + end + end + + # Force users to call this, so that we can merge objects if + # necessary. FIXME This method should be responsible for most of the + # error handling. + def self.create(args) + # Don't modify the original hash; instead, create a duplicate and modify it. + # We have to dup and use the ! so that it stays a TransObject if it is + # one. + hash = args.dup + symbolizehash!(hash) + + # If we're the base class, then pass the info on appropriately + if self == Puppet::Type + type = nil + if hash.is_a? TransObject + type = hash.type + else + # If we're using the type to determine object type, then delete it + if type = hash[:type] + hash.delete(:type) + end + end + + if type + if typeklass = self.type(type) + return typeklass.create(hash) + else + raise Puppet::Error, "Unknown type %s" % type + end + else + raise Puppet::Error, "No type found for %s" % hash.inspect + end + end + + # Handle this new object being implicit + implicit = hash[:implicit] || false + if hash.include?(:implicit) + hash.delete(:implicit) + end + + name = nil + unless hash.is_a? TransObject + hash = self.hash2trans(hash) + end + + # XXX This will have to change when transobjects change to using titles + title = hash.name + + #Puppet.debug "Creating %s[%s]" % [self.name, title] + + # if the object already exists + if self.isomorphic? and retobj = self[title] + # if only one of our objects is implicit, then it's easy to see + # who wins -- the non-implicit one. + if retobj.implicit? and ! implicit + Puppet.notice "Removing implicit %s" % retobj.title + # Remove all of the objects, but do not remove their subscriptions. + retobj.remove(false) + + # now pass through and create the new object + elsif implicit + Puppet.notice "Ignoring implicit %s" % title + + return retobj + else + # If only one of the objects is being managed, then merge them + if retobj.managed? + raise Puppet::Error, "%s '%s' is already being managed" % + [self.name, title] + else + retobj.merge(hash) + return retobj + end + # We will probably want to support merging of some kind in + # the future, but for now, just throw an error. + #retobj.merge(hash) + + #return retobj + end + end + + # create it anew + # if there's a failure, destroy the object if it got that far, but raise + # the error. + begin + obj = new(hash) + rescue => detail + Puppet.err "Could not create %s: %s" % [title, detail.to_s] + if obj + obj.remove(true) + elsif obj = self[title] + obj.remove(true) + end + raise + end + + if implicit + obj.implicit = true + end + + # Store the object by title + self[obj.title] = obj + + return obj + end + + # remove a specified object + def self.delete(object) + return unless defined? @objects + if @objects.include?(object.title) + @objects.delete(object.title) + end + if @aliases.include?(object.title) + @aliases.delete(object.title) + end + end + + # iterate across each of the type's instances + def self.each + return unless defined? @objects + @objects.each { |name,instance| + yield instance + } + end + + # does the type have an object with the given name? + def self.has_key?(name) + return @objects.has_key?(name) + end + + # Convert a hash to a TransObject. + def self.hash2trans(hash) + title = nil + if hash.include? :title + title = hash[:title] + hash.delete(:title) + elsif hash.include? self.namevar + title = hash[self.namevar] + hash.delete(self.namevar) + + if hash.include? :name + raise ArgumentError, "Cannot provide both name and %s to %s" % + [self.namevar, self.name] + end + elsif hash[:name] + title = hash[:name] + hash.delete :name + end + + unless title + raise Puppet::Error, + "You must specify a title for objects of type %s" % self.to_s + end + + if hash.include? :type + unless self.validattr? :type + hash.delete :type + end + end + # okay, now make a transobject out of hash + begin + trans = TransObject.new(title, self.name.to_s) + hash.each { |param, value| + trans[param] = value + } + rescue => detail + raise Puppet::Error, "Could not create %s: %s" % + [name, detail] + end + + return trans + end +end + +# $Id$ diff --git a/lib/puppet/metatype/manager.rb b/lib/puppet/metatype/manager.rb new file mode 100644 index 000000000..0eee2213d --- /dev/null +++ b/lib/puppet/metatype/manager.rb @@ -0,0 +1,153 @@ +require 'puppet' +require 'puppet/util/classgen' + +# Methods dealing with Type management. This module gets included into the +# Puppet::Type class, it's just split out here for clarity. +module Puppet::MetaType +module Manager + include Puppet::Util::ClassGen + + # remove all type instances; this is mostly only useful for testing + def allclear + Puppet::Event::Subscription.clear + @types.each { |name, type| + type.clear + } + end + + # iterate across all of the subclasses of Type + def eachtype + @types.each do |name, type| + # Only consider types that have names + #if ! type.parameters.empty? or ! type.validstates.empty? + yield type + #end + end + end + + # Load all types. Only currently used for documentation. + def loadall + typeloader.loadall + end + + # Do an on-demand plugin load + def loadplugin(name) + paths = Puppet[:pluginpath].split(":") + unless paths.include?(Puppet[:plugindest]) + Puppet.notice "Adding plugin destination %s to plugin search path" % + Puppet[:plugindest] + Puppet[:pluginpath] += ":" + Puppet[:plugindest] + end + paths.each do |dir| + file = ::File.join(dir, name.to_s + ".rb") + if FileTest.exists?(file) + begin + load file + Puppet.info "loaded %s" % file + return true + rescue LoadError => detail + Puppet.info "Could not load plugin %s: %s" % + [file, detail] + return false + end + end + end + end + + # Define a new type. + def newtype(name, parent = nil, &block) + # First make sure we don't have a method sitting around + name = symbolize(name) + newmethod = "new#{name.to_s}" + + # Used for method manipulation. + selfobj = metaclass() + + @types ||= {} + + if @types.include?(name) + if self.respond_to?(newmethod) + # Remove the old newmethod + selfobj.send(:remove_method,newmethod) + end + end + + # Then create the class. + klass = genclass(name, + :parent => (parent || Puppet::Type), + :overwrite => true, + :hash => @types, + &block + ) + + # Now define a "new<type>" method for convenience. + if self.respond_to? newmethod + # Refuse to overwrite existing methods like 'newparam' or 'newtype'. + Puppet.warning "'new#{name.to_s}' method already exists; skipping" + else + selfobj.send(:define_method, newmethod) do |*args| + klass.create(*args) + end + end + + # If they've got all the necessary methods defined and they haven't + # already added the state, then do so now. + if klass.ensurable? and ! klass.validstate?(:ensure) + klass.ensurable + end + + # Now set up autoload any providers that might exist for this type. + klass.providerloader = Puppet::Autoload.new(klass, + "puppet/provider/#{klass.name.to_s}" + ) + + # We have to load everything so that we can figure out the default type. + klass.providerloader.loadall() + + klass + end + + # Return a Type instance by name. + def type(name) + @types ||= {} + + name = symbolize(name) + + if t = @types[name] + return t + else + if typeloader.load(name) + unless @types.include? name + Puppet.warning "Loaded puppet/type/#{name} but no class was created" + end + else + # If we can't load it from there, try loading it as a plugin. + loadplugin(name) + end + + return @types[name] + end + end + + # Create a loader for Puppet types. + def typeloader + unless defined? @typeloader + @typeloader = Puppet::Autoload.new(self, + "puppet/type", :wrap => false + ) + end + + @typeloader + end + + # remove all type instances; this is mostly only useful for testing + def allclear + Puppet::Event::Subscription.clear + @types.each { |name, type| + type.clear + } + end +end +end + +# $Id$ diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb new file mode 100644 index 000000000..b4030d3d0 --- /dev/null +++ b/lib/puppet/metatype/metaparams.rb @@ -0,0 +1,351 @@ +require 'puppet' +require 'puppet/type' + +class Puppet::Type + # Add all of the meta parameters. + #newmetaparam(:onerror) do + # desc "How to handle errors -- roll back innermost + # transaction, roll back entire transaction, ignore, etc. Currently + # non-functional." + #end + + newmetaparam(:noop) do + desc "Boolean flag indicating whether work should actually + be done. *true*/**false**" + munge do |noop| + if noop == "true" or noop == true + return true + elsif noop == "false" or noop == false + return false + else + self.fail("Invalid noop value '%s'" % noop) + end + end + end + + newmetaparam(:schedule) do + desc "On what schedule the object should be managed. You must create a + schedule object, and then reference the name of that object to use + that for your schedule: + + schedule { daily: + period => daily, + range => \"2-4\" + } + + exec { \"/usr/bin/apt-get update\": + schedule => daily + } + + The creation of the schedule object does not need to appear in the + configuration before objects that use it." + + munge do |name| + if schedule = Puppet.type(:schedule)[name] + return schedule + else + return name + end + end + end + + newmetaparam(:check) do + desc "States which should have their values retrieved + but which should not actually be modified. This is currently used + internally, but will eventually be used for querying, so that you + could specify that you wanted to check the install state of all + packages, and then query the Puppet client daemon to get reports + on all packages." + + munge do |args| + # If they've specified all, collect all known states + if args == :all + args = @parent.class.states.collect do |state| + state.name + end + end + + unless args.is_a?(Array) + args = [args] + end + + unless defined? @parent + self.devfail "No parent for %s, %s?" % + [self.class, self.name] + end + + args.each { |state| + unless state.is_a?(Symbol) + state = state.intern + end + next if @parent.statedefined?(state) + + stateklass = @parent.class.validstate?(state) + + unless stateklass + raise Puppet::Error, "%s is not a valid attribute for %s" % + [state, self.class.name] + end + next unless stateklass.checkable? + + @parent.newstate(state) + } + end + end + # For each object we require, subscribe to all events that it generates. We + # might reduce the level of subscription eventually, but for now... + newmetaparam(:require) do + desc "One or more objects that this object depends on. + This is used purely for guaranteeing that changes to required objects + happen before the dependent object. For instance: + + # Create the destination directory before you copy things down + file { \"/usr/local/scripts\": + ensure => directory + } + + file { \"/usr/local/scripts/myscript\": + source => \"puppet://server/module/myscript\", + mode => 755, + require => file[\"/usr/local/scripts\"] + } + + Note that Puppet will autorequire everything that it can, and + there are hooks in place so that it's easy for elements to add new + ways to autorequire objects, so if you think Puppet could be + smarter here, let us know. + + In fact, the above code was redundant -- Puppet will autorequire + any parent directories that are being managed; it will + automatically realize that the parent directory should be created + before the script is pulled down. + + Currently, exec elements will autorequire their CWD (if it is + specified) plus any fully qualified paths that appear in the + command. For instance, if you had an ``exec`` command that ran + the ``myscript`` mentioned above, the above code that pulls the + file down would be automatically listed as a requirement to the + ``exec`` code, so that you would always be running againts the + most recent version. + " + + # Take whatever dependencies currently exist and add these. + # Note that this probably doesn't behave correctly with unsubscribe. + munge do |requires| + # We need to be two arrays deep... + unless requires.is_a?(Array) + requires = [requires] + end + unless requires[0].is_a?(Array) + requires = [requires] + end + if values = @parent[:require] + requires = values + requires + end + requires + end + end + + # For each object we require, subscribe to all events that it generates. + # We might reduce the level of subscription eventually, but for now... + newmetaparam(:subscribe) do + desc "One or more objects that this object depends on. Changes in the + subscribed to objects result in the dependent objects being + refreshed (e.g., a service will get restarted). For instance: + + class nagios { + file { \"/etc/nagios/nagios.conf\": + source => \"puppet://server/module/nagios.conf\", + alias => nagconf # just to make things easier for me + } + service { nagios: + running => true, + subscribe => file[nagconf] + } + } + " + + munge do |requires| + if values = @parent[:subscribe] + requires = values + requires + end + requires + # @parent.handledepends(requires, :ALL_EVENTS, :refresh) + end + end + + newmetaparam(:loglevel) do + desc "Sets the level that information will be logged. + The log levels have the biggest impact when logs are sent to + syslog (which is currently the default)." + defaultto :notice + + newvalues(*Puppet::Log.levels) + newvalues(:verbose) + + munge do |loglevel| + val = super(loglevel) + if val == :verbose + val = :info + end + val + end + end + + newmetaparam(:alias) do + desc "Creates an alias for the object. Puppet uses this internally when you + provide a symbolic name: + + file { sshdconfig: + path => $operatingsystem ? { + solaris => \"/usr/local/etc/ssh/sshd_config\", + default => \"/etc/ssh/sshd_config\" + }, + source => \"...\" + } + + service { sshd: + subscribe => file[sshdconfig] + } + + When you use this feature, the parser sets ``sshdconfig`` as the name, + and the library sets that as an alias for the file so the dependency + lookup for ``sshd`` works. You can use this parameter yourself, + but note that only the library can use these aliases; for instance, + the following code will not work: + + file { \"/etc/ssh/sshd_config\": + owner => root, + group => root, + alias => sshdconfig + } + + file { sshdconfig: + mode => 644 + } + + There's no way here for the Puppet parser to know that these two stanzas + should be affecting the same file. + + See the [language tutorial][] for more information. + + [language tutorial]: languagetutorial.html + + " + + munge do |aliases| + unless aliases.is_a?(Array) + aliases = [aliases] + end + @parent.info "Adding aliases %s" % aliases.collect { |a| + a.inspect + }.join(", ") + aliases.each do |other| + if obj = @parent.class[other] + unless obj == @parent + self.fail( + "%s can not create alias %s: object already exists" % + [@parent.title, other] + ) + end + next + end + @parent.class.alias(other, @parent) + end + end + end + + newmetaparam(:tag) do + desc "Add the specified tags to the associated element. While all elements + are automatically tagged with as much information as possible + (e.g., each class and component containing the element), it can + be useful to add your own tags to a given element. + + Tags are currently useful for things like applying a subset of a + host's configuration: + + puppetd --test --tag mytag + + This way, when you're testing a configuration you can run just the + portion you're testing." + + munge do |tags| + tags = [tags] unless tags.is_a? Array + + tags.each do |tag| + @parent.tag(tag) + end + end + end + + newmetaparam(:notify) do + desc %{This parameter is the opposite of **subscribe** -- it sends events + to the specified object: + + file { "/etc/sshd_config": + source => "....", + notify => service[sshd] + } + + service { sshd: + ensure => running + } + + This will restart the sshd service if the sshd config file changes.} + + + # Take whatever dependencies currently exist and add these. + munge do |notifies| + # We need to be two arrays deep... + unless notifies.is_a?(Array) + notifies = [notifies] + end + unless notifies[0].is_a?(Array) + notifies = [notifies] + end + if values = @parent[:notify] + notifies = values + notifies + end + notifies + end + + end + + newmetaparam(:before) do + desc %{This parameter is the opposite of **require** -- it guarantees + that the specified object is applied later than the specifying + object: + + file { "/var/nagios/configuration": + source => "...", + recurse => true, + before => exec["nagios-rebuid"] + } + + exec { "nagios-rebuild": + command => "/usr/bin/make", + cwd => "/var/nagios/configuration" + } + + This will make sure all of the files are up to date before the + make command is run.} + + # Take whatever dependencies currently exist and add these. + munge do |notifies| + # We need to be two arrays deep... + unless notifies.is_a?(Array) + notifies = [notifies] + end + unless notifies[0].is_a?(Array) + notifies = [notifies] + end + if values = @parent[:notify] + notifies = values + notifies + end + notifies + end + + end +end # Puppet::Type + +# $Id$ diff --git a/lib/puppet/metatype/providers.rb b/lib/puppet/metatype/providers.rb new file mode 100644 index 000000000..ab7371f10 --- /dev/null +++ b/lib/puppet/metatype/providers.rb @@ -0,0 +1,183 @@ +class Puppet::Type + attr_reader :provider + + # the Type class attribute accessors + class << self + attr_accessor :providerloader + attr_writer :defaultprovider + end + + # Find the default provider. + def self.defaultprovider + unless defined? @defaultprovider and @defaultprovider + suitable = suitableprovider() + + # Find which providers are a default for this system. + defaults = suitable.find_all { |provider| provider.default? } + + # If we don't have any default we use suitable providers + defaults = suitable if defaults.empty? + max = defaults.collect { |provider| provider.defaultnum }.max + defaults = defaults.find_all { |provider| provider.defaultnum == max } + + retval = nil + if defaults.length > 1 + Puppet.warning( + "Found multiple default providers for %s: %s; using %s" % + [self.name, defaults.collect { |i| i.name.to_s }.join(", "), + defaults[0].name] + ) + retval = defaults.shift + elsif defaults.length == 1 + retval = defaults.shift + else + raise Puppet::DevError, "Could not find a default provider for %s" % + self.name + end + + @defaultprovider = retval + end + + return @defaultprovider + end + + # Retrieve a provider by name. + def self.provider(name) + name = Puppet::Util.symbolize(name) + + # If we don't have it yet, try loading it. + unless @providers.has_key?(name) + @providerloader.load(name) + end + return @providers[name] + end + + # Just list all of the providers. + def self.providers + @providers.keys + end + + def self.validprovider?(name) + name = Puppet::Util.symbolize(name) + + return (@providers.has_key?(name) && @providers[name].suitable?) + end + + # Create a new provider of a type. This method must be called + # directly on the type that it's implementing. + def self.provide(name, options = {}, &block) + name = Puppet::Util.symbolize(name) + model = self + + parent = if pname = options[:parent] + if pname.is_a? Class + pname + else + if provider = self.provider(pname) + provider + else + raise Puppet::DevError, + "Could not find parent provider %s of %s" % + [pname, name] + end + end + else + Puppet::Type::Provider + end + + self.providify + + provider = genclass(name, + :parent => parent, + :hash => @providers, + :prefix => "Provider", + :block => block, + :attributes => { + :model => model + } + ) + + return provider + end + + # Make sure we have a :provider parameter defined. Only gets called if there + # are providers. + def self.providify + return if @paramhash.has_key? :provider + model = self + newparam(:provider) do + desc "The specific backend for #{self.name.to_s} to use. You will + seldom need to specify this -- Puppet will usually discover the + appropriate provider for your platform." + + # This is so we can refer back to the type to get a list of + # providers for documentation. + class << self + attr_accessor :parenttype + end + + # We need to add documentation for each provider. + def self.doc + @doc + " Available providers are:\n\n" + parenttype().providers.sort { |a,b| + a.to_s <=> b.to_s + }.collect { |i| + "* **%s**: %s" % [i, parenttype().provider(i).doc] + }.join("\n") + end + + defaultto { @parent.class.defaultprovider.name } + + validate do |value| + value = value[0] if value.is_a? Array + if provider = @parent.class.provider(value) + unless provider.suitable? + raise ArgumentError, + "Provider '%s' is not functional on this platform" % + [value] + end + else + raise ArgumentError, "Invalid %s provider '%s'" % + [@parent.class.name, value] + end + end + + munge do |provider| + provider = provider[0] if provider.is_a? Array + if provider.is_a? String + provider = provider.intern + end + @parent.provider = provider + provider + end + end.parenttype = self + end + + def self.unprovide(name) + if @providers.has_key? name + if @defaultprovider and @defaultprovider.name == name + @defaultprovider = nil + end + @providers.delete(name) + end + end + + # Return an array of all of the suitable providers. + def self.suitableprovider + @providers.find_all { |name, provider| + provider.suitable? + }.collect { |name, provider| + provider + } + end + + def provider=(name) + if klass = self.class.provider(name) + @provider = klass.new(self) + else + raise UnknownProviderError, "Could not find %s provider of %s" % + [name, self.class.name] + end + end +end + +# $Id$ diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb new file mode 100644 index 000000000..714ed7690 --- /dev/null +++ b/lib/puppet/metatype/relationships.rb @@ -0,0 +1,215 @@ +class Puppet::Type + # Specify a block for generating a list of objects to autorequire. This + # makes it so that you don't have to manually specify things that you clearly + # require. + def self.autorequire(name, &block) + @autorequires ||= {} + @autorequires[name] = block + end + + # Yield each of those autorequires in turn, yo. + def self.eachautorequire + @autorequires ||= {} + @autorequires.each { |type, block| + yield(type, block) + } + end + + # Figure out of there are any objects we can automatically add as + # dependencies. + def autorequire + self.class.eachautorequire { |type, block| + # Ignore any types we can't find, although that would be a bit odd. + next unless typeobj = Puppet.type(type) + + # Retrieve the list of names from the block. + next unless list = self.instance_eval(&block) + unless list.is_a?(Array) + list = [list] + end + + # Collect the current prereqs + list.each { |dep| + obj = nil + # Support them passing objects directly, to save some effort. + if dep.is_a? Puppet::Type + type = dep.class.name + obj = dep + + # Now change our dependency to just the string, instead of + # the object itself. + dep = dep.title + else + # Skip autorequires that we aren't managing + unless obj = typeobj[dep] + next + end + end + + # Skip autorequires that we already require + next if self.requires?(obj) + + debug "Autorequiring %s %s" % [obj.class.name, obj.title] + self[:require] = [type, dep] + } + + #self.info reqs.inspect + #self[:require] = reqs + } + end + + # Build the dependencies associated with an individual object. + def builddepends + # Handle the requires + if self[:require] + self.handledepends(self[:require], :NONE, nil, true) + end + + # And the subscriptions + if self[:subscribe] + self.handledepends(self[:subscribe], :ALL_EVENTS, :refresh, true) + end + + if self[:notify] + self.handledepends(self[:notify], :ALL_EVENTS, :refresh, false) + end + + if self[:before] + self.handledepends(self[:before], :NONE, nil, false) + end + end + + # return all objects that we depend on + def eachdependency + Puppet::Event::Subscription.dependencies(self).each { |dep| + yield dep.source + } + end + + # return all objects subscribed to the current object + def eachsubscriber + Puppet::Event::Subscription.subscribers(self).each { |sub| + yield sub.target + } + end + + def handledepends(requires, event, method, up) + # Requires are specified in the form of [type, name], so they're always + # an array. But we want them to be an array of arrays. + unless requires[0].is_a?(Array) + requires = [requires] + end + requires.each { |rname| + # we just have a name and a type, and we need to convert it + # to an object... + type = nil + object = nil + tname = rname[0] + unless type = Puppet::Type.type(tname) + self.fail "Could not find type %s" % tname.inspect + end + name = rname[1] + unless object = type[name] + self.fail "Could not retrieve object '%s' of type '%s'" % + [name,type] + end + self.debug("subscribes to %s" % [object]) + + # Are we requiring them, or vice versa? + source = target = nil + if up + source = object + target = self + else + source = self + target = object + end + + # ok, both sides of the connection store some information + # we store the method to call when a given subscription is + # triggered, but the source object decides whether + subargs = { + :event => event, + :source => source, + :target => target + } + + if method and target.respond_to?(method) + subargs[:callback] = method + end + Puppet::Event::Subscription.new(subargs) + } + end + + def requires?(object) + req = false + self.eachdependency { |dep| + if dep == object + req = true + break + end + } + + return req + end + + def subscribe(hash) + hash[:source] = self + Puppet::Event::Subscription.new(hash) + + # add to the correct area + #@subscriptions.push sub + end + + def subscribesto?(object) + sub = false + self.eachsubscriber { |o| + if o == object + sub = true + break + end + } + + return sub + end + + # Unsubscribe from a given object, possibly with a specific event. + def unsubscribe(object, event = nil) + Puppet::Event::Subscription.dependencies(self).find_all { |sub| + if event + sub.match?(event) + else + sub.source == object + end + }.each { |sub| + sub.delete + } + end + + # we've received an event + # we only support local events right now, so we can pass actual + # objects around, including the transaction object + # the assumption here is that container objects will pass received + # methods on to contained objects + # i.e., we don't trigger our children, our refresh() method calls + # refresh() on our children + def trigger(event, source) + trans = event.transaction + if @callbacks.include?(source) + [:ALL_EVENTS, event.event].each { |eventname| + if method = @callbacks[source][eventname] + if trans.triggered?(self, method) > 0 + next + end + if self.respond_to?(method) + self.send(method) + end + + trans.triggered(self, method) + end + } + end + end +end + +# $Id$ diff --git a/lib/puppet/metatype/schedules.rb b/lib/puppet/metatype/schedules.rb new file mode 100644 index 000000000..40fa24cc6 --- /dev/null +++ b/lib/puppet/metatype/schedules.rb @@ -0,0 +1,39 @@ +class Puppet::Type + # Look up the schedule and set it appropriately. This is done after + # the instantiation phase, so that the schedule can be anywhere in the + # file. + def schedule + + # If we've already set the schedule, then just move on + return if self[:schedule].is_a?(Puppet.type(:schedule)) + + return unless self[:schedule] + + # Schedules don't need to be scheduled + #return if self.is_a?(Puppet.type(:schedule)) + + # Nor do components + #return if self.is_a?(Puppet.type(:component)) + + if sched = Puppet.type(:schedule)[self[:schedule]] + self[:schedule] = sched + else + self.fail "Could not find schedule %s" % self[:schedule] + end + end + + # Check whether we are scheduled to run right now or not. + def scheduled? + return true if Puppet[:ignoreschedules] + return true unless schedule = self[:schedule] + + # We use 'checked' here instead of 'synced' because otherwise we'll + # end up checking most elements most times, because they will generally + # have been synced a long time ago (e.g., a file only gets updated + # once a month on the server and its schedule is daily; the last sync time + # will have been a month ago, so we'd end up checking every run). + return schedule.match?(self.cached(:checked).to_i) + end +end + +# $Id$ diff --git a/lib/puppet/metatype/tags.rb b/lib/puppet/metatype/tags.rb new file mode 100644 index 000000000..2a78d89ec --- /dev/null +++ b/lib/puppet/metatype/tags.rb @@ -0,0 +1,39 @@ +class Puppet::Type + attr_reader :tags + + # Add a new tag. + def tag(tag) + tag = tag.intern if tag.is_a? String + unless @tags.include? tag + @tags << tag + end + end + + # Define the initial list of tags. + def tags=(list) + list = [list] unless list.is_a? Array + + @tags = list.collect do |t| + case t + when String: t.intern + when Symbol: t + else + self.warning "Ignoring tag %s of type %s" % [tag.inspect, tag.class] + end + end + + @tags << self.class.name unless @tags.include?(self.class.name) + end + + # Figure out of any of the specified tags apply to this object. This is an + # OR operation. + def tagged?(tags) + tags = [tags] unless tags.is_a? Array + + tags = tags.collect { |t| t.intern } + + return tags.find { |tag| @tags.include? tag } + end +end + +# $Id$ diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 9d3465e44..75f8c02c3 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -7,14 +7,26 @@ require 'puppet/type/state' require 'puppet/parameter' require 'puppet/util' require 'puppet/autoload' +require 'puppet/metatype/manager' # see the bottom of the file for the rest of the inclusions module Puppet -# The type is unknown -class UnknownTypeError < Puppet::Error; end -class UnknownProviderError < Puppet::Error; end class Type < Puppet::Element + # Nearly all of the code in this class is stored in files in the + # metatype/ directory. This is a temporary measure until I get a chance + # to refactor this class entirely. There's still more simplification to + # do, but this works for now. + require 'puppet/metatype/attributes' + require 'puppet/metatype/closure' + require 'puppet/metatype/container' + require 'puppet/metatype/evaluation' + require 'puppet/metatype/instances' + require 'puppet/metatype/metaparams' + require 'puppet/metatype/providers' + require 'puppet/metatype/relationships' + require 'puppet/metatype/schedules' + require 'puppet/metatype/tags' # Types (which map to elements in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an @@ -24,19 +36,10 @@ class Type < Puppet::Element # In naming methods, I have tried to consistently name the method so # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. - attr_accessor :children - attr_reader :provider attr_accessor :file, :line - attr_reader :tags, :parent + attr_reader :parent - attr_writer :implicit, :title - def implicit? - if defined? @implicit and @implicit - return true - else - return false - end - end + attr_writer :title include Enumerable @@ -46,47 +49,9 @@ class Type < Puppet::Element # the Type class attribute accessors class << self - attr_reader :name, :states - attr_accessor :providerloader - attr_writer :defaultprovider - + attr_reader :name include Enumerable, Puppet::Util::ClassGen - end - - # iterate across all of the subclasses of Type - def self.eachtype - @types.each do |name, type| - # Only consider types that have names - #if ! type.parameters.empty? or ! type.validstates.empty? - yield type - #end - end - end - - # Create the 'ensure' class. This is a separate method so other types - # can easily call it and create their own 'ensure' values. - def self.ensurable(&block) - if block_given? - self.newstate(:ensure, :parent => Puppet::State::Ensure, &block) - else - self.newstate(:ensure, :parent => Puppet::State::Ensure) do - self.defaultvalues - end - end - end - - # Should we add the 'ensure' state to this class? - def self.ensurable? - # If the class has all three of these methods defined, then it's - # ensurable. - #ens = [:create, :destroy].inject { |set, method| - ens = [:exists?, :create, :destroy].inject { |set, method| - set &&= self.public_method_defined?(method) - } - - #puts "%s ensurability: %s" % [self.name, ens] - - return ens + include Puppet::MetaType::Manager end # all of the variables that must be initialized for each subclass @@ -128,623 +93,6 @@ class Type < Puppet::Element end - # Load all types. Only currently used for documentation. - def self.loadall - typeloader.loadall - end - - # Do an on-demand plugin load - def self.loadplugin(name) - paths = Puppet[:pluginpath].split(":") - unless paths.include?(Puppet[:plugindest]) - Puppet.notice "Adding plugin destination %s to plugin search path" % - Puppet[:plugindest] - Puppet[:pluginpath] += ":" + Puppet[:plugindest] - end - paths.each do |dir| - file = ::File.join(dir, name.to_s + ".rb") - if FileTest.exists?(file) - begin - load file - Puppet.info "loaded %s" % file - return true - rescue LoadError => detail - Puppet.info "Could not load plugin %s: %s" % - [file, detail] - return false - end - end - end - end - - # Define a new type. - def self.newtype(name, parent = nil, &block) - # First make sure we don't have a method sitting around - name = symbolize(name) - newmethod = "new#{name.to_s}" - - # Used for method manipulation. - selfobj = metaclass() - - @types ||= {} - - if @types.include?(name) - if self.respond_to?(newmethod) - # Remove the old newmethod - selfobj.send(:remove_method,newmethod) - end - end - - # Then create the class. - klass = genclass(name, - :parent => (parent || Puppet::Type), - :overwrite => true, - :hash => @types, - &block - ) - - # Now define a "new<type>" method for convenience. - if self.respond_to? newmethod - # Refuse to overwrite existing methods like 'newparam' or 'newtype'. - Puppet.warning "'new#{name.to_s}' method already exists; skipping" - else - selfobj.send(:define_method, newmethod) do |*args| - klass.create(*args) - end - end - - # If they've got all the necessary methods defined and they haven't - # already added the state, then do so now. - if klass.ensurable? and ! klass.validstate?(:ensure) - klass.ensurable - end - - # Now set up autoload any providers that might exist for this type. - klass.providerloader = Puppet::Autoload.new(klass, - "puppet/provider/#{klass.name.to_s}" - ) - - # We have to load everything so that we can figure out the default type. - klass.providerloader.loadall() - - klass - end - - # Return a Type instance by name. - def self.type(name) - @types ||= {} - - name = symbolize(name) - - if t = @types[name] - return t - else - if typeloader.load(name) - unless @types.include? name - Puppet.warning "Loaded puppet/type/#{name} but no class was created" - end - else - # If we can't load it from there, try loading it as a plugin. - loadplugin(name) - end - - return @types[name] - end - end - - def self.typeloader - unless defined? @typeloader - @typeloader = Puppet::Autoload.new(self, - "puppet/type", :wrap => false - ) - end - - @typeloader - end - - # class methods dealing with type instance management - - public - - # Create an alias. We keep these in a separate hash so that we don't encounter - # the objects multiple times when iterating over them. - def self.alias(name, obj) - if @objects.include?(name) - unless @objects[name] == obj - raise Puppet::Error.new( - "Cannot create alias %s: object already exists" % - [name] - ) - end - end - - if @aliases.include?(name) - unless @aliases[name] == obj - raise Puppet::Error.new( - "Object %s already has alias %s" % - [@aliases[name].name, name] - ) - end - end - - @aliases[name] = obj - end - - # retrieve a named instance of the current type - def self.[](name) - if @objects.has_key?(name) - return @objects[name] - elsif @aliases.has_key?(name) - return @aliases[name] - else - return nil - end - end - - # add an instance by name to the class list of instances - def self.[]=(name,object) - newobj = nil - if object.is_a?(Puppet::Type) - newobj = object - else - raise Puppet::DevError, "must pass a Puppet::Type object" - end - - if exobj = @objects.has_key?(name) and self.isomorphic? - msg = "Object '%s[%s]' already exists" % - [name, newobj.class.name] - - if exobj.file and exobj.line - msg += ("in file %s at line %s" % - [object.file, object.line]) - end - if object.file and object.line - msg += ("and cannot be redefined in file %s at line %s" % - [object.file, object.line]) - end - error = Puppet::Error.new(msg) - else - #Puppet.info("adding %s of type %s to class list" % - # [name,object.class]) - @objects[name] = newobj - end - end - - # remove all type instances; this is mostly only useful for testing - def self.allclear - Puppet::Event::Subscription.clear - @types.each { |name, type| - type.clear - } - end - - # remove all of the instances of a single type - def self.clear - if defined? @objects - @objects.each do |name, obj| - obj.remove(true) - end - @objects.clear - end - if defined? @aliases - @aliases.clear - end - end - - # remove a specified object - def self.delete(object) - return unless defined? @objects - if @objects.include?(object.title) - @objects.delete(object.title) - end - if @aliases.include?(object.title) - @aliases.delete(object.title) - end - end - - # iterate across each of the type's instances - def self.each - return unless defined? @objects - @objects.each { |name,instance| - yield instance - } - end - - # does the type have an object with the given name? - def self.has_key?(name) - return @objects.has_key?(name) - end - - # Allow an outside party to specify the 'is' value for a state. The - # arguments are an array because you can't use parens with 'is=' calls. - # Most classes won't use this. - def is=(ary) - param, value = ary - if param.is_a?(String) - param = param.intern - end - if self.class.validstate?(param) - unless @states.include?(param) - self.newstate(param) - end - @states[param].is = value - else - self[param] = value - end - end - - # class and instance methods dealing with parameters and states - - public - - # Find the namevar - def self.namevar - unless defined? @namevar - params = @parameters.find_all { |param| - param.isnamevar? or param.name == :name - } - - if params.length > 1 - raise Puppet::DevError, "Found multiple namevars for %s" % self.name - elsif params.length == 1 - @namevar = params[0].name - else - raise Puppet::DevError, "No namevar for %s" % self.name - end - end - @namevar - end - - # Copy an existing class parameter. This allows other types to avoid - # duplicating a parameter definition, and is mostly used by subclasses - # of the File class. - def self.copyparam(klass, name) - param = klass.attrclass(name) - - unless param - raise Puppet::DevError, "Class %s has no param %s" % [klass, name] - end - @parameters << param - @parameters.each { |p| @paramhash[name] = p } - - if param.isnamevar? - @namevar = param.name - end - end - - # Create a new metaparam. Requires a block and a name, stores it in the - # @parameters array, and does some basic checking on it. - def self.newmetaparam(name, &block) - @@metaparams ||= [] - @@metaparamhash ||= {} - name = symbolize(name) - - param = genclass(name, - :parent => Puppet::Parameter, - :prefix => "MetaParam", - :hash => @@metaparamhash, - :array => @@metaparams, - &block - ) - - param.ismetaparameter - - return param - end - - def self.eachmetaparam - @@metaparams.each { |p| yield p.name } - end - - # Find the default provider. - def self.defaultprovider - unless defined? @defaultprovider and @defaultprovider - suitable = suitableprovider() - - # Find which providers are a default for this system. - defaults = suitable.find_all { |provider| provider.default? } - - # If we don't have any default we use suitable providers - defaults = suitable if defaults.empty? - max = defaults.collect { |provider| provider.defaultnum }.max - defaults = defaults.find_all { |provider| provider.defaultnum == max } - - retval = nil - if defaults.length > 1 - Puppet.warning( - "Found multiple default providers for %s: %s; using %s" % - [self.name, defaults.collect { |i| i.name.to_s }.join(", "), - defaults[0].name] - ) - retval = defaults.shift - elsif defaults.length == 1 - retval = defaults.shift - else - raise Puppet::DevError, "Could not find a default provider for %s" % - self.name - end - - @defaultprovider = retval - end - - return @defaultprovider - end - - # Retrieve a provider by name. - def self.provider(name) - name = Puppet::Util.symbolize(name) - - # If we don't have it yet, try loading it. - unless @providers.has_key?(name) - @providerloader.load(name) - end - return @providers[name] - end - - # Just list all of the providers. - def self.providers - @providers.keys - end - - def self.validprovider?(name) - name = Puppet::Util.symbolize(name) - - return (@providers.has_key?(name) && @providers[name].suitable?) - end - - # Create a new provider of a type. This method must be called - # directly on the type that it's implementing. - def self.provide(name, options = {}, &block) - name = Puppet::Util.symbolize(name) - model = self - - parent = if pname = options[:parent] - if pname.is_a? Class - pname - else - if provider = self.provider(pname) - provider - else - raise Puppet::DevError, - "Could not find parent provider %s of %s" % - [pname, name] - end - end - else - Puppet::Type::Provider - end - - self.providify - - provider = genclass(name, - :parent => parent, - :hash => @providers, - :prefix => "Provider", - :block => block, - :attributes => { - :model => model - } - ) - - return provider - end - - # Make sure we have a :provider parameter defined. Only gets called if there - # are providers. - def self.providify - return if @paramhash.has_key? :provider - model = self - newparam(:provider) do - desc "The specific backend for #{self.name.to_s} to use. You will - seldom need to specify this -- Puppet will usually discover the - appropriate provider for your platform." - - # This is so we can refer back to the type to get a list of - # providers for documentation. - class << self - attr_accessor :parenttype - end - - # We need to add documentation for each provider. - def self.doc - @doc + " Available providers are:\n\n" + parenttype().providers.sort { |a,b| - a.to_s <=> b.to_s - }.collect { |i| - "* **%s**: %s" % [i, parenttype().provider(i).doc] - }.join("\n") - end - - defaultto { @parent.class.defaultprovider.name } - - validate do |value| - value = value[0] if value.is_a? Array - if provider = @parent.class.provider(value) - unless provider.suitable? - raise ArgumentError, - "Provider '%s' is not functional on this platform" % - [value] - end - else - raise ArgumentError, "Invalid %s provider '%s'" % - [@parent.class.name, value] - end - end - - munge do |provider| - provider = provider[0] if provider.is_a? Array - if provider.is_a? String - provider = provider.intern - end - @parent.provider = provider - provider - end - end.parenttype = self - end - - def self.unprovide(name) - if @providers.has_key? name - if @defaultprovider and @defaultprovider.name == name - @defaultprovider = nil - end - @providers.delete(name) - end - end - - # Return an array of all of the suitable providers. - def self.suitableprovider - @providers.find_all { |name, provider| - provider.suitable? - }.collect { |name, provider| - provider - } - end - - def provider=(name) - if klass = self.class.provider(name) - @provider = klass.new(self) - else - raise UnknownProviderError, "Could not find %s provider of %s" % - [name, self.class.name] - end - end - - # Create a new parameter. Requires a block and a name, stores it in the - # @parameters array, and does some basic checking on it. - def self.newparam(name, options = {}, &block) - param = genclass(name, - :parent => options[:parent] || Puppet::Parameter, - :attributes => { :element => self }, - :block => block, - :prefix => "Parameter", - :array => @parameters, - :hash => @paramhash - ) - - # These might be enabled later. -# define_method(name) do -# @parameters[name].value -# end -# -# define_method(name.to_s + "=") do |value| -# newparam(param, value) -# end - - if param.isnamevar? - @namevar = param.name - end - - return param - end - - # Create a new state. The first parameter must be the name of the state; - # this is how users will refer to the state when creating new instances. - # The second parameter is a hash of options; the options are: - # * <tt>:parent</tt>: The parent class for the state. Defaults to Puppet::State. - # * <tt>:retrieve</tt>: The method to call on the provider or @parent object (if - # the provider is not set) to retrieve the current value. - def self.newstate(name, options = {}, &block) - name = symbolize(name) - - # This is here for types that might still have the old method of defining - # a parent class. - unless options.is_a? Hash - raise Puppet::DevError, - "Options must be a hash, not %s" % options.inspect - end - - if @validstates.include?(name) - raise Puppet::DevError, "Class %s already has a state named %s" % - [self.name, name] - end - - # We have to create our own, new block here because we want to define - # an initial :retrieve method, if told to, and then eval the passed - # block if available. - s = genclass(name, - :parent => options[:parent] || Puppet::State, - :hash => @validstates - ) do - # If they've passed a retrieve method, then override the retrieve - # method on the class. - if options[:retrieve] - define_method(:retrieve) do - instance_variable_set( - "@is", provider.send(options[:retrieve]) - ) - end - end - - if block - class_eval(&block) - end - end - - # If it's the 'ensure' state, always put it first. - if name == :ensure - @states.unshift s - else - @states << s - end - -# define_method(name) do -# @states[name].should -# end -# -# define_method(name.to_s + "=") do |value| -# newstate(name, :should => value) -# end - - return s - end - - # Specify a block for generating a list of objects to autorequire. This - # makes it so that you don't have to manually specify things that you clearly - # require. - def self.autorequire(name, &block) - @autorequires ||= {} - @autorequires[name] = block - end - - # Yield each of those autorequires in turn, yo. - def self.eachautorequire - @autorequires ||= {} - @autorequires.each { |type, block| - yield(type, block) - } - end - - # Return the parameter names - def self.parameters - return [] unless defined? @parameters - @parameters.collect { |klass| klass.name } - end - - # Find the metaparameter class associated with a given metaparameter name. - def self.metaparamclass(name) - @@metaparamhash[symbolize(name)] - end - - # Find the parameter class associated with a given parameter name. - def self.paramclass(name) - @paramhash[name] - end - - # Find the class associated with any given attribute. - def self.attrclass(name) - @attrclasses ||= {} - - # We cache the value, since this method gets called such a huge number - # of times (as in, hundreds of thousands in a given run). - unless @attrclasses.include?(name) - @attrclasses[name] = case self.attrtype(name) - when :state: @validstates[name] - when :meta: @@metaparamhash[name] - when :param: @paramhash[name] - end - end - @attrclasses[name] - end - def self.to_s if defined? @name "Puppet::Type::" + @name.to_s.capitalize @@ -760,226 +108,6 @@ class Type < Puppet::Element #@validate = block end - # does the name reflect a valid state? - def self.validstate?(name) - name = name.intern if name.is_a? String - if @validstates.include?(name) - return @validstates[name] - else - return false - end - end - - # Return the list of validstates - def self.validstates - return {} unless defined? @states - - return @validstates.keys - end - - # Return the state class associated with a name - def self.statebyname(name) - @validstates[name] - end - - # does the name reflect a valid parameter? - def self.validparameter?(name) - unless defined? @parameters - raise Puppet::DevError, "Class %s has not defined parameters" % self - end - if @paramhash.include?(name) or @@metaparamhash.include?(name) - return true - else - return false - end - end - - # What type of parameter are we dealing with? Cache the results, because - # this method gets called so many times. - def self.attrtype(attr) - @attrtypes ||= {} - unless @attrtypes.include?(attr) - @attrtypes[attr] = case - when @validstates.include?(attr): :state - when @@metaparamhash.include?(attr): :meta - when @paramhash.include?(attr): :param - else - raise Puppet::DevError, - "Invalid attribute '%s' for class '%s'" % - [attr, self.name] - end - end - - @attrtypes[attr] - end - - # All parameters, in the appropriate order. The namevar comes first, - # then the states, then the params and metaparams in the order they - # were specified in the files. - def self.allattrs - # now get all of the arguments, in a specific order - # Cache this, since it gets called so many times - namevar = self.namevar - - order = [namevar] - order << [self.states.collect { |state| state.name }, - self.parameters, - self.metaparams].flatten.reject { |param| - # we don't want our namevar in there multiple times - param == namevar - } - - order.flatten! - - return order - end - - # A similar function but one that yields the name, type, and class. - # This is mainly so that setdefaults doesn't call quite so many functions. - def self.eachattr(*ary) - # now get all of the arguments, in a specific order - # Cache this, since it gets called so many times - - if ary.empty? - ary = nil - end - self.states.each { |state| - yield(state, :state) if ary.nil? or ary.include?(state.name) - } - - @parameters.each { |param| - yield(param, :param) if ary.nil? or ary.include?(param.name) - } - - @@metaparams.each { |param| - yield(param, :meta) if ary.nil? or ary.include?(param.name) - } - end - - def self.validattr?(name) - name = symbolize(name) - @validattrs ||= {} - - unless @validattrs.include?(name) - if self.validstate?(name) or self.validparameter?(name) or self.metaparam?(name) - @validattrs[name] = true - else - @validattrs[name] = false - end - end - - @validattrs[name] - end - - # abstract accessing parameters and states, and normalize - # access to always be symbols, not strings - # This returns a value, not an object. It returns the 'is' - # value, but you can also specifically return 'is' and 'should' - # values using 'object.is(:state)' or 'object.should(:state)'. - def [](name) - if name.is_a?(String) - name = name.intern - end - - if name == :name - name = self.class.namevar - end - case self.class.attrtype(name) - when :state - if @states.include?(name) - return @states[name].is - else - return nil - end - when :meta - if @metaparams.include?(name) - return @metaparams[name].value - else - if default = self.class.metaparamclass(name).default - return default - else - return nil - end - end - when :param - if @parameters.include?(name) - return @parameters[name].value - else - if default = self.class.paramclass(name).default - return default - else - return nil - end - end - else - raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect]) - end - end - - # Abstract setting parameters and states, and normalize - # access to always be symbols, not strings. This sets the 'should' - # value on states, and otherwise just sets the appropriate parameter. - def []=(name,value) - if name.is_a?(String) - name = name.intern - end - - if name == :name - name = self.class.namevar - end - if value.nil? - raise Puppet::Error.new("Got nil value for %s" % name) - end - - case self.class.attrtype(name) - when :state - if value.is_a?(Puppet::State) - self.debug "'%s' got handed a state for '%s'" % [self,name] - @states[name] = value - else - if @states.include?(name) - @states[name].should = value - else - # newstate returns true if it successfully created the state, - # false otherwise; I just don't know what to do with that - # fact. - unless newstate(name, :should => value) - #self.info "%s failed" % name - end - end - end - when :meta - self.newmetaparam(self.class.metaparamclass(name), value) - when :param - klass = self.class.attrclass(name) - # if they've got a method to handle the parameter, then do it that way - self.newparam(klass, value) - else - raise Puppet::Error, "Invalid parameter %s" % [name] - end - end - - # remove a state from the object; useful in testing or in cleanup - # when an error has been encountered - def delete(attr) - case attr - when Puppet::Type - if @children.include?(attr) - @children.delete(attr) - end - else - if @states.has_key?(attr) - @states.delete(attr) - elsif @parameters.has_key?(attr) - @parameters.delete(attr) - elsif @metaparams.has_key?(attr) - @metaparams.delete(attr) - else - raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}") - end - end - end - # iterate across all children, and then iterate across states # we do children first so we're sure that all dependent objects # are checked first @@ -1013,32 +141,6 @@ class Type < Puppet::Element end block.call(self) end - - # iterate across the existing states - def eachstate - # states() is a private method - states().each { |state| - yield state - } - end - - # retrieve the 'is' value for a specified state - def is(state) - if @states.include?(state) - return @states[state].is - else - return nil - end - end - - # retrieve the 'should' value for a specified state - def should(state) - if @states.include?(state) - return @states[state].should - else - return nil - end - end # create a log at specified level def log(msg) @@ -1049,435 +151,12 @@ class Type < Puppet::Element ) end - # is the instance a managed instance? A 'yes' here means that - # the instance was created from the language, vs. being created - # in order resolve other questions, such as finding a package - # in a list - def managed? - # Once an object is managed, it always stays managed; but an object - # that is listed as unmanaged might become managed later in the process, - # so we have to check that every time - if defined? @managed and @managed - return @managed - else - @managed = false - states.each { |state| - if state.should and ! state.class.unmanaged - @managed = true - break - end - } - return @managed - end - end - - # Create a new parameter. - def newparam(klass, value = nil) - newattr(:param, klass, value) - end - - # Create a new parameter or metaparameter. We'll leave the calling - # method to store it appropriately. - def newmetaparam(klass, value = nil) - newattr(:meta, klass, value) - end - - # The base function that the others wrap. - def newattr(type, klass, value = nil) - # This should probably be a bit, um, different, but... - if type == :state - return newstate(klass) - end - param = klass.new - param.parent = self - - unless value.nil? - param.value = value - end - - case type - when :meta - @metaparams[klass.name] = param - when :param - @parameters[klass.name] = param - else - self.devfail("Invalid param type %s" % type) - end - - return param - end - - # create a new state - def newstate(name, hash = {}) - stateklass = nil - if name.is_a?(Class) - stateklass = name - name = stateklass.name - else - stateklass = self.class.validstate?(name) - unless stateklass - self.fail("Invalid state %s" % name) - end - end - if @states.include?(name) - hash.each { |var,value| - @states[name].send(var.to_s + "=", value) - } - else - #Puppet.warning "Creating state %s for %s" % - # [stateklass.name,self.name] - begin - hash[:parent] = self - # make sure the state doesn't have any errors - newstate = stateklass.new(hash) - @states[name] = newstate - return newstate - rescue Puppet::Error => detail - # the state failed, so just ignore it - self.warning "State %s failed: %s" % - [name, detail] - return false - rescue Puppet::DevError => detail - # the state failed, so just ignore it - self.err "State %s failed: %s" % - [name, detail] - return false - rescue => detail - # the state failed, so just ignore it - self.err "State %s failed: %s (%s)" % - [name, detail, detail.class] - return false - end - end - end - - # return the value of a parameter - def parameter(name) - unless name.is_a? Symbol - name = name.intern - end - return @parameters[name].value - end - - def parent=(parent) - if self.parentof?(parent) - devfail "%s[%s] is already the parent of %s[%s]" % - [self.class.name, self.title, parent.class.name, parent.title] - end - @parent = parent - end - - # Add a hook for testing for recursion. - def parentof?(child) - if (self == child) - debug "parent is equal to child" - return true - elsif defined? @parent and @parent.parentof?(child) - debug "My parent is parent of child" - return true - elsif @children.include?(child) - debug "child is already in children array" - return true - else - return false - end - end - - def push(*childs) - unless defined? @children - @children = [] - end - childs.each { |child| - # Make sure we don't have any loops here. - if parentof?(child) - devfail "Already the parent of %s[%s]" % [child.class.name, child.title] - end - unless child.is_a?(Puppet::Element) - self.debug "Got object of type %s" % child.class - self.devfail( - "Containers can only contain Puppet::Elements, not %s" % - child.class - ) - end - @children.push(child) - child.parent = self - } - end - - # Remove an object. The argument determines whether the object's - # subscriptions get eliminated, too. - def remove(rmdeps = true) - # Our children remove themselves from our @children array (else the object - # we called this on at the top would not be removed), so we duplicate the - # array and iterate over that. If we don't do this, only half of the - # objects get removed. - @children.dup.each { |child| - child.remove(rmdeps) - } - - @children.clear - - # This is hackish (mmm, cut and paste), but it works for now, and it's - # better than warnings. - [@states, @parameters, @metaparams].each do |hash| - hash.each do |name, obj| - obj.remove - end - - hash.clear - end - - if rmdeps - Puppet::Event::Subscription.dependencies(self).each { |dep| - #info "Deleting dependency %s" % dep - #begin - # self.unsubscribe(dep) - #rescue - # # ignore failed unsubscribes - #end - dep.delete - } - Puppet::Event::Subscription.subscribers(self).each { |dep| - #info "Unsubscribing from %s" % dep - begin - dep.unsubscribe(self) - rescue - # ignore failed unsubscribes - end - } - end - self.class.delete(self) - - if defined? @parent and @parent - @parent.delete(self) - @parent = nil - end - - # Remove the reference to the provider. - if self.provider - @provider.clear - @provider = nil - end - end - - # Is the named state defined? - def statedefined?(name) - unless name.is_a? Symbol - name = name.intern - end - return @states.include?(name) - end - - # return an actual type by name; to return the value, use 'inst[name]' - # FIXME this method should go away - def state(name) - unless name.is_a? Symbol - name = name.intern - end - return @states[name] - end - - private - - def states - #debug "%s has %s states" % [self,@states.length] - tmpstates = [] - self.class.states.each { |state| - if @states.include?(state.name) - tmpstates.push(@states[state.name]) - end - } - unless tmpstates.length == @states.length - self.devfail( - "Something went very wrong with tmpstates creation" - ) - end - return tmpstates - end - # instance methods related to instance intrinsics # e.g., initialize() and name() public - # Force users to call this, so that we can merge objects if - # necessary. FIXME This method should be responsible for most of the - # error handling. - def self.create(args) - # Don't modify the original hash; instead, create a duplicate and modify it. - # We have to dup and use the ! so that it stays a TransObject if it is - # one. - hash = args.dup - symbolizehash!(hash) - - # If we're the base class, then pass the info on appropriately - if self == Puppet::Type - type = nil - if hash.is_a? TransObject - type = hash.type - else - # If we're using the type to determine object type, then delete it - if type = hash[:type] - hash.delete(:type) - end - end - - if type - if typeklass = self.type(type) - return typeklass.create(hash) - else - raise Puppet::Error, "Unknown type %s" % type - end - else - raise Puppet::Error, "No type found for %s" % hash.inspect - end - end - - # Handle this new object being implicit - implicit = hash[:implicit] || false - if hash.include?(:implicit) - hash.delete(:implicit) - end - - name = nil - unless hash.is_a? TransObject - hash = self.hash2trans(hash) - end - - # XXX This will have to change when transobjects change to using titles - title = hash.name - - #Puppet.debug "Creating %s[%s]" % [self.name, title] - - # if the object already exists - if self.isomorphic? and retobj = self[title] - # if only one of our objects is implicit, then it's easy to see - # who wins -- the non-implicit one. - if retobj.implicit? and ! implicit - Puppet.notice "Removing implicit %s" % retobj.title - # Remove all of the objects, but do not remove their subscriptions. - retobj.remove(false) - - # now pass through and create the new object - elsif implicit - Puppet.notice "Ignoring implicit %s" % title - - return retobj - else - # If only one of the objects is being managed, then merge them - if retobj.managed? - raise Puppet::Error, "%s '%s' is already being managed" % - [self.name, title] - else - retobj.merge(hash) - return retobj - end - # We will probably want to support merging of some kind in - # the future, but for now, just throw an error. - #retobj.merge(hash) - - #return retobj - end - end - - # create it anew - # if there's a failure, destroy the object if it got that far, but raise - # the error. - begin - obj = new(hash) - rescue => detail - Puppet.err "Could not create %s: %s" % [title, detail.to_s] - if obj - obj.remove(true) - elsif obj = self[title] - obj.remove(true) - end - raise - end - - if implicit - obj.implicit = true - end - - # Store the object by title - self[obj.title] = obj - - return obj - end - - # Convert a hash to a TransObject. - def self.hash2trans(hash) - title = nil - if hash.include? :title - title = hash[:title] - hash.delete(:title) - elsif hash.include? self.namevar - title = hash[self.namevar] - hash.delete(self.namevar) - - if hash.include? :name - raise ArgumentError, "Cannot provide both name and %s to %s" % - [self.namevar, self.name] - end - elsif hash[:name] - title = hash[:name] - hash.delete :name - end - - unless title - raise Puppet::Error, - "You must specify a title for objects of type %s" % self.to_s - end - - if hash.include? :type - unless self.validattr? :type - hash.delete :type - end - end - # okay, now make a transobject out of hash - begin - trans = TransObject.new(title, self.name.to_s) - hash.each { |param, value| - trans[param] = value - } - rescue => detail - raise Puppet::Error, "Could not create %s: %s" % - [name, detail] - end - - return trans - end - - def self.implicitcreate(hash) - unless hash.include?(:implicit) - hash[:implicit] = true - end - if obj = self.create(hash) - obj.implicit = true - - return obj - else - return nil - end - end - - # Is this type's name isomorphic with the object? That is, if the - # name conflicts, does it necessarily mean that the objects conflict? - # Defaults to true. - def self.isomorphic? - if defined? @isomorphic - return @isomorphic - else - return true - end - end - - # and then make 'new' private - class << self - private :new - end - def initvars @children = [] @evalcount = 0 @@ -1559,17 +238,6 @@ class Type < Puppet::Element # Munge up the namevar stuff so we only have one value. hash = self.argclean(hash) - # If we've got both a title via some other mechanism, set it as an alias. -# if defined? @title and @title and ! hash[:name] -# if aliases = hash[:alias] -# aliases = [aliases] unless aliases.is_a? Array -# aliases << @title -# hash[:alias] = aliases -# else -# hash[:alias] = @title -# end -# end - # Let's do the name first, because some things need to happen once # we have the name but before anything else @@ -1600,13 +268,6 @@ class Type < Puppet::Element end end - # The information to cache to disk. We have to do this after - # the name is set because it uses the name and/or path, but before - # everything else is set because the states need to be able to - # retrieve their stored info. - #@cache = Puppet::Storage.cache(self) - - # This is all of our attributes except the namevar. attrs.each { |attr| if hash.include?(attr) @@ -1639,49 +300,6 @@ class Type < Puppet::Element end end - # Figure out of there are any objects we can automatically add as - # dependencies. - def autorequire - self.class.eachautorequire { |type, block| - # Ignore any types we can't find, although that would be a bit odd. - next unless typeobj = Puppet.type(type) - - # Retrieve the list of names from the block. - next unless list = self.instance_eval(&block) - unless list.is_a?(Array) - list = [list] - end - - # Collect the current prereqs - list.each { |dep| - obj = nil - # Support them passing objects directly, to save some effort. - if dep.is_a? Puppet::Type - type = dep.class.name - obj = dep - - # Now change our dependency to just the string, instead of - # the object itself. - dep = dep.title - else - # Skip autorequires that we aren't managing - unless obj = typeobj[dep] - next - end - end - - # Skip autorequires that we already require - next if self.requires?(obj) - - debug "Autorequiring %s %s" % [obj.class.name, obj.title] - self[:require] = [type, dep] - } - - #self.info reqs.inspect - #self[:require] = reqs - } - end - # Set up all of our autorequires. def finish self.autorequire @@ -1703,87 +321,6 @@ class Type < Puppet::Element #@cache[name] = value end - # Look up the schedule and set it appropriately. This is done after - # the instantiation phase, so that the schedule can be anywhere in the - # file. - def schedule - - # If we've already set the schedule, then just move on - return if self[:schedule].is_a?(Puppet.type(:schedule)) - - return unless self[:schedule] - - # Schedules don't need to be scheduled - #return if self.is_a?(Puppet.type(:schedule)) - - # Nor do components - #return if self.is_a?(Puppet.type(:component)) - - if sched = Puppet.type(:schedule)[self[:schedule]] - self[:schedule] = sched - else - self.fail "Could not find schedule %s" % self[:schedule] - end - end - - # Check whether we are scheduled to run right now or not. - def scheduled? - return true if Puppet[:ignoreschedules] - return true unless schedule = self[:schedule] - - # We use 'checked' here instead of 'synced' because otherwise we'll - # end up checking most elements most times, because they will generally - # have been synced a long time ago (e.g., a file only gets updated - # once a month on the server and its schedule is daily; the last sync time - # will have been a month ago, so we'd end up checking every run). - return schedule.match?(self.cached(:checked).to_i) - end - - # Add a new tag. - def tag(tag) - tag = tag.intern if tag.is_a? String - unless @tags.include? tag - @tags << tag - end - end - - # Define the initial list of tags. - def tags=(list) - list = [list] unless list.is_a? Array - - @tags = list.collect do |t| - case t - when String: t.intern - when Symbol: t - else - self.warning "Ignoring tag %s of type %s" % [tag.inspect, tag.class] - end - end - - @tags << self.class.name unless @tags.include?(self.class.name) - end - - # Figure out of any of the specified tags apply to this object. This is an - # OR operation. - def tagged?(tags) - tags = [tags] unless tags.is_a? Array - - tags = tags.collect { |t| t.intern } - - return tags.find { |tag| @tags.include? tag } - end - - # Is the specified parameter set? - def attrset?(type, attr) - case type - when :state: return @states.include?(attr) - when :param: return @parameters.include?(attr) - when :meta: return @metaparams.include?(attr) - else - self.devfail "Invalid set type %s" % [type] - end - end - # def set(name, value) # send(name.to_s + "=", value) # end @@ -1792,102 +329,10 @@ class Type < Puppet::Element # send(name) # end - # For any parameters or states that have defaults and have not yet been - # set, set them now. - def setdefaults(*ary) - self.class.eachattr(*ary) { |klass, type| - # not many attributes will have defaults defined, so we short-circuit - # those away - next unless klass.method_defined?(:default) - next if self.attrset?(type, klass.name) - - obj = self.newattr(type, klass) - value = obj.default - unless value.nil? - #self.debug "defaulting %s to %s" % [obj.name, obj.default] - obj.value = value - else - #self.debug "No default for %s" % obj.name - # "obj" is a Parameter. - self.delete(obj.name) - end - } - - end - - # Merge new information with an existing object, checking for conflicts - # and such. This allows for two specifications of the same object and - # the same values, but it's pretty limited right now. The result of merging - # states is very different from the result of merging parameters or metaparams. - # This is currently unused. - def merge(hash) - hash.each { |param, value| - if param.is_a?(String) - param = param.intern - end - - # Of course names are the same, duh. - next if param == :name or param == self.class.namevar - - unless value.is_a?(Array) - value = [value] - end - - if @states.include?(param) and oldvals = @states[param].shouldorig - unless oldvals.is_a?(Array) - oldvals = [oldvals] - end - # If the values are exactly the same, order and everything, - # then it's okay. - if oldvals == value - return true - end - # take the intersection - newvals = oldvals & value - if newvals.empty? - self.fail "No common values for %s on %s(%s)" % - [param, self.class.name, self.title] - elsif newvals.length > 1 - self.fail "Too many values for %s on %s(%s)" % - [param, self.class.name, self.title] - else - self.debug "Reduced old values %s and new values %s to %s" % - [oldvals.inspect, value.inspect, newvals.inspect] - @states[param].should = newvals - #self.should = newvals - return true - end - else - self[param] = value - end - } - - # Set the defaults again, just in case. - self.setdefaults - end - # For now, leave the 'name' method functioning like it used to. Once 'title' # works everywhere, I'll switch it. def name return self[:name] -# unless defined? @name and @name -# namevar = self.class.namevar -# if self.class.validparameter?(namevar) -# @name = self[:name] -# elsif self.class.validstate?(namevar) -# @name = self.should(namevar) -# else -# self.devfail "Could not find namevar %s for %s" % -# [namevar, self.class.name] -# end -# end -# -# unless @name -# self.devfail "Could not find namevar '%s' for %s" % -# [self.class.namevar, self.class.name] -# end -# -# return @name end # Retrieve the title of an object. If no title was set separately, @@ -1908,48 +353,6 @@ class Type < Puppet::Element return @title end - # fix any namevar => param translations - def argclean(oldhash) - # This duplication is here because it might be a transobject. - hash = oldhash.dup.to_hash - - if hash.include?(:parent) - hash.delete(:parent) - end - namevar = self.class.namevar - - # Do a simple translation for those cases where they've passed :name - # but that's not our namevar - if hash.include? :name and namevar != :name - if hash.include? namevar - raise ArgumentError, "Cannot provide both name and %s" % namevar - end - hash[namevar] = hash[:name] - hash.delete(:name) - end - - # Make sure we have a name, one way or another - unless hash.include? namevar - if defined? @title and @title - hash[namevar] = @title - else - raise Puppet::Error, - "Was not passed a namevar or title" - end - end - - return hash - end - - # retrieve the current value of all contained states - def retrieve - # it's important to use the method here, as it follows the order - # in which they're defined in the object - states().each { |state| - state.retrieve - } - end - # convert to a string def to_s self.title @@ -1983,658 +386,6 @@ class Type < Puppet::Element return trans end - # instance methods dealing with actually doing work - - public - - # this is a retarded hack method to get around the difference between - # component children and file children - def self.depthfirst? - if defined? @depthfirst - return @depthfirst - else - return false - end - end - - # Retrieve the changes associated with all of the states. - def statechanges - # If we are changing the existence of the object, then none of - # the other states matter. - changes = [] - if @states.include?(:ensure) and ! @states[:ensure].insync? - #self.info "ensuring %s from %s" % - # [@states[:ensure].should, @states[:ensure].is] - changes = [Puppet::StateChange.new(@states[:ensure])] - # Else, if the 'ensure' state is correctly absent, then do - # nothing - elsif @states.include?(:ensure) and @states[:ensure].is == :absent - #self.info "Object is correctly absent" - return [] - else - #if @states.include?(:ensure) - # self.info "ensure: Is: %s, Should: %s" % - # [@states[:ensure].is, @states[:ensure].should] - #else - # self.info "no ensure state" - #end - changes = states().find_all { |state| - ! state.insync? - }.collect { |state| - Puppet::StateChange.new(state) - } - end - - if Puppet[:debug] and changes.length > 0 - self.debug("Changing " + changes.collect { |ch| - ch.state.name - }.join(",") - ) - end - - changes - end - - # this method is responsible for collecting state changes - # we always descend into the children before we evaluate our current - # states - # this returns any changes resulting from testing, thus 'collect' - # rather than 'each' - def evaluate - now = Time.now - - #Puppet.err "Evaluating %s" % self.path.join(":") - unless defined? @evalcount - self.err "No evalcount defined on '%s' of type '%s'" % - [self.title,self.class] - @evalcount = 0 - end - @evalcount += 1 - - changes = [] - - # this only operates on states, not states + children - # it's important that we call retrieve() on the type instance, - # not directly on the state, because it allows the type to override - # the method, like pfile does - self.retrieve - - # states() is a private method, returning an ordered list - unless self.class.depthfirst? - changes += statechanges() - end - - changes << @children.collect { |child| - ch = child.evaluate - child.cache(:checked, now) - ch - } - - if self.class.depthfirst? - changes += statechanges() - end - - changes.flatten! - - # now record how many changes we've resulted in - if changes.length > 0 - self.debug "%s change(s)" % - [changes.length] - end - self.cache(:checked, now) - return changes.flatten - end - - # if all contained objects are in sync, then we're in sync - # FIXME I don't think this is used on the type instances any more, - # it's really only used for testing - def insync? - insync = true - - if state = @states[:ensure] - if state.insync? and state.should == :absent - return true - end - end - - states.each { |state| - unless state.insync? - state.debug("Not in sync: %s vs %s" % - [state.is.inspect, state.should.inspect]) - insync = false - #else - # state.debug("In sync") - end - } - - #self.debug("%s sync status is %s" % [self,insync]) - return insync - end - - # Meta-parameter methods: These methods deal with the results - # of specifying metaparameters - - def self.metaparams - @@metaparams.collect { |param| param.name } - end - - # Is the parameter in question a meta-parameter? - def self.metaparam?(param) - param = symbolize(param) - @@metaparamhash.include?(param) - end - - # Subscription and relationship methods - - #def addcallback(object, event, method) - # @callbacks[object][event] = method - #end - - # Build the dependencies associated with an individual object. - def builddepends - # Handle the requires - if self[:require] - self.handledepends(self[:require], :NONE, nil, true) - end - - # And the subscriptions - if self[:subscribe] - self.handledepends(self[:subscribe], :ALL_EVENTS, :refresh, true) - end - - if self[:notify] - self.handledepends(self[:notify], :ALL_EVENTS, :refresh, false) - end - - if self[:before] - self.handledepends(self[:before], :NONE, nil, false) - end - end - - # return all objects that we depend on - def eachdependency - Puppet::Event::Subscription.dependencies(self).each { |dep| - yield dep.source - } - end - - # return all objects subscribed to the current object - def eachsubscriber - Puppet::Event::Subscription.subscribers(self).each { |sub| - yield sub.target - } - end - - def handledepends(requires, event, method, up) - # Requires are specified in the form of [type, name], so they're always - # an array. But we want them to be an array of arrays. - unless requires[0].is_a?(Array) - requires = [requires] - end - requires.each { |rname| - # we just have a name and a type, and we need to convert it - # to an object... - type = nil - object = nil - tname = rname[0] - unless type = Puppet::Type.type(tname) - self.fail "Could not find type %s" % tname.inspect - end - name = rname[1] - unless object = type[name] - self.fail "Could not retrieve object '%s' of type '%s'" % - [name,type] - end - self.debug("subscribes to %s" % [object]) - - # Are we requiring them, or vice versa? - source = target = nil - if up - source = object - target = self - else - source = self - target = object - end - - # ok, both sides of the connection store some information - # we store the method to call when a given subscription is - # triggered, but the source object decides whether - subargs = { - :event => event, - :source => source, - :target => target - } - - if method and target.respond_to?(method) - subargs[:callback] = method - end - Puppet::Event::Subscription.new(subargs) - } - end - - def requires?(object) - req = false - self.eachdependency { |dep| - if dep == object - req = true - break - end - } - - return req - end - - def subscribe(hash) - hash[:source] = self - Puppet::Event::Subscription.new(hash) - - # add to the correct area - #@subscriptions.push sub - end - - def subscribesto?(object) - sub = false - self.eachsubscriber { |o| - if o == object - sub = true - break - end - } - - return sub - end - - # Unsubscribe from a given object, possibly with a specific event. - def unsubscribe(object, event = nil) - Puppet::Event::Subscription.dependencies(self).find_all { |sub| - if event - sub.match?(event) - else - sub.source == object - end - }.each { |sub| - sub.delete - } - end - - # we've received an event - # we only support local events right now, so we can pass actual - # objects around, including the transaction object - # the assumption here is that container objects will pass received - # methods on to contained objects - # i.e., we don't trigger our children, our refresh() method calls - # refresh() on our children - def trigger(event, source) - trans = event.transaction - if @callbacks.include?(source) - [:ALL_EVENTS, event.event].each { |eventname| - if method = @callbacks[source][eventname] - if trans.triggered?(self, method) > 0 - next - end - if self.respond_to?(method) - self.send(method) - end - - trans.triggered(self, method) - end - } - end - end - - # Documentation methods - def self.paramdoc(param) - @paramhash[param].doc - end - def self.metaparamdoc(metaparam) - @@metaparamhash[metaparam].doc - end - - # Add all of the meta parameters. - #newmetaparam(:onerror) do - # desc "How to handle errors -- roll back innermost - # transaction, roll back entire transaction, ignore, etc. Currently - # non-functional." - #end - - newmetaparam(:noop) do - desc "Boolean flag indicating whether work should actually - be done. *true*/**false**" - munge do |noop| - if noop == "true" or noop == true - return true - elsif noop == "false" or noop == false - return false - else - self.fail("Invalid noop value '%s'" % noop) - end - end - end - - newmetaparam(:schedule) do - desc "On what schedule the object should be managed. You must create a - schedule object, and then reference the name of that object to use - that for your schedule: - - schedule { daily: - period => daily, - range => \"2-4\" - } - - exec { \"/usr/bin/apt-get update\": - schedule => daily - } - - The creation of the schedule object does not need to appear in the - configuration before objects that use it." - - munge do |name| - if schedule = Puppet.type(:schedule)[name] - return schedule - else - return name - end - end - end - - newmetaparam(:check) do - desc "States which should have their values retrieved - but which should not actually be modified. This is currently used - internally, but will eventually be used for querying, so that you - could specify that you wanted to check the install state of all - packages, and then query the Puppet client daemon to get reports - on all packages." - - munge do |args| - # If they've specified all, collect all known states - if args == :all - args = @parent.class.states.collect do |state| - state.name - end - end - - unless args.is_a?(Array) - args = [args] - end - - unless defined? @parent - self.devfail "No parent for %s, %s?" % - [self.class, self.name] - end - - args.each { |state| - unless state.is_a?(Symbol) - state = state.intern - end - next if @parent.statedefined?(state) - - stateklass = @parent.class.validstate?(state) - - unless stateklass - raise Puppet::Error, "%s is not a valid attribute for %s" % - [state, self.class.name] - end - next unless stateklass.checkable? - - @parent.newstate(state) - } - end - end - # For each object we require, subscribe to all events that it generates. We - # might reduce the level of subscription eventually, but for now... - newmetaparam(:require) do - desc "One or more objects that this object depends on. - This is used purely for guaranteeing that changes to required objects - happen before the dependent object. For instance: - - # Create the destination directory before you copy things down - file { \"/usr/local/scripts\": - ensure => directory - } - - file { \"/usr/local/scripts/myscript\": - source => \"puppet://server/module/myscript\", - mode => 755, - require => file[\"/usr/local/scripts\"] - } - - Note that Puppet will autorequire everything that it can, and - there are hooks in place so that it's easy for elements to add new - ways to autorequire objects, so if you think Puppet could be - smarter here, let us know. - - In fact, the above code was redundant -- Puppet will autorequire - any parent directories that are being managed; it will - automatically realize that the parent directory should be created - before the script is pulled down. - - Currently, exec elements will autorequire their CWD (if it is - specified) plus any fully qualified paths that appear in the - command. For instance, if you had an ``exec`` command that ran - the ``myscript`` mentioned above, the above code that pulls the - file down would be automatically listed as a requirement to the - ``exec`` code, so that you would always be running againts the - most recent version. - " - - # Take whatever dependencies currently exist and add these. - # Note that this probably doesn't behave correctly with unsubscribe. - munge do |requires| - # We need to be two arrays deep... - unless requires.is_a?(Array) - requires = [requires] - end - unless requires[0].is_a?(Array) - requires = [requires] - end - if values = @parent[:require] - requires = values + requires - end - requires - end - end - - # For each object we require, subscribe to all events that it generates. - # We might reduce the level of subscription eventually, but for now... - newmetaparam(:subscribe) do - desc "One or more objects that this object depends on. Changes in the - subscribed to objects result in the dependent objects being - refreshed (e.g., a service will get restarted). For instance: - - class nagios { - file { \"/etc/nagios/nagios.conf\": - source => \"puppet://server/module/nagios.conf\", - alias => nagconf # just to make things easier for me - } - service { nagios: - running => true, - subscribe => file[nagconf] - } - } - " - - munge do |requires| - if values = @parent[:subscribe] - requires = values + requires - end - requires - # @parent.handledepends(requires, :ALL_EVENTS, :refresh) - end - end - - newmetaparam(:loglevel) do - desc "Sets the level that information will be logged. - The log levels have the biggest impact when logs are sent to - syslog (which is currently the default)." - defaultto :notice - - newvalues(*Puppet::Log.levels) - newvalues(:verbose) - - munge do |loglevel| - val = super(loglevel) - if val == :verbose - val = :info - end - val - end - end - - newmetaparam(:alias) do - desc "Creates an alias for the object. Puppet uses this internally when you - provide a symbolic name: - - file { sshdconfig: - path => $operatingsystem ? { - solaris => \"/usr/local/etc/ssh/sshd_config\", - default => \"/etc/ssh/sshd_config\" - }, - source => \"...\" - } - - service { sshd: - subscribe => file[sshdconfig] - } - - When you use this feature, the parser sets ``sshdconfig`` as the name, - and the library sets that as an alias for the file so the dependency - lookup for ``sshd`` works. You can use this parameter yourself, - but note that only the library can use these aliases; for instance, - the following code will not work: - - file { \"/etc/ssh/sshd_config\": - owner => root, - group => root, - alias => sshdconfig - } - - file { sshdconfig: - mode => 644 - } - - There's no way here for the Puppet parser to know that these two stanzas - should be affecting the same file. - - See the [language tutorial][] for more information. - - [language tutorial]: languagetutorial.html - - " - - munge do |aliases| - unless aliases.is_a?(Array) - aliases = [aliases] - end - @parent.info "Adding aliases %s" % aliases.collect { |a| - a.inspect - }.join(", ") - aliases.each do |other| - if obj = @parent.class[other] - unless obj == @parent - self.fail( - "%s can not create alias %s: object already exists" % - [@parent.title, other] - ) - end - next - end - @parent.class.alias(other, @parent) - end - end - end - - newmetaparam(:tag) do - desc "Add the specified tags to the associated element. While all elements - are automatically tagged with as much information as possible - (e.g., each class and component containing the element), it can - be useful to add your own tags to a given element. - - Tags are currently useful for things like applying a subset of a - host's configuration: - - puppetd --test --tag mytag - - This way, when you're testing a configuration you can run just the - portion you're testing." - - munge do |tags| - tags = [tags] unless tags.is_a? Array - - tags.each do |tag| - @parent.tag(tag) - end - end - end - - newmetaparam(:notify) do - desc %{This parameter is the opposite of **subscribe** -- it sends events - to the specified object: - - file { "/etc/sshd_config": - source => "....", - notify => service[sshd] - } - - service { sshd: - ensure => running - } - - This will restart the sshd service if the sshd config file changes.} - - - # Take whatever dependencies currently exist and add these. - munge do |notifies| - # We need to be two arrays deep... - unless notifies.is_a?(Array) - notifies = [notifies] - end - unless notifies[0].is_a?(Array) - notifies = [notifies] - end - if values = @parent[:notify] - notifies = values + notifies - end - notifies - end - - end - - newmetaparam(:before) do - desc %{This parameter is the opposite of **require** -- it guarantees - that the specified object is applied later than the specifying - object: - - file { "/var/nagios/configuration": - source => "...", - recurse => true, - before => exec["nagios-rebuid"] - } - - exec { "nagios-rebuild": - command => "/usr/bin/make", - cwd => "/var/nagios/configuration" - } - - This will make sure all of the files are up to date before the - make command is run.} - - # Take whatever dependencies currently exist and add these. - munge do |notifies| - # We need to be two arrays deep... - unless notifies.is_a?(Array) - notifies = [notifies] - end - unless notifies[0].is_a?(Array) - notifies = [notifies] - end - if values = @parent[:notify] - notifies = values + notifies - end - notifies - end - - end end # Puppet::Type end diff --git a/lib/puppet/util/variables.rb b/lib/puppet/util/variables.rb new file mode 100644 index 000000000..7e04cf1c0 --- /dev/null +++ b/lib/puppet/util/variables.rb @@ -0,0 +1,39 @@ +module Puppet::Util::Variables + def inithooks + @instance_init_hooks.dup + end + + def initvars + return unless defined? @class_init_hooks + self.inithooks.each do |var, value| + if value.is_a?(Class) + instance_variable_set("@" + var.to_s, value.new) + else + instance_variable_set("@" + var.to_s, value) + end + end + end + + def instancevar(hash) + @instance_init_hooks ||= {} + + unless method_defined?(:initvars) + define_method(:initvars) do + self.class.inithooks.each do |var, value| + if value.is_a?(Class) + instance_variable_set("@" + var.to_s, value.new) + else + instance_variable_set("@" + var.to_s, value) + end + end + end + end + hash.each do |var, value| + raise("Already initializing %s" % var) if @instance_init_hooks[var] + + @instance_init_hooks[var] = value + end + end +end + +# $Id$ |
