diff --git a/lib/puppet/provider/yumrepo/inifile.rb b/lib/puppet/provider/yumrepo/inifile.rb index bffe9aad3..0abb3dc72 100644 --- a/lib/puppet/provider/yumrepo/inifile.rb +++ b/lib/puppet/provider/yumrepo/inifile.rb @@ -1,298 +1,301 @@ 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 + # Use directories in reposdir if they are set instead of default + dirs = reposdir.split(",").map(&:strip) if reposdir + 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) + file.read 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/spec/unit/provider/yumrepo/inifile_spec.rb b/spec/unit/provider/yumrepo/inifile_spec.rb index 0b526c30b..5fcc7faff 100644 --- a/spec/unit/provider/yumrepo/inifile_spec.rb +++ b/spec/unit/provider/yumrepo/inifile_spec.rb @@ -1,309 +1,310 @@ require 'spec_helper' describe Puppet::Type.type(:yumrepo).provider(:inifile) do after(:each) do described_class.clear end describe "enumerating all yum repo files" do it "reads all files in the directories specified by reposdir" do described_class.expects(:reposdir).returns ['/etc/yum.repos.d'] Dir.expects(:glob).with("/etc/yum.repos.d/*.repo").returns(['/etc/yum.repos.d/first.repo', '/etc/yum.repos.d/second.repo']) actual = described_class.repofiles expect(actual).to include("/etc/yum.repos.d/first.repo") expect(actual).to include("/etc/yum.repos.d/second.repo") end it "includes '/etc/yum.conf' as the first element" do described_class.expects(:reposdir).returns [] actual = described_class.repofiles expect(actual[0]).to eq "/etc/yum.conf" end end describe "generating the virtual inifile" do let(:files) { ['/etc/yum.repos.d/first.repo', '/etc/yum.repos.d/second.repo', '/etc/yum.conf'] } let(:collection) { mock('virtual inifile') } before do described_class.clear Puppet::Util::IniConfig::FileCollection.expects(:new).returns collection end it "reads all files in the directories specified by self.repofiles" do described_class.expects(:repofiles).returns(files) files.each do |file| Puppet::FileSystem.stubs(:file?).with(file).returns true collection.expects(:read).with(file) end described_class.virtual_inifile end it "ignores repofile entries that are not files" do described_class.expects(:repofiles).returns(files) Puppet::FileSystem.stubs(:file?).with('/etc/yum.repos.d/first.repo').returns true Puppet::FileSystem.stubs(:file?).with('/etc/yum.repos.d/second.repo').returns false Puppet::FileSystem.stubs(:file?).with('/etc/yum.conf').returns true collection.expects(:read).with('/etc/yum.repos.d/first.repo').once collection.expects(:read).with('/etc/yum.repos.d/second.repo').never collection.expects(:read).with('/etc/yum.conf').once described_class.virtual_inifile end end describe 'creating provider instances' do let(:virtual_inifile) { stub('virtual inifile') } before :each do described_class.stubs(:virtual_inifile).returns(virtual_inifile) end let(:main_section) do sect = Puppet::Util::IniConfig::Section.new('main', '/some/imaginary/file') sect.entries << ['distroverpkg', 'centos-release'] sect.entries << ['plugins', '1'] sect end let(:updates_section) do sect = Puppet::Util::IniConfig::Section.new('updates', '/some/imaginary/file') sect.entries << ['name', 'Some long description of the repo'] sect.entries << ['enabled', '1'] sect end it "ignores the main section" do virtual_inifile.expects(:each_section).multiple_yields(main_section, updates_section) instances = described_class.instances expect(instances).to have(1).items expect(instances[0].name).to eq 'updates' end it "creates provider instances for every non-main section that was found" do virtual_inifile.expects(:each_section).multiple_yields(main_section, updates_section) sect = described_class.instances.first expect(sect.name).to eq 'updates' expect(sect.descr).to eq 'Some long description of the repo' expect(sect.enabled).to eq '1' end end describe "retrieving a section from the inifile" do let(:collection) { stub('ini file collection') } let(:ini_section) { stub('ini file section') } before do described_class.stubs(:virtual_inifile).returns(collection) end describe "and the requested section exists" do before do collection.stubs(:[]).with('updates').returns ini_section end it "returns the existing section" do expect(described_class.section('updates')).to eq ini_section end it "doesn't create a new section" do collection.expects(:add_section).never described_class.section('updates') end end describe "and the requested section doesn't exist" do it "creates a section in the preferred repodir" do described_class.stubs(:reposdir).returns ['/etc/yum.repos.d', '/etc/alternate.repos.d'] collection.expects(:[]).with('updates') collection.expects(:add_section).with('updates', '/etc/alternate.repos.d/updates.repo') described_class.section('updates') end it "creates a section in yum.conf if no repodirs exist" do described_class.stubs(:reposdir).returns [] collection.expects(:[]).with('updates') collection.expects(:add_section).with('updates', '/etc/yum.conf') described_class.section('updates') end end end describe "setting and getting properties" do let(:type_instance) do Puppet::Type.type(:yumrepo).new( :name => 'puppetlabs-products', :ensure => :present, :baseurl => 'http://yum.puppetlabs.com/el/6/products/$basearch', :descr => 'Puppet Labs Products El 6 - $basearch', :enabled => '1', :gpgcheck => '1', :gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs' ) end let(:provider) do described_class.new(type_instance) end let(:section) do stub('inifile puppetlabs section', :name => 'puppetlabs-products') end before do type_instance.provider = provider described_class.stubs(:section).with('puppetlabs-products').returns(section) end describe "methods used by ensurable" do it "#create sets the yumrepo properties on the according section" do section.expects(:[]=).with('baseurl', 'http://yum.puppetlabs.com/el/6/products/$basearch') section.expects(:[]=).with('name', 'Puppet Labs Products El 6 - $basearch') section.expects(:[]=).with('enabled', '1') section.expects(:[]=).with('gpgcheck', '1') section.expects(:[]=).with('gpgkey', 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs') provider.create end it "#exists? checks if the repo has been marked as present" do described_class.stubs(:section).returns(stub(:[]= => nil)) provider.create expect(provider).to be_exist end it "#destroy deletes the associated ini file section" do described_class.expects(:section).returns(section) section.expects(:destroy=).with(true) provider.destroy end end describe "getting properties" do it "maps the 'descr' property to the 'name' INI property" do section.expects(:[]).with('name').returns 'Some rather long description of the repository' expect(provider.descr).to eq 'Some rather long description of the repository' end it "gets the property from the INI section" do section.expects(:[]).with('enabled').returns '1' expect(provider.enabled).to eq '1' end it "sets the property as :absent if the INI property is nil" do section.expects(:[]).with('exclude').returns nil expect(provider.exclude).to eq :absent end end describe "setting properties" do it "maps the 'descr' property to the 'name' INI property" do section.expects(:[]=).with('name', 'Some rather long description of the repository') provider.descr = 'Some rather long description of the repository' end it "sets the property on the INI section" do section.expects(:[]=).with('enabled', '0') provider.enabled = '0' end it "sets the section field to nil when the specified value is absent" do section.expects(:[]=).with('exclude', nil) provider.exclude = :absent end end end describe 'reposdir' do let(:defaults) { ['/etc/yum.repos.d', '/etc/yum/repos.d'] } before do Puppet::FileSystem.stubs(:exist?).with('/etc/yum.repos.d').returns(true) Puppet::FileSystem.stubs(:exist?).with('/etc/yum/repos.d').returns(true) end it "returns the default directories if yum.conf doesn't contain a `reposdir` entry" do described_class.stubs(:find_conf_value).with('reposdir', '/etc/yum.conf') expect(described_class.reposdir('/etc/yum.conf')).to eq(defaults) end it "includes the directory specified by the yum.conf 'reposdir' entry when the directory is present" do Puppet::FileSystem.expects(:exist?).with("/etc/yum/extra.repos.d").returns(true) described_class.expects(:find_conf_value).with('reposdir', '/etc/yum.conf').returns "/etc/yum/extra.repos.d" expect(described_class.reposdir('/etc/yum.conf')).to include("/etc/yum/extra.repos.d") end it "doesn't include the directory specified by the yum.conf 'reposdir' entry when the directory is absent" do Puppet::FileSystem.expects(:exist?).with("/etc/yum/extra.repos.d").returns(false) described_class.expects(:find_conf_value).with('reposdir', '/etc/yum.conf').returns "/etc/yum/extra.repos.d" expect(described_class.reposdir('/etc/yum.conf')).not_to include("/etc/yum/extra.repos.d") end it "logs a warning and returns an empty array if none of the specified repo directories exist" do Puppet::FileSystem.unstub(:exist?) Puppet::FileSystem.stubs(:exist?).returns false described_class.stubs(:find_conf_value).with('reposdir', '/etc/yum.conf') Puppet.expects(:debug).with('No yum directories were found on the local filesystem') expect(described_class.reposdir('/etc/yum.conf')).to be_empty end end describe "looking up a conf value" do describe "and the file doesn't exist" do it "returns nil" do Puppet::FileSystem.stubs(:exist?).returns false expect(described_class.find_conf_value('reposdir')).to be_nil end end describe "and the file exists" do let(:pfile) { stub('yum.conf physical file') } let(:sect) { stub('ini section') } before do Puppet::FileSystem.stubs(:exist?).with('/etc/yum.conf').returns true Puppet::Util::IniConfig::PhysicalFile.stubs(:new).with('/etc/yum.conf').returns pfile + pfile.expects(:read) end it "creates a PhysicalFile to parse the given file" do pfile.expects(:get_section) described_class.find_conf_value('reposdir') end it "returns nil if the file exists but the 'main' section doesn't exist" do pfile.expects(:get_section).with('main') expect(described_class.find_conf_value('reposdir')).to be_nil end it "returns nil if the file exists but the INI property doesn't exist" do pfile.expects(:get_section).with('main').returns sect sect.expects(:[]).with('reposdir') expect(described_class.find_conf_value('reposdir')).to be_nil end it "returns the value if the value is defined in the PhysicalFile" do pfile.expects(:get_section).with('main').returns sect sect.expects(:[]).with('reposdir').returns '/etc/alternate.repos.d' expect(described_class.find_conf_value('reposdir')).to eq '/etc/alternate.repos.d' end end end end