From 886245f2cb7614a8c749d34e6f08ee17b92c970f Mon Sep 17 00:00:00 2001 From: Daniele Sluijters Date: Tue, 14 Apr 2015 17:25:40 +0200 Subject: Initial commit (this is not finished). --- .fixtures.yml | 11 +++++ .gitignore | 9 ++++ .rspec | 2 + Gemfile | 22 +++++++++ LICENSE | 35 ++++++++++++++ README.md | 78 ++++++++++++++++++++++++++++++++ Rakefile | 56 +++++++++++++++++++++++ manifests/init.pp | 69 ++++++++++++++++++++++++++++ manifests/params.pp | 50 ++++++++++++++++++++ spec/classes/unattended_upgrades_spec.rb | 32 +++++++++++++ spec/spec_helper.rb | 1 + templates/periodic.erb | 47 +++++++++++++++++++ templates/unattended-upgrades.erb | 60 ++++++++++++++++++++++++ 13 files changed, 472 insertions(+) create mode 100644 .fixtures.yml create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 Gemfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 manifests/init.pp create mode 100644 manifests/params.pp create mode 100644 spec/classes/unattended_upgrades_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 templates/periodic.erb create mode 100644 templates/unattended-upgrades.erb diff --git a/.fixtures.yml b/.fixtures.yml new file mode 100644 index 0000000..7566fe3 --- /dev/null +++ b/.fixtures.yml @@ -0,0 +1,11 @@ +fixtures: + forge_modules: + stdlib: + repo: "puppetlabs-stdlib" + ref: "4.5.1" + repositories: + apt: + repo: "https://github.com/puppetlabs/puppetlabs-apt.git" + ref: "2.0.x" + symlinks: + unattended_upgrades: "#{source_dir}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5db85e --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +pkg/ +Gemfile.lock +vendor/ +spec/fixtures/ +.vagrant/ +.bundle/ +coverage/ +.idea/ +*.iml diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..8c18f1a --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..a2f968e --- /dev/null +++ b/Gemfile @@ -0,0 +1,22 @@ +source "https://rubygems.org" + +group :test do + gem "rake" + gem "puppet", ENV['PUPPET_VERSION'] || '~> 3.7.0' + gem "rspec", '< 3.2.0' + gem "rspec-puppet", :git => 'https://github.com/rodjek/rspec-puppet.git' + gem "puppetlabs_spec_helper" + gem "metadata-json-lint" + gem "rspec-puppet-facts" +end + +group :development do + gem "travis" + gem "travis-lint" + gem "puppet-blacksmith" +end + +group :system_tests do + gem "beaker" + gem "beaker-rspec" +end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1807ab9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,35 @@ +Copyright (c) 2011 Evolving Web Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Copyright 2014 Puppet Labs, 2015 Puppet Community + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f56957 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# unattended\_upgrades + +## Overview + +The unattended\_upgrades module allows for the installation and configuration of automatic security (and other) updates through apt. + +This functionality used to be part of the puppetlabs-apt module but was split off into its own module. + +## Module Description + +The unattended\_upgrades module automates the configuration of apt package updates. + +## Setup + +### What unattended\_upgrades affects: + +* Package/configuration for unattended\_upgrades + +### Beginning with unattended\_upgrades + +All you need to do is include the module `include unattended_upgrades`. + +## Usage + +Using unattended\_upgrades simply consists of including the module and if needed altering some of the default settings. + +## Reference + +### Classes + +* `unattended_upgrades`: Main class, installs the necessary packages and writes the configuration. + +### Parameters + +#### unattended\_upgrades + +* `auto`: A hash of settings with three possible keys: + * `fix_interrupted_dpkg`(`true`): Try to fix package installation state + * `reboot`(`false`): Reboot system after package update installation + * `remove`(`true`): Remove unneeded dependencies after update installation + + Any of these keys can be specified and will be merged into the defaults, so if you only want to change the `reboot` behaviour the following is enough: + + ```puppet + class { 'unattended_upgrades': + auto => { 'reboot' => true }, + } + ``` +* `blacklist`(`[]`): A list of packages to **not** automatically upgrade. This list is empty by default. +* `dl_limit`(`undef`): Use a bandwidth limit for downloading, specified in kb/sec. +* `enable` (`1`): Enable the automatic installation of updates. +* `install_on_shutdown` (`false`): Install updates on shutdown instead of in the background. +* `legacy_origin` (`false`): Use the legacy `Unattended-Upgrade::Allowed-Origins` setting or the modern `Unattended-Upgrade::Origins-Pattern`. +* `mail`: A hash to configure email behaviour. The possible keys are: + * `only_on_error` (`true`): Only send mail when something went wrong + * `to` (`undef`): Email address to send email too + + If the default for `to` is kept you will not receive any mail at all. You'll likely want to set this parameter: + + ```puppet + class { 'unattended_upgrades': + mail => { 'to' => 'admin@domain.tld', }, + } + ``` +* `minimal_steps` (`true`): Split the upgrade process into sections to allow shutdown during upgrade. +* `origins`: The repositories from which to automatically upgrade included packages. +* `package_ensure` (`installed`): The ensure state for the 'unattended-upgrades' package. + + +## Limitations + +This module should work across all versions of Debian/Ubuntu. + +## License + +The original code for this module comes from Evolving Web and was licensed under the MIT license. Code added since the fork of that module into puppetlabs-apt is covered under the Apache License version 2 as is any code added since it was split off into this separate unattended\_upgrades module. + +The LICENSE contains both licenses. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..1778d2a --- /dev/null +++ b/Rakefile @@ -0,0 +1,56 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet/version' +require 'puppet/vendor/semantic/lib/semantic' unless Puppet.version.to_f < 3.6 +require 'puppet-lint/tasks/puppet-lint' +require 'puppet-syntax/tasks/puppet-syntax' + +# These gems aren't always present, for instance +# on Travis with --without development +begin + require 'puppet_blacksmith/rake_tasks' +rescue LoadError +end + +Rake::Task[:lint].clear + +PuppetLint.configuration.relative = true +PuppetLint.configuration.send("disable_80chars") +PuppetLint.configuration.log_format = "%{path}:%{linenumber}:%{check}:%{KIND}:%{message}" +PuppetLint.configuration.fail_on_warnings = true + +# Forsake support for Puppet 2.6.2 for the benefit of cleaner code. +# http://puppet-lint.com/checks/class_parameter_defaults/ +PuppetLint.configuration.send('disable_class_parameter_defaults') +# http://puppet-lint.com/checks/class_inherits_from_params_class/ +PuppetLint.configuration.send('disable_class_inherits_from_params_class') + +exclude_paths = [ + "bundle/**/*", + "pkg/**/*", + "vendor/**/*", + "spec/**/*", +] +PuppetLint.configuration.ignore_paths = exclude_paths +PuppetSyntax.exclude_paths = exclude_paths + +desc "Run acceptance tests" +RSpec::Core::RakeTask.new(:acceptance) do |t| + t.pattern = 'spec/acceptance' +end + +desc "Populate CONTRIBUTORS file" +task :contributors do + system("git log --format='%aN' | sort -u > CONTRIBUTORS") +end + +task :metadata do + sh "metadata-json-lint metadata.json" +end + +desc "Run syntax, lint, and spec tests." +task :test => [ + :syntax, + :lint, + :spec, + :metadata, +] diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 0000000..414abe2 --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,69 @@ +class unattended_upgrades ( + $age = {}, + $auto = {}, + $backup = {}, + $blacklist = [], + $dl_limit = undef, + $enable = 1, + $install_on_shutdown = false, + $legacy_origin = $::unattended_upgrades::params::legacy_origin, + $mail = {}, + $minimal_steps = true, + $origins = $::unattended_upgrades::params::origins, + $package_ensure = installed, + $size = {}, + $update = 1, + $upgrade = 1, + $upgradeable_packages = {}, + $verbose = 0, +) inherits ::unattended_upgrades::params { + + include ::apt + Class['apt'] -> Class['unattended_upgrades'] + + validate_bool( + $install_on_shutdown, + $legacy_origin, + $minimal_steps, + ) + validate_array($blacklist) + validate_array($origins) + validate_hash($auto) + $_auto = merge($auto, $::unattended_upgrades::default_auto) + validate_hash($mail) + if $mail['only_on_error'] { + validate_bool($mail['only_on_error']) + } + $_mail = merge($mail, $::unattended_upgrades::default_mail) + validate_hash($backup) + $_backup = merge($backup, $::unattended_upgrades::default_backup) + validate_hash($age) + $_age = merge($age, $::unattended_upgrades::default_age) + validate_hash($size) + $_size = merge($size, $::unattended_upgrades::default_size) + validate_hash($upgradeable_packages) + $_upgradeable_packages = merge($upgradeable_packages, $::unattended_upgrades::upgradeable_packages) + + package { 'unattended-upgrades': + ensure => $package_ensure, + } + + apt::conf { 'unattended-upgrades': + priority => 50, + content => template("${module_name}/unattended-upgrades.erb"), + require => Package['unattended-upgrades'], + } + + apt::conf { 'periodic': + priority => 10, + content => template("${module_name}/periodic.erb"), + require => Package['unattended-upgrades'], + } + + apt::conf { 'auto-upgrades': + ensure => absent, + priority => 20, + require => Package['unattended-upgrades'], + } + +} diff --git a/manifests/params.pp b/manifests/params.pp new file mode 100644 index 0000000..49aec20 --- /dev/null +++ b/manifests/params.pp @@ -0,0 +1,50 @@ +class unattended_upgrades::params { + + if $::osfamily != 'Debian' { + fail('This module only works on Debian or derivatives like Ubuntu') + } + + $default_auto = { 'fix_interrupted_dpkg' => true, 'remove' => true, 'reboot' => false, 'clean' => 0, } + $default_mail = { 'only_on_errors' => true, 'to' => undef, } + $default_backup = { 'archive_interval' => 0, 'level' => 3, } + $default_age = { 'min' => 2, 'max' => 0, } + $default_size = { 'max' => 0, } + $default_upgradeable_packages = { 'download_only' => 0, 'debdelta' => 1, } + + # Strict variables facts lookup compatibility + $xfacts = { + 'lsbdistid' => defined('$lsbdistid') ? { + true => $::lsbdistid, + default => undef, + }, + 'lsbdistcodename' => defined('$lsbdistcodename') ? { + true => $::lsbdistcodename, + default => undef, + }, + } + + case $xfacts['lsbdistid'] { + 'debian': { + case $xfacts['lsbdistcodename'] { + 'squeeze': { + $legacy_origin = true + $origins = ['${distro_id} oldstable', #lint:ignore:single_quote_string_with_variables + '${distro_id} ${distro_codename}-security', #lint:ignore:single_quote_string_with_variables + '${distro_id} ${distro_codename}-lts',] #lint:ignore:single_quote_string_with_variables + } + default: { + $legacy_origin = false + $origins = ['origin=Debian,archive=stable,label=Debian-Security'] + } + } + } + 'ubuntu': { + $legacy_origin = true + $origins = ['${distro_id} {$distro_codename}-security', #lint:ignore:single_quote_string_with_variables + '${distro_id} {$distro_codename}-updates',] #lint:ignore:single_quote_string_with_variables + } + default: { + fail('Please explicitly specify unattended_upgrades::legacy_origin and unattended_upgrades::origins') + } + } +} diff --git a/spec/classes/unattended_upgrades_spec.rb b/spec/classes/unattended_upgrades_spec.rb new file mode 100644 index 0000000..b31f3d7 --- /dev/null +++ b/spec/classes/unattended_upgrades_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'unattended_upgrades' do + let(:file_unattended) { '/etc/apt/apt.conf.d/50unattended-upgrades' } + let(:file_periodic) { '/etc/apt/apt.conf.d/10periodic' } + let(:facts) { { + :osfamily => 'Debian', + :lsbdistid => 'Debian', + :lsbistcodename => 'wheezy', + :lsbrelease => '7.0.3', + } } + + it { should contain_package("unattended-upgrades") } + + it { + should create_file(file_unattended).with({ + "owner" => "root", + "group" => "root", + "mode" => "0644", + "require" => "Package[unattended-upgrades]", + }) + } + + it { + should create_file(file_periodic).with({ + "owner" => "root", + "group" => "root", + "mode" => "0644", + "require" => "Package[unattended-upgrades]", + }) + } +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..2c6f566 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1 @@ +require 'puppetlabs_spec_helper/module_spec_helper' diff --git a/templates/periodic.erb b/templates/periodic.erb new file mode 100644 index 0000000..8a581e0 --- /dev/null +++ b/templates/periodic.erb @@ -0,0 +1,47 @@ +APT::Periodic::Enable "<%= @enable %>"; +# - Enable the update/upgrade script (0=disable) +# +APT::Periodic::BackupArchiveInterval "<%= @_backup['archive_interval'] %>"; +# - Backup after n-days if archive contents changed.(0=disable) +# +APT::Periodic::BackupLevel "<%= @_backup['level'] %>"; +# - Backup level.(0=disable), 1 is invalid. +# +APT::Periodic::MaxAge "<%= @_age['max'] %>"; +# - Set maximum allowed age of a cache package file. If a cache +# package file is older it is deleted (0=disable) +# +APT::Periodic::MinAge "<%= @_age['min'] %>"; +# - Set minimum age of a package file. If a file is younger it +# will not be deleted (0=disable). Usefull to prevent races +# and to keep backups of the packages for emergency. +# +APT::Periodic::MaxSize "<%= @_size['max'] %>"; +# - Set maximum size of the cache in MB (0=disable). If the cache +# is bigger, cached package files are deleted until the size +# requirement is met (the biggest packages will be deleted +# first). +# +APT::Periodic::Update-Package-Lists "<%= @update %>"; +# - Do "apt-get update" automatically every n-days (0=disable) +# +APT::Periodic::Download-Upgradeable-Packages "<%= @_upgradeable_packages['downlaod_only'] %>"; +# - Do "apt-get upgrade --download-only" every n-days (0=disable) +# +# APT::Periodic::Download-Upgradeable-Packages-Debdelta "<%= @_upgradeable_packages['debdelta'] %>"; +# - Use debdelta-upgrade to download updates if available (0=disable) +APT::Periodic::Unattended-Upgrade "<%= @upgrade %>"; +# - Run the "unattended-upgrade" security upgrade script +# every n-days (0=disabled) +# Requires the package "unattended-upgrades" and will write +# a log in /var/log/unattended-upgrades +# +APT::Periodic::AutocleanInterval "<%= @_auto['clean'] %>"; +# - Do "apt-get autoclean" every n-days (0=disable) +# +APT::Periodic::Verbose "<%= @verbose %>"; +# - Send report mail to root +# 0: no report (or null string) +# 1: progress report (actually any string) +# 2: + command outputs (remove -qq, remove 2>/dev/null, add -d) +# 3: + trace on diff --git a/templates/unattended-upgrades.erb b/templates/unattended-upgrades.erb new file mode 100644 index 0000000..5007193 --- /dev/null +++ b/templates/unattended-upgrades.erb @@ -0,0 +1,60 @@ +// Automatically upgrade packages from these (origin:archive) pairs +<%- if @legacy_origin -%> +Unattended-Upgrade::Allowed-Origins { +<%- else -%> +Unattended-Upgrade::Origins-Pattern { +<%- end -%> +<% @origins.each do |origin| -%> + "<%= origin %>"; +<% end -%> +}; + +// List of packages to not update +Unattended-Upgrade::Package-Blacklist { +<% @blacklist.each do |package| -%> + "<%= package %>"; +<% end -%> +}; + +// This option allows you to control if on a unclean dpkg exit +// unattended-upgrades will automatically run +// dpkg --force-confold --configure -a +// The default is true, to ensure updates keep getting installed +Unattended-Upgrade::AutoFixInterruptedDpkg "<%= @_auto['fix_interrupted_dpkg'].to_s %>"; + +// Split the upgrade into the smallest possible chunks so that +// they can be interrupted with SIGUSR1. This makes the upgrade +// a bit slower but it has the benefit that shutdown while a upgrade +// is running is possible (with a small delay) +Unattended-Upgrade::MinimalSteps "<%= @minimal_steps.to_s %>"; + +// Install all unattended-upgrades when the machine is shuting down +// instead of doing it in the background while the machine is running +// This will (obviously) make shutdown slower +Unattended-Upgrade::InstallOnShutdown "<%= @install_on_shutdown.to_s %>"; + +<% unless @mail_to.nil? %> +// Send email to this address for problems or packages upgrades +// If empty or unset then no email is sent, make sure that you +// have a working mail setup on your system. A package that provides +// 'mailx' must be installed. +Unattended-Upgrade::Mail "<%= @mail_to %>"; + +// Set this value to "true" to get emails only on errors. Default +// is to always send a mail if Unattended-Upgrade::Mail is set +Unattended-Upgrade::MailOnlyOnError "<%= @mail_only_on_error.to_s %>"; +<% end %> + +// Do automatic removal of new unused dependencies after the upgrade +// (equivalent to apt-get autoremove) +Unattended-Upgrade::Remove-Unused-Dependencies "<%= @_auto['remove'].to_s %>"; + +// Automatically reboot *WITHOUT CONFIRMATION* if a +// the file /var/run/reboot-required is found after the upgrade +Unattended-Upgrade::Automatic-Reboot "<%= @_auto['reboot'].to_s %>"; + +<% unless @dl_limit.nil? %> +// Use apt bandwidth limit feature, this example limits the download +// speed to 70kb/sec +Acquire::http::Dl-Limit "<%= @dl_limit %>"; +<% end %> -- cgit v1.2.3