diff --git a/lib/puppet/provider/yumrepo/inifile.rb b/lib/puppet/provider/yumrepo/inifile.rb new file mode 100644 index 000000000..6115ce836 --- /dev/null +++ b/lib/puppet/provider/yumrepo/inifile.rb @@ -0,0 +1,153 @@ +require 'puppet/util/inifile' + +Puppet::Type.type(:yumrepo).provide(:inifile) do + desc 'Manage yum repos' + + PROPERTIES = Puppet::Type.type(:yumrepo).validproperties + + def self.instances + instances = [] + # Iterate over each section of our virtual file. + virtual_inifile.each_section do |section| + attributes_hash = {:name => section.name, :ensure => :present, :provider => :yumrepo} + # We need to build up a attributes hash + section.entries.each do |key, value| + key = key.to_sym + if valid_property?(key) + # We strip the values here to handle cases where distros set values + # like enabled = 1 with spaces. + attributes_hash[key] = value.strip + end + end + instances << new(attributes_hash) + end + return instances + end + + 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 + + # Search for a reposdir in the yum configuration file and append it to the + # list of repodirs to use. + 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 + + return dirs + end + + # Find configuration values in .conf files and return them + # if found. + def self.find_conf_value(value, conf='/etc/yum.conf') + if File.exists?(conf) + contents = File.read(conf) + match = /^#{value}\s*=\s*(.*)/.match(contents) + end + + return match.captures[0] if match + end + + # Build a virtual inifile by reading in numerous .repo + # files into a single virtual file to ease manipulation. + def self.virtual_inifile + unless @virtual + @virtual = Puppet::Util::IniConfig::File.new + reposdir.each do |dir| + Dir.glob("#{dir}/*.repo").each do |file| + @virtual.read(file) if ::File.file?(file) + end + end + end + return @virtual + end + + def self.valid_property?(key) + PROPERTIES.include?(key) + end + + # Return the named section out of the virtual_inifile. + def self.section(name) + result = self.virtual_inifile[name] + # Create a new section if not found. + unless result + reposdir.each do |dir| + if File.directory?(dir) + path = ::File.join(dir, "#{name}.repo") + Puppet.info("create new repo #{name} in file #{path}") + result = self.virtual_inifile.add_section(name, path) + end + end + end + result + end + + # Store all modifications back to disk + def self.store + inifile = self.virtual_inifile + inifile.store + unless Puppet[:noop] + 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] + ::File.chmod(target_mode, file) + end + end + end + end + + def create + @property_hash[:ensure] = :present + + # 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) + section(@resource[:name])[property.to_s] = value + @property_hash[property] = value + end + end + end + + def destroy + # Flag file for deletion on flush. + section(@resource[:name]).destroy=(true) + + @property_hash.clear + end + + def flush + self.class.store + end + + def section(name) + self.class.section(name) + end + + # Create all of our setters. + mk_resource_methods + PROPERTIES.each do |property| + # Exclude ensure, as we don't need to create an ensure= + next if property == :ensure + # Builds the property= method. + define_method("#{property.to_s}=") do |value| + section(@property_hash[:name])[property.to_s] = value + @property_hash[property] = value + end + end + + def exists? + @property_hash[:ensure] == :present + end + +end diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb index 8bf1b44fc..d44de1f13 100644 --- a/lib/puppet/type/yumrepo.rb +++ b/lib/puppet/type/yumrepo.rb @@ -1,395 +1,270 @@ -require 'puppet/util/inifile' - -module Puppet - # A property for one entry in a .ini-style file - class IniProperty < Puppet::Property - def insync?(is) - # A should property of :absent is the same as nil - if is.nil? && should == :absent - return true - end - super(is) - end +require 'uri' - def sync - if safe_insync?(retrieve) - result = nil - else - result = set(self.should) - if should == :absent - resource.section[inikey] = nil - else - resource.section[inikey] = should - end - end - result - end +Puppet::Type.newtype(:yumrepo) do + @doc = "The client-side description of a yum repository. Repository + configurations are found by parsing `/etc/yum.conf` and + the files indicated by the `reposdir` option in that file + (see `yum.conf(5)` for details). - def retrieve - resource.section[inikey] - end + Most parameters are identical to the ones documented + in the `yum.conf(5)` man page. - def inikey - name.to_s - end + Continuation lines that yum supports (for the `baseurl`, for example) + are not supported. This type does not attempt to read or verify the + exinstence of files listed in the `include` attribute." - # Set the key associated with this property to KEY, instead - # of using the property's NAME - def self.inikey(key) - # Override the inikey instance method - # Is there a way to do this without resorting to strings ? - # Using a block fails because the block can't access - # the variable 'key' in the outer scope - self.class_eval("def inikey ; \"#{key.to_s}\" ; end") - end + # Ensure yumrepos can be removed too. + ensurable + # Doc string for properties that can be made 'absent' + ABSENT_DOC="Set this to `absent` to remove it from the file completely." + # False can be false/0/no and True can be true/1/yes in yum. + YUM_BOOLEAN=/(True|False|0|1|No|Yes)/ + YUM_BOOLEAN_DOC="Valid values are: False/0/No or True/1/Yes." + newparam(:name, :namevar => true) do + desc "The name of the repository. This corresponds to the + `repositoryid` parameter in `yum.conf(5)`." end - # Doc string for properties that can be made 'absent' - ABSENT_DOC="Set this to `absent` to remove it from the file completely." + newparam(:target) do + desc "The filename to write the yum repository to." - newtype(:yumrepo) do - @doc = "The client-side description of a yum repository. Repository - configurations are found by parsing `/etc/yum.conf` and - the files indicated by the `reposdir` option in that file - (see `yum.conf(5)` for details). - - Most parameters are identical to the ones documented - in the `yum.conf(5)` man page. - - Continuation lines that yum supports (for the `baseurl`, for example) - are not supported. This type does not attempt to read or verify the - exinstence of files listed in the `include` attribute." - - class << self - attr_accessor :filetype - # The writer is only used for testing, there should be no need - # to change yumconf or inifile in any other context - attr_accessor :yumconf - attr_writer :inifile - end + defaultto :absent + end - self.filetype = Puppet::Util::FileType.filetype(:flat) - - @inifile = nil - - @yumconf = "/etc/yum.conf" - - # Where to put files for brand new sections - @defaultrepodir = nil - - def self.instances - l = [] - check = validproperties - clear - inifile.each_section do |s| - next if s.name == "main" - obj = new(:name => s.name, :audit => check) - current_values = obj.retrieve - obj.eachproperty do |property| - if current_values[property].nil? - obj.delete(property.name) - else - property.should = current_values[property] - end - end - obj.delete(:audit) - l << obj - end - l - end + newproperty(:descr) do + desc "A human-readable description of the repository. + This corresponds to the name parameter in `yum.conf(5)`. + #{ABSENT_DOC}" - # Return the Puppet::Util::IniConfig::File for the whole yum config - def self.inifile - if @inifile.nil? - @inifile = read - main = @inifile['main'] - raise Puppet::Error, "File #{yumconf} does not contain a main section" if main.nil? - reposdir = main['reposdir'] - reposdir ||= "/etc/yum.repos.d, /etc/yum/repos.d" - reposdir.gsub!(/[\n,]/, " ") - reposdir.split.each do |dir| - Dir::glob("#{dir}/*.repo").each do |file| - @inifile.read(file) if ::File.file?(file) - end - end - reposdir.split.each do |dir| - if ::File.directory?(dir) && ::File.writable?(dir) - @defaultrepodir = dir - break - end - end - end - @inifile - end + newvalues(/.*/, :absent) + end - # Parse the yum config files. Only exposed for the tests - # Non-test code should use self.inifile to get at the - # underlying file - def self.read - result = Puppet::Util::IniConfig::File.new - result.read(yumconf) - main = result['main'] - raise Puppet::Error, "File #{yumconf} does not contain a main section" if main.nil? - reposdir = main['reposdir'] - reposdir ||= "/etc/yum.repos.d, /etc/yum/repos.d" - reposdir.gsub!(/[\n,]/, " ") - reposdir.split.each do |dir| - Dir::glob("#{dir}/*.repo").each do |file| - result.read(file) if ::File.file?(file) - end - end - if @defaultrepodir.nil? - reposdir.split.each do |dir| - if ::File.directory?(dir) && ::File.writable?(dir) - @defaultrepodir = dir - break - end - end - end - result - end + newproperty(:mirrorlist) do + desc "The URL that holds the list of mirrors for this repository. + #{ABSENT_DOC}" - # Return the Puppet::Util::IniConfig::Section with name NAME - # from the yum config - def self.section(name) - result = inifile[name] - if result.nil? - # Brand new section - path = yumconf - path = ::File.join(@defaultrepodir, "#{name}.repo") unless @defaultrepodir.nil? - Puppet::info "create new repo #{name} in file #{path}" - result = inifile.add_section(name, path) - end - result + newvalues(/.*/, :absent) + validate do |value| + parsed = URI.parse(value) + fail("Must be a valid URL") unless ['file', 'http'].include?(parsed.scheme) end + end - # Store all modifications back to disk - def self.store - inifile.store - unless Puppet[:noop] - target_mode = 0644 # FIXME: should be configurable - 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] - ::File.chmod(target_mode, file) - end - end - end - end + newproperty(:baseurl) do + desc "The URL for this repository. #{ABSENT_DOC}" - # This is only used during testing. - def self.clear - @inifile = nil - @yumconf = "/etc/yum.conf" - @defaultrepodir = nil + newvalues(/.*/, :absent) + validate do |value| + parsed = URI.parse(value) + fail("Must be a valid URL") unless ['file', 'http'].include?(parsed.scheme) end + end - # Return the Puppet::Util::IniConfig::Section for this yumrepo resource - def section - self.class.section(self[:name]) - end + newproperty(:enabled) do + desc "Whether this repository is enabled. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" - # Store modifications to this yumrepo resource back to disk - def flush - self.class.store - end + newvalues(YUM_BOOLEAN, :absent) + end - newparam(:name) do - desc "The name of the repository. This corresponds to the - `repositoryid` parameter in `yum.conf(5)`." - isnamevar - end + newproperty(:gpgcheck) do + desc "Whether to check the GPG signature on packages installed + from this repository. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" - newproperty(:descr, :parent => Puppet::IniProperty) do - desc "A human-readable description of the repository. - This corresponds to the name parameter in `yum.conf(5)`. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } - inikey "name" - end + newvalues(YUM_BOOLEAN, :absent) + end - newproperty(:mirrorlist, :parent => Puppet::IniProperty) do - desc "The URL that holds the list of mirrors for this repository. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - # Should really check that it's a valid URL - newvalue(/.*/) { } - end + newproperty(:repo_gpgcheck) do + desc "Whether to check the GPG signature on repodata. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" - newproperty(:baseurl, :parent => Puppet::IniProperty) do - desc "The URL for this repository. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - # Should really check that it's a valid URL - newvalue(/.*/) { } - end + newvalues(YUM_BOOLEAN, :absent) + end - newproperty(:enabled, :parent => Puppet::IniProperty) do - desc "Whether this repository is enabled, as represented by a - `0` or `1`. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/^(0|1)$/) { } - end + newproperty(:gpgkey) do + desc "The URL for the GPG key with which packages from this + repository are signed. #{ABSENT_DOC}" - newproperty(:gpgcheck, :parent => Puppet::IniProperty) do - desc "Whether to check the GPG signature on packages installed - from this repository, as represented by a `0` or `1`. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/^(0|1)$/) { } + newvalues(/.*/, :absent) + validate do |value| + parsed = URI.parse(value) + fail("Must be a valid URL") unless ['file', 'http'].include?(parsed.scheme) end + end - newproperty(:gpgkey, :parent => Puppet::IniProperty) do - desc "The URL for the GPG key with which packages from this - repository are signed. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - # Should really check that it's a valid URL - newvalue(/.*/) { } - end + newproperty(:include) do + desc "The URL of a remote file containing additional yum configuration + settings. Puppet does not check for this file's existence or validity. + #{ABSENT_DOC}" - newproperty(:include, :parent => Puppet::IniProperty) do - desc "The URL of a remote file containing additional yum configuration - settings. Puppet does not check for this file's existence or validity. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - # Should really check that it's a valid URL - newvalue(/.*/) { } + newvalues(/.*/, :absent) + validate do |value| + parsed = URI.parse(value) + fail("Must be a valid URL") unless ['file', 'http'].include?(parsed.scheme) end + end - newproperty(:exclude, :parent => Puppet::IniProperty) do - desc "List of shell globs. Matching packages will never be - considered in updates or installs for this repo. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } - end + newproperty(:exclude) do + desc "List of shell globs. Matching packages will never be + considered in updates or installs for this repo. + #{ABSENT_DOC}" - newproperty(:includepkgs, :parent => Puppet::IniProperty) do - desc "List of shell globs. If this is set, only packages - matching one of the globs will be considered for - update or install from this repo. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } - end + newvalues(/.*/, :absent) + end - newproperty(:enablegroups, :parent => Puppet::IniProperty) do - desc "Whether yum will allow the use of package groups for this - repository, as represented by a `0` or `1`. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/^(0|1)$/) { } - end + newproperty(:includepkgs) do + desc "List of shell globs. If this is set, only packages + matching one of the globs will be considered for + update or install from this repo. #{ABSENT_DOC}" - newproperty(:failovermethod, :parent => Puppet::IniProperty) do - desc "The failover methode for this repository; should be either - `roundrobin` or `priority`. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r{roundrobin|priority}) { } - end + newvalues(/.*/, :absent) + end - newproperty(:keepalive, :parent => Puppet::IniProperty) do - desc "Whether HTTP/1.1 keepalive should be used with this repository, as - represented by a `0` or `1`. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/^(0|1)$/) { } - end + newproperty(:enablegroups) do + desc "Whether yum will allow the use of package groups for this + repository. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" - newproperty(:http_caching, :parent => Puppet::IniProperty) do - desc "What to cache from this repository. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r(packages|all|none)) { } - end - - newproperty(:timeout, :parent => Puppet::IniProperty) do - desc "Number of seconds to wait for a connection before timing - out. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r{[0-9]+}) { } - end + newvalues(YUM_BOOLEAN, :absent) + end - newproperty(:metadata_expire, :parent => Puppet::IniProperty) do - desc "Number of seconds after which the metadata will expire. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r{[0-9]+}) { } - end + newproperty(:failovermethod) do + desc "The failover method for this repository; should be either + `roundrobin` or `priority`. #{ABSENT_DOC}" - newproperty(:protect, :parent => Puppet::IniProperty) do - desc "Enable or disable protection for this repository. Requires - that the `protectbase` plugin is installed and enabled. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/^(0|1)$/) { } - end + newvalues(/roundrobin|priority/, :absent) + end - newproperty(:priority, :parent => Puppet::IniProperty) do - desc "Priority of this repository from 1-99. Requires that - the `priorities` plugin is installed and enabled. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r{[1-9][0-9]?}) { } - end + newproperty(:keepalive) do + desc "Whether HTTP/1.1 keepalive should be used with this repository. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" - newproperty(:cost, :parent => Puppet::IniProperty) do - desc "Cost of this repository. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r{\d+}) { } - end + newvalues(YUM_BOOLEAN, :absent) + end - newproperty(:proxy, :parent => Puppet::IniProperty) do - desc "URL to the proxy server for this repository. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - # Should really check that it's a valid URL - newvalue(/.*/) { } - end + newproperty(:http_caching) do + desc "What to cache from this repository. #{ABSENT_DOC}" - newproperty(:proxy_username, :parent => Puppet::IniProperty) do - desc "Username for this proxy. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } - end + newvalues(/(packages|all|none)/, :absent) + end - newproperty(:proxy_password, :parent => Puppet::IniProperty) do - desc "Password for this proxy. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } - end + newproperty(:timeout) do + desc "Number of seconds to wait for a connection before timing + out. #{ABSENT_DOC}" - newproperty(:s3_enabled, :parent => Puppet::IniProperty) do - desc "Access the repo via S3. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/^(0|1)$/) { } - end + newvalues(/[0-9]+/, :absent) + end - newproperty(:sslcacert, :parent => Puppet::IniProperty) do - desc "Path to the directory containing the databases of the - certificate authorities yum should use to verify SSL certificates. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } - end + newproperty(:metadata_expire) do + desc "Number of seconds after which the metadata will expire. + #{ABSENT_DOC}" - newproperty(:sslverify, :parent => Puppet::IniProperty) do - desc "Should yum verify SSL certificates/hosts at all. - Possible values are 'True' or 'False'. - #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(%r(True|False)) { } + newvalues(/[0-9]+/, :absent) + end + + newproperty(:protect) do + desc "Enable or disable protection for this repository. Requires + that the `protectbase` plugin is installed and enabled. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" + + newvalues(YUM_BOOLEAN, :absent) + end + + newproperty(:priority) do + desc "Priority of this repository from 1-99. Requires that + the `priorities` plugin is installed and enabled. + #{ABSENT_DOC}" + + newvalues(/.*/, :absent) + validate do |value| + unless value == :absent or (1..99).include?(value.to_i) + fail("Must be within range 1-99") + end end + end + + newproperty(:cost) do + desc "Cost of this repository. #{ABSENT_DOC}" + + newvalues(/\d+/, :absent) + end + + newproperty(:proxy) do + desc "URL to the proxy server for this repository. #{ABSENT_DOC}" - newproperty(:sslclientcert, :parent => Puppet::IniProperty) do - desc "Path to the SSL client certificate yum should use to connect - to repos/remote sites. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } + newvalues(/.*/, :absent) + validate do |value| + parsed = URI.parse(value) + fail("Must be a valid URL") unless ['file', 'http'].include?(parsed.scheme) end + end + + newproperty(:proxy_username) do + desc "Username for this proxy. #{ABSENT_DOC}" + + newvalues(/.*/, :absent) + end + + newproperty(:proxy_password) do + desc "Password for this proxy. #{ABSENT_DOC}" + + newvalues(/.*/, :absent) + end + + newproperty(:s3_enabled) do + desc "Access the repo via S3. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" + + newvalues(YUM_BOOLEAN, :absent) + end + + newproperty(:sslcacert) do + desc "Path to the directory containing the databases of the + certificate authorities yum should use to verify SSL certificates. + #{ABSENT_DOC}" + + newvalues(/.*/, :absent) + end + + newproperty(:sslverify) do + desc "Should yum verify SSL certificates/hosts at all. + #{YUM_BOOLEAN_DOC} + #{ABSENT_DOC}" + + newvalues(YUM_BOOLEAN, :absent) + end + + newproperty(:sslclientcert) do + desc "Path to the SSL client certificate yum should use to connect + to repos/remote sites. #{ABSENT_DOC}" + + newvalues(/.*/, :absent) + end + + newproperty(:sslclientkey) do + desc "Path to the SSL client key yum should use to connect + to repos/remote sites. #{ABSENT_DOC}" + + newvalues(/.*/, :absent) + end + + newproperty(:metalink) do + desc "Metalink for mirrors. #{ABSENT_DOC}" - newproperty(:sslclientkey, :parent => Puppet::IniProperty) do - desc "Path to the SSL client key yum should use to connect - to repos/remote sites. #{ABSENT_DOC}" - newvalue(:absent) { self.should = :absent } - newvalue(/.*/) { } + newvalues(/.*/, :absent) + validate do |value| + parsed = URI.parse(value) + fail("Must be a valid URL") unless ['file', 'http'].include?(parsed.scheme) end end + end diff --git a/spec/unit/provider/yumrepo/inifile_spec.rb b/spec/unit/provider/yumrepo/inifile_spec.rb new file mode 100644 index 000000000..d362193bc --- /dev/null +++ b/spec/unit/provider/yumrepo/inifile_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' +require 'puppet' + +describe Puppet::Type.type(:yumrepo).provider(:inifile) do + let(:yumrepo) { + 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' + ) + } + let(:yumrepo_provider) { yumrepo.provider } + let(:repo_file) { ' +[updates] +name="updates" +enabled = 1 +descr="test updates" +' + } + + before :each do + Dir.stubs(:glob).with('/etc/yum.repos.d/*.repo').returns(['/etc/yum.repos.d/test.repo']) + end + + describe 'self.instances' do + before :each do + described_class.stubs(:reposdir).returns(['/etc/yum.repos.d']) + File.expects(:file?).with('/etc/yum.repos.d/test.repo').returns(true) + File.expects(:exist?).with(Pathname.new('/etc/yum.repos.d/test.repo')).returns(true) + File.expects(:read).with('/etc/yum.repos.d/test.repo').returns(repo_file) + end + + it 'finds the update repo' do + providers = yumrepo_provider.class.instances + providers.count.should == 1 + providers[0].name.should == 'updates' + providers[0].enabled.should == '1' + end + end + + describe 'create' do + it 'creates a yumrepo' do + yumrepo_provider.section('puppetlabs-products').expects(:[]=).at_least(1) + yumrepo_provider.create + end + end + + describe 'destroy' do + it 'flags the section to be destroyed' do + yumrepo_provider.section('puppetlabs-products').expects(:destroy=).with(true) + yumrepo_provider.destroy + end + end + + describe 'exists?' do + it 'checks if yumrepo exists' do + described_class.stubs(:reposdir).returns(['/etc/yum.repos.d']) + yumrepo_provider.ensure= :present + yumrepo_provider.exists?.should be_true + end + end + + describe 'reposdir' do + let(:defaults) { ['/etc/yum.repos.d', '/etc/yum/repos.d'] } + let(:all) { ['/etc/yum.repos.d', '/etc/yum/repos.d', '/etc/yum/test'] } + + it 'returns defaults if no yum conf' do + File.expects(:exists?).with('/etc/yum.conf').returns(false) + + described_class.reposdir('/etc/yum.conf').should == defaults + end + + it 'returns defaults if yumconf has no reposdir' do + File.expects(:exists?).with('/etc/yum.conf').returns(true) + File.expects(:read).with('/etc/yum.conf').returns("[main]\ntest = /etc/yum/test") + + described_class.reposdir('/etc/yum.conf').should == defaults + end + + it 'returns all directories if yum.conf contains reposdir' do + File.expects(:exists?).with('/etc/yum.conf').returns(true) + File.expects(:read).with('/etc/yum.conf').returns("[main]\nreposdir = /etc/yum/test") + + described_class.reposdir('/etc/yum.conf').should == all + end + + end + + +end diff --git a/spec/unit/type/yumrepo_spec.rb b/spec/unit/type/yumrepo_spec.rb index 66ddfd3e9..8547989c5 100644 --- a/spec/unit/type/yumrepo_spec.rb +++ b/spec/unit/type/yumrepo_spec.rb @@ -1,223 +1,65 @@ -#! /usr/bin/env ruby - require 'spec_helper' +require 'puppet' describe Puppet::Type.type(:yumrepo) do - include PuppetSpec::Files + let(:yumrepo) { + Puppet::Type.type(:yumrepo).new( + :name => "puppetlabs" + ) + } describe "When validating attributes" do it "should have a 'name' parameter'" do - Puppet::Type.type(:yumrepo).new(:name => "puppetlabs")[:name].should == "puppetlabs" + yumrepo[:name].should == "puppetlabs" end - [:baseurl, :cost, :descr, :enabled, :enablegroups, :exclude, :failovermethod, :gpgcheck, :gpgkey, :http_caching, - :include, :includepkgs, :keepalive, :metadata_expire, :mirrorlist, :priority, :protect, :proxy, :proxy_username, :proxy_password, :timeout, - :sslcacert, :sslverify, :sslclientcert, :sslclientkey, :s3_enabled].each do |param| + [:baseurl, :cost, :descr, :enabled, :enablegroups, :exclude, :failovermethod, + :gpgcheck, :repo_gpgcheck, :gpgkey, :http_caching, :include, :includepkgs, :keepalive, + :metadata_expire, :mirrorlist, :priority, :protect, :proxy, :proxy_username, + :proxy_password, :timeout, :sslcacert, :sslverify, :sslclientcert, + :sslclientkey, :s3_enabled, :metalink].each do |param| it "should have a '#{param}' parameter" do Puppet::Type.type(:yumrepo).attrtype(param).should == :property end end end describe "When validating attribute values" do - [:cost, :enabled, :enablegroups, :failovermethod, :gpgcheck, :http_caching, :keepalive, :metadata_expire, :priority, :protect, :timeout].each do |param| + [:cost, :enabled, :enablegroups, :failovermethod, :gpgcheck, :repo_gpgcheck, :http_caching, + :keepalive, :metadata_expire, :priority, :protect, :timeout].each do |param| it "should support :absent as a value to '#{param}' parameter" do - Puppet::Type.type(:yumrepo).new(:name => "puppetlabs.repo", param => :absent) + Puppet::Type.type(:yumrepo).new(:name => 'puppetlabs', param => :absent) end end - [:cost, :enabled, :enablegroups, :gpgcheck, :keepalive, :metadata_expire, :priority, :protect, :timeout].each do |param| - it "should fail if '#{param}' is not a number" do - lambda { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", param => "notanumber") }.should raise_error + [:cost, :enabled, :enablegroups, :gpgcheck, :repo_gpgcheck, :keepalive, :metadata_expire, + :priority, :protect, :timeout].each do |param| + it "should fail if '#{param}' is not true/false, 0/1, or yes/no" do + expect { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", param => "notanumber") }.to raise_error end end - [:enabled, :enabledgroups, :gpgcheck, :keepalive, :protect, :s3_enabled].each do |param| + [:enabled, :enabledgroups, :gpgcheck, :repo_gpgcheck, :keepalive, :protect, :s3_enabled].each do |param| it "should fail if '#{param}' does not have one of the following values (0|1)" do - lambda { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", param => "2") }.should raise_error + expect { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", param => "2") }.to raise_error end end it "should fail if 'failovermethod' does not have one of the following values (roundrobin|priority)" do - lambda { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :failovermethod => "notavalidvalue") }.should raise_error + expect { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :failovermethod => "notavalidvalue") }.to raise_error end it "should fail if 'http_caching' does not have one of the following values (packages|all|none)" do - lambda { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :http_caching => "notavalidvalue") }.should raise_error + expect { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :http_caching => "notavalidvalue") }.to raise_error end it "should fail if 'sslverify' does not have one of the following values (True|False)" do - lambda { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :sslverify => "notavalidvalue") }.should raise_error + expect { Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :sslverify => "notavalidvalue") }.to raise_error end it "should succeed if 'sslverify' has one of the following values (True|False)" do Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :sslverify => "True")[:sslverify].should == "True" Puppet::Type.type(:yumrepo).new(:name => "puppetlabs", :sslverify => "False")[:sslverify].should == "False" end end - - # these tests were ported from the old spec/unit/type/yumrepo_spec.rb, pretty much verbatim - describe "When manipulating config file" do - def make_repo(name, hash={}) - hash[:name] = name - Puppet::Type.type(:yumrepo).new(hash) - end - - def all_sections(inifile) - sections = [] - inifile.each_section { |section| sections << section.name } - sections.sort - end - - def create_data_files() - File.open(File.join(@yumdir, "fedora.repo"), "w") do |f| - f.print(FEDORA_REPO_FILE) - end - - File.open(File.join(@yumdir, "fedora-devel.repo"), "w") do |f| - f.print(FEDORA_DEVEL_REPO_FILE) - end - end - - before(:each) do - @yumdir = tmpdir("yumrepo_spec_tmpdir") - @yumconf = File.join(@yumdir, "yum.conf") - File.open(@yumconf, "w") do |f| - f.print "[main]\nreposdir=#{@yumdir} /no/such/dir\n" - end - Puppet::Type.type(:yumrepo).yumconf = @yumconf - - # It needs to be reset each time, otherwise the cache is used. - Puppet::Type.type(:yumrepo).inifile = nil - end - - it "should be able to create a valid config file" do - values = { - :descr => "Fedora Core $releasever - $basearch - Base", - :baseurl => "http://example.com/yum/$releasever/$basearch/os/", - :enabled => "1", - :gpgcheck => "1", - :includepkgs => "absent", - :gpgkey => "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora", - :proxy => "http://proxy.example.com:80/", - :proxy_username => "username", - :proxy_password => "password" - } - repo = make_repo("base", values) - - catalog = Puppet::Resource::Catalog.new - # Stop Puppet from doing a bunch of magic; might want to think about a util for specs that handles this - catalog.host_config = false - catalog.add_resource(repo) - catalog.apply - - inifile = Puppet::Type.type(:yumrepo).read - sections = all_sections(inifile) - sections.should == ['base', 'main'] - text = inifile["base"].format - text.should == EXPECTED_CONTENTS_FOR_CREATED_FILE - end - - # Modify one existing section - it "should be able to modify an existing config file" do - create_data_files - - devel = make_repo("development", { :descr => "New description" }) - current_values = devel.retrieve - - devel[:name].should == "development" - current_values[devel.property(:descr)].should == 'Fedora Core $releasever - Development Tree' - devel[:descr].should == 'New description' - - catalog = Puppet::Resource::Catalog.new - # Stop Puppet from doing a bunch of magic; might want to think about a util for specs that handles this - catalog.host_config = false - catalog.add_resource(devel) - catalog.apply - - inifile = Puppet::Type.type(:yumrepo).read - inifile['development']['name'].should == 'New description' - inifile['base']['name'].should == 'Fedora Core $releasever - $basearch - Base' - inifile['base']['exclude'].should == "foo\n bar\n baz" - all_sections(inifile).should == ['base', 'development', 'main'] - end - - # Delete mirrorlist by setting it to :absent and enable baseurl - it "should support 'absent' value" do - create_data_files - - baseurl = 'http://example.com/' - - devel = make_repo( - "development", - { :mirrorlist => 'absent', - - :baseurl => baseurl }) - devel.retrieve - - catalog = Puppet::Resource::Catalog.new - # Stop Puppet from doing a bunch of magic; might want to think about a util for specs that handles this - catalog.host_config = false - catalog.add_resource(devel) - catalog.apply - - inifile = Puppet::Type.type(:yumrepo).read - sec = inifile["development"] - sec["mirrorlist"].should == nil - sec["baseurl"].should == baseurl - end - end end - -EXPECTED_CONTENTS_FOR_CREATED_FILE = <<'EOF' -[base] -name=Fedora Core $releasever - $basearch - Base -baseurl=http://example.com/yum/$releasever/$basearch/os/ -enabled=1 -gpgcheck=1 -gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora -proxy=http://proxy.example.com:80/ -proxy_username=username -proxy_password=password -EOF - -FEDORA_REPO_FILE = <