diff --git a/lib/puppet/provider/yumrepo/inifile.rb b/lib/puppet/provider/yumrepo/inifile.rb index 16f76e065..bffe9aad3 100644 --- a/lib/puppet/provider/yumrepo/inifile.rb +++ b/lib/puppet/provider/yumrepo/inifile.rb @@ -1,300 +1,298 @@ require 'puppet/util/inifile' Puppet::Type.type(:yumrepo).provide(:inifile) do desc <<-EOD Manage yum repo configurations by parsing yum INI configuration files. ## Fetching instances When fetching repo instances, directory entries in '/etc/yum/repos.d', '/etc/yum.repos.d', and the directory optionally specified by the reposdir key in '/etc/yum.conf' will be checked. If a given directory does not exist it will be ignored. In addition, all sections in '/etc/yum.conf' aside from 'main' will be created as sections. ## Storing instances When creating a new repository, a new section will be added in the first yum repo directory that exists. The custom directory specified by the '/etc/yum.conf' reposdir property is checked first, followed by '/etc/yum/repos.d', and then '/etc/yum.repos.d'. If none of these exist, the section will be created in '/etc/yum.conf'. EOD PROPERTIES = Puppet::Type.type(:yumrepo).validproperties # Retrieve all providers based on existing yum repositories # # @api public # @return [Array] providers generated from existing yum # repository definitions. def self.instances instances = [] virtual_inifile.each_section do |section| # Ignore the 'main' section in yum.conf since it's not a repository. next if section.name == "main" attributes_hash = {:name => section.name, :ensure => :present, :provider => :yumrepo} section.entries.each do |key, value| key = key.to_sym if valid_property?(key) attributes_hash[key] = value elsif key == :name attributes_hash[:descr] = value end end instances << new(attributes_hash) end instances end # Match catalog type instances to provider instances. # # @api public # @param resources [Array] Resources to prefetch. # @return [void] def self.prefetch(resources) repos = instances resources.keys.each do |name| if provider = repos.find { |repo| repo.name == name } resources[name].provider = provider end end end # Return a list of existing directories that could contain repo files. # # @api private # @param conf [String] Configuration file to look for directories in. # @param dirs [Array] Default locations for yum repos. # @return [Array] All present directories that may contain yum repo configs. def self.reposdir(conf='/etc/yum.conf', dirs=['/etc/yum.repos.d', '/etc/yum/repos.d']) reposdir = find_conf_value('reposdir', conf) dirs << reposdir if reposdir - # We can't use the below due to Ruby 1.8.7 - # dirs.select! { |dir| Puppet::FileSystem.exist?(dir) } - dirs.delete_if { |dir| ! Puppet::FileSystem.exist?(dir) } + dirs.select! { |dir| Puppet::FileSystem.exist?(dir) } if dirs.empty? Puppet.debug('No yum directories were found on the local filesystem') end dirs end # Used for testing only # @api private def self.clear @virtual = nil end # Helper method to look up specific values in ini style files. # # @api private # @param value [String] Value to look for in the configuration file. # @param conf [String] Configuration file to check for value. # @return [String] The value of a looked up key from the configuration file. def self.find_conf_value(value, conf='/etc/yum.conf') if Puppet::FileSystem.exist?(conf) file = Puppet::Util::IniConfig::PhysicalFile.new(conf) if (main = file.get_section('main')) main[value] end end end # Enumerate all files that may contain yum repository configs. # '/etc/yum.conf' is always included. # # @api private # @return [Array def self.repofiles files = ["/etc/yum.conf"] reposdir.each do |dir| Dir.glob("#{dir}/*.repo").each do |file| files << file end end files end # Build a virtual inifile by reading in numerous .repo files into a single # virtual file to ease manipulation. # @api private # @return [Puppet::Util::IniConfig::File] The virtual inifile representing # multiple real files. def self.virtual_inifile unless @virtual @virtual = Puppet::Util::IniConfig::File.new self.repofiles.each do |file| @virtual.read(file) if Puppet::FileSystem.file?(file) end end return @virtual end # Is the given key a valid type property? # # @api private # @param key [String] The property to look up. # @return [Boolean] Returns true if the property is defined in the type. def self.valid_property?(key) PROPERTIES.include?(key) end # Return an existing INI section or create a new section in the default location # # The default location is determined based on what yum repo directories # and files are present. If /etc/yum.conf has a value for 'reposdir' then that # is preferred. If no such INI property is found then the first default yum # repo directory that is present is used. If no default directories exist then # /etc/yum.conf is used. # # @param name [String] Section name to lookup in the virtual inifile. # @return [Puppet::Util::IniConfig] The IniConfig section def self.section(name) result = self.virtual_inifile[name] # Create a new section if not found. unless result dirs = reposdir() if dirs.empty? # If no repo directories are present, default to using yum.conf. path = '/etc/yum.conf' else # The ordering of reposdir is [defaults, custom], and we want to use # the custom directory if present. path = File.join(dirs.last, "#{name}.repo") end result = self.virtual_inifile.add_section(name, path) end result end # Save all yum repository files and force the mode to 0644 # @api private # @return [void] def self.store inifile = self.virtual_inifile inifile.store target_mode = 0644 inifile.each_file do |file| current_mode = Puppet::FileSystem.stat(file).mode & 0777 unless current_mode == target_mode Puppet.info "changing mode of #{file} from %03o to %03o" % [current_mode, target_mode] Puppet::FileSystem.chmod(target_mode, file) end end end # Create a new section for the given repository and set all the specified # properties in the section. # # @api public # @return [void] def create @property_hash[:ensure] = :present new_section = current_section # We fetch a list of properties from the type, then iterate # over them, avoiding ensure. We're relying on .should to # check if the property has been set and should be modified, # and if so we set it in the virtual inifile. PROPERTIES.each do |property| next if property == :ensure if value = @resource.should(property) self.send("#{property}=", value) end end end # Does the given repository already exist? # # @api public # @return [Boolean] def exists? @property_hash[:ensure] == :present end # Mark the given repository section for destruction. # # The actual removal of the section will be handled by {#flush} after the # resource has been fully evaluated. # # @api public # @return [void] def destroy # Flag file for deletion on flush. current_section.destroy=(true) @property_hash.clear end # Finalize the application of the given resource. # # @api public # @return [void] def flush self.class.store end # Generate setters and getters for our INI properties. PROPERTIES.each do |property| # The ensure property uses #create, #exists, and #destroy we can't generate # meaningful setters and getters for this next if property == :ensure define_method(property) do get_property(property) end define_method("#{property}=") do |value| set_property(property, value) end end # Map the yumrepo 'descr' type property to the 'name' INI property. def descr if ! @property_hash.has_key?(:descr) @property_hash[:descr] = current_section['name'] end value = @property_hash[:descr] value.nil? ? :absent : value end def descr=(value) value = (value == :absent ? nil : value) current_section['name'] = value @property_hash[:descr] = value end private def get_property(property) if ! @property_hash.has_key?(property) @property_hash[property] = current_section[property.to_s] end value = @property_hash[property] value.nil? ? :absent : value end def set_property(property, value) value = (value == :absent ? nil : value) current_section[property.to_s] = value @property_hash[property] = value end def section(name) self.class.section(name) end def current_section self.class.section(self.name) end end diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb index f57677c26..b9caaf247 100644 --- a/lib/puppet/transaction/resource_harness.rb +++ b/lib/puppet/transaction/resource_harness.rb @@ -1,251 +1,250 @@ require 'puppet/resource/status' class Puppet::Transaction::ResourceHarness NO_ACTION = Object.new extend Forwardable def_delegators :@transaction, :relationship_graph attr_reader :transaction def initialize(transaction) @transaction = transaction end def evaluate(resource) status = Puppet::Resource::Status.new(resource) begin context = ResourceApplicationContext.from_resource(resource, status) perform_changes(resource, context) if status.changed? && ! resource.noop? cache(resource, :synced, Time.now) resource.flush if resource.respond_to?(:flush) end rescue => detail status.failed_because(detail) ensure status.evaluation_time = Time.now - status.time end status end def scheduled?(resource) return true if Puppet[:ignoreschedules] return true unless schedule = schedule(resource) # We use 'checked' here instead of 'synced' because otherwise we'll # end up checking most resources 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). schedule.match?(cached(resource, :checked).to_i) end def schedule(resource) unless resource.catalog resource.warning "Cannot schedule without a schedule-containing catalog" return nil end return nil unless name = resource[:schedule] resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}") end # Used mostly for scheduling and auditing at this point. def cached(resource, name) Puppet::Util::Storage.cache(resource)[name] end # Used mostly for scheduling and auditing at this point. def cache(resource, name, value) Puppet::Util::Storage.cache(resource)[name] = value end private def perform_changes(resource, context) cache(resource, :checked, Time.now) return [] if ! allow_changes?(resource) # Record the current state in state.yml. context.audited_params.each do |param| cache(resource, param, context.current_values[param]) end ensure_param = resource.parameter(:ensure) if ensure_param && ensure_param.should ensure_event = sync_if_needed(ensure_param, context) else ensure_event = NO_ACTION end if ensure_event == NO_ACTION if context.resource_present? resource.properties.each do |param| sync_if_needed(param, context) end else resource.debug("Nothing to manage: no ensure and the resource doesn't exist") end end capture_audit_events(resource, context) end def allow_changes?(resource) if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \ and ! deps.empty? and deps.detect { |d| ! d.deleting? } deplabel = deps.collect { |r| r.ref }.join(",") plurality = deps.length > 1 ? "":"s" resource.warning "#{deplabel} still depend#{plurality} on me -- not purging" false else true end end def sync_if_needed(param, context) historical_value = context.historical_values[param.name] current_value = context.current_values[param.name] do_audit = context.audited_params.include?(param.name) begin if param.should && !param.safe_insync?(current_value) event = create_change_event(param, current_value, historical_value) if do_audit event = audit_event(event, param) end brief_audit_message = audit_message(param, do_audit, historical_value, current_value) if param.noop noop(event, param, current_value, brief_audit_message) else sync(event, param, current_value, brief_audit_message) end event else NO_ACTION end rescue => detail # Execution will continue on StandardErrors, just store the event Puppet.log_exception(detail) event = create_change_event(param, current_value, historical_value) event.status = "failure" event.message = "change from #{param.is_to_s(current_value)} to #{param.should_to_s(param.should)} failed: #{detail}" event rescue Exception => detail # Execution will halt on Exceptions, they get raised to the application event = create_change_event(param, current_value, historical_value) event.status = "failure" event.message = "change from #{param.is_to_s(current_value)} to #{param.should_to_s(param.should)} failed: #{detail}" raise ensure if event context.record(event) event.send_log context.synced_params << param.name end end end def create_change_event(property, current_value, historical_value) event = property.event event.previous_value = current_value event.desired_value = property.should event.historical_value = historical_value event end # This method is an ugly hack because, given a Time object with nanosecond # resolution, roundtripped through YAML serialization, the Time object will # be truncated to microseconds. # For audit purposes, this code special cases this comparison, and compares # the two objects by their second and microsecond components. tv_sec is the # number of seconds since the epoch, and tv_usec is only the microsecond - # portion of time. This compare satisfies compatibility requirements for - # Ruby 1.8.7, where to_r does not exist on the Time class. + # portion of time. def are_audited_values_equal(a, b) a == b || (a.is_a?(Time) && b.is_a?(Time) && a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec) end private :are_audited_values_equal def audit_event(event, property) event.audited = true event.status = "audit" if !are_audited_values_equal(event.historical_value, event.previous_value) event.message = "audit change: previously recorded value #{property.is_to_s(event.historical_value)} has been changed to #{property.is_to_s(event.previous_value)}" end event end def audit_message(param, do_audit, historical_value, current_value) if do_audit && historical_value && !are_audited_values_equal(historical_value, current_value) " (previously recorded value was #{param.is_to_s(historical_value)})" else "" end end def noop(event, param, current_value, audit_message) event.message = "current_value #{param.is_to_s(current_value)}, should be #{param.should_to_s(param.should)} (noop)#{audit_message}" event.status = "noop" end def sync(event, param, current_value, audit_message) param.sync event.message = "#{param.change_to_s(current_value, param.should)}#{audit_message}" event.status = "success" end def capture_audit_events(resource, context) context.audited_params.each do |param_name| if context.historical_values.include?(param_name) if !are_audited_values_equal(context.historical_values[param_name], context.current_values[param_name]) && !context.synced_params.include?(param_name) parameter = resource.parameter(param_name) event = audit_event(create_change_event(parameter, context.current_values[param_name], context.historical_values[param_name]), parameter) event.send_log context.record(event) end else resource.property(param_name).notice "audit change: newly-recorded value #{context.current_values[param_name]}" end end end # @api private ResourceApplicationContext = Struct.new(:resource, :current_values, :historical_values, :audited_params, :synced_params, :status) do def self.from_resource(resource, status) ResourceApplicationContext.new(resource, resource.retrieve_resource.to_hash, Puppet::Util::Storage.cache(resource).dup, (resource[:audit] || []).map { |p| p.to_sym }, [], status) end def resource_present? resource.present?(current_values) end def record(event) status << event end end end