Browse Source

Merge pull request #259 from daenney/apt-hold

apt::hold: Add a mechanism to hold a package.
Ashley Penney 10 years ago
parent
commit
3917e080f3
3 changed files with 194 additions and 0 deletions
  1. 40 0
      README.md
  2. 54 0
      manifests/hold.pp
  3. 100 0
      spec/defines/hold_spec.rb

+ 40 - 0
README.md

@@ -138,6 +138,45 @@ If you wish to pin a number of packages you may specify the packages as a space
 delimited string using the `packages` attribute or pass in an array of package
 names.
 
+### apt::hold
+
+When you wish to hold a package in Puppet is should be done by passing in
+'held' as the ensure attribute to the package resource. However, a lot of
+public modules do not take this into account and generally do not work well
+with an ensure of 'held'.
+
+There is an additional issue that when Puppet is told to hold a package, it
+will hold it at the current version installed, there is no way to tell it in
+one go to install a specific version and then hold that version without using
+an exec resource that wraps `dpkg --set-selections` or `apt-mark`.
+
+At first glance this could also be solved by just passing the version required
+to the ensure attribute but that only means that Puppet will install that
+version once it processes that package. It does not inform apt that we want
+this package to be held. In other words; if another package somehow wants to
+upgrade this one (because of a version requirement in a dependency), apt
+should not allow it.
+
+In order to solve this you can use apt::hold. It's implemented by creating
+a preferences file with a priority of 1001, meaning that under normal
+circumstances this preference will always win. Because the priority is > 1000
+apt will interpret this as 'this should be the version installed and I am
+allowed to downgrade the current package if needed'.
+
+With this you can now set a package's ensure attribute to 'latest' but still
+get the version specified by apt::hold. You can do it like this:
+
+    apt::hold { 'vim':
+      version => '2:7.3.547-7',
+    }
+
+Since you might just want to hold Vim at version 7.3 and not care about the
+rest you can also pass in a version with a glob:
+
+    apt::hold { 'vim':
+      version => '2:7.3.*',
+    }
+
 ### apt::ppa
 
 Adds a ppa repository using `add-apt-repository`.
@@ -257,3 +296,4 @@ A lot of great people have contributed to this module. A somewhat current list f
 * Spencer Krum <spencer@puppetlabs.com>
 * William Van Hevelingen <blkperl@cat.pdx.edu> <wvan13@gmail.com>
 * Zach Leslie <zach@puppetlabs.com>
+* Daniele Sluijters <github@daenney.net>

+ 54 - 0
manifests/hold.pp

@@ -0,0 +1,54 @@
+# == Define apt::hold
+#
+# This defined type allows you to hold a package based on the version you
+# require. It's implemented by dropping an apt preferences file pinning the
+# package to the version you require.
+#
+# === Parameters
+#
+# [*version*]
+#   The version at which you wish to pin a package.
+#
+#   This can either be the full version, such as 4:2.11.8.1-5, or
+#   a partial version, such as 4:2.11.*
+#
+# [*package*]
+#   _default_: +$title+, the title/name of the resource.
+#
+#   Name of the package that apt is to hold.
+#
+# [*priority*]
+#   _default_: +1001+
+#
+#   The default priority of 1001 causes this preference to always win. By
+#   setting the priority to a number greater than 1000 apt will always install
+#   this version even if it means downgrading the currently installed version.
+define apt::hold(
+  $version,
+  $ensure   = 'present',
+  $package  = $title,
+  $priority = 1001,
+){
+
+  validate_string($title)
+  validate_re($ensure, ['^present|absent',])
+  validate_string($package)
+  validate_string($version)
+
+  if ! is_integer($priority) {
+    fail('$priority must be an integer')
+  }
+
+  if $ensure == 'present' {
+    ::apt::pin { "hold ${package} at ${version}":
+      packages => $package,
+      version  => $version,
+      priority => $priority,
+    }
+  } else {
+    ::apt::pin { "hold ${package} at ${version}":
+      ensure => 'absent',
+    }
+  }
+
+}

+ 100 - 0
spec/defines/hold_spec.rb

@@ -0,0 +1,100 @@
+require 'spec_helper'
+describe 'apt::hold' do
+  let :facts do {
+      :osfamily   => 'Debian',
+      :lsbdistid  => 'Debian',
+      :lsbrelease => 'wheezy',
+  } end
+
+  let :title do
+    'vim'
+  end
+
+  let :default_params do {
+    :version => '1.1.1',
+  } end
+
+  describe 'default params' do
+    let :params do default_params end
+
+    it 'creates an apt preferences file' do
+      should contain_apt__hold(title).with({
+        :ensure   => 'present',
+        :package  => title,
+        :version  => params[:version],
+        :priority => 1001,
+      })
+
+      should contain_apt__pin("hold #{title} at #{params[:version]}").with({
+        :ensure   => 'present',
+        :packages => title,
+        :version  => params[:version],
+        :priority => 1001,
+      })
+    end
+  end
+
+  describe 'ensure => absent' do
+    let :params do default_params.merge({:ensure => 'absent',}) end
+
+    it 'creates an apt preferences file' do
+      should contain_apt__hold(title).with({
+        :ensure   => params[:ensure],
+      })
+
+      should contain_apt__pin("hold #{title} at #{params[:version]}").with({
+        :ensure   => params[:ensure],
+      })
+    end
+  end
+
+  describe 'priority => 990' do
+    let :params do default_params.merge({:priority => 990,}) end
+
+    it 'creates an apt preferences file' do
+      should contain_apt__hold(title).with({
+        :ensure   => 'present',
+        :package  => title,
+        :version  => params[:version],
+        :priority => params[:priority],
+      })
+
+      should contain_apt__pin("hold #{title} at #{params[:version]}").with({
+        :ensure   => 'present',
+        :packages => title,
+        :version  => params[:version],
+        :priority => params[:priority],
+      })
+    end
+  end
+
+  describe 'validation' do
+    context 'version => {}' do
+      let :params do { :version => {}, } end
+      it 'should fail' do
+        expect { subject }.to raise_error(/is not a string/)
+      end
+    end
+
+    context 'ensure => bananana' do
+      let :params do default_params.merge({:ensure => 'bananana',}) end
+      it 'should fail' do
+        expect { subject }.to raise_error(/does not match/)
+      end
+    end
+
+    context 'package => []' do
+      let :params do default_params.merge({:package => [],}) end
+      it 'should fail' do
+        expect { subject }.to raise_error(/is not a string/)
+      end
+    end
+
+    context 'priority => bananana' do
+      let :params do default_params.merge({:priority => 'bananana',}) end
+      it 'should fail' do
+        expect { subject }.to raise_error(/must be an integer/)
+      end
+    end
+  end
+end