feat: initial commit

This commit is contained in:
amy 2025-04-01 17:40:03 +00:00
commit 38f495e3f4
457 changed files with 40577 additions and 0 deletions

View file

@ -0,0 +1,119 @@
# @summary Manages backports.
#
# @example Set up a backport source for Ubuntu
# include apt::backports
#
# @param location
# Specifies an Apt repository containing the backports to manage. Valid options: a string containing a URL. Default value for Debian and
# Ubuntu varies:
#
# - Debian: 'http://deb.debian.org/debian'
#
# - Ubuntu: 'http://archive.ubuntu.com/ubuntu'
#
# @param release
# Specifies a distribution of the Apt repository containing the backports to manage. Used in populating the `sources.list` configuration file.
# Default: on Debian and Ubuntu, `${fact('os.distro.codename')}-backports`. We recommend keeping this default, except on other operating
# systems.
#
# @param repos
# Specifies a component of the Apt repository containing the backports to manage. Used in populating the `sources.list` configuration file.
# Default value for Debian and Ubuntu varies:
#
# - Debian: 'main contrib non-free non-free-firmware'
#
# - Ubuntu: 'main universe multiverse restricted'
#
# @param key
# Specifies a key to authenticate the backports. Valid options: a string to be passed to the id parameter of the apt::key defined type, or a
# hash of parameter => value pairs to be passed to apt::key's id, server, content, source, and/or options parameters.
#
# @param keyring
# Absolute path to a file containing the PGP keyring used to sign this
# repository. Value is passed to the apt::source and used to set signed-by on
# the source entry.
#
# @param pin
# Specifies a pin priority for the backports. Valid options: a number or string to be passed to the `id` parameter of the `apt::pin` defined
# type, or a hash of `parameter => value` pairs to be passed to `apt::pin`'s corresponding parameters.
#
# @param include
# Specifies whether to include 'deb' or 'src', or both.
#
class apt::backports (
Optional[Stdlib::HTTPUrl] $location = undef,
Optional[String[1]] $release = undef,
Optional[String[1]] $repos = undef,
Optional[Variant[String[1], Hash]] $key = undef,
Stdlib::AbsolutePath $keyring = "/usr/share/keyrings/${facts['os']['name'].downcase}-archive-keyring.gpg",
Variant[Integer, String[1], Hash] $pin = 200,
Hash $include = {},
) {
include apt
if $location {
$_location = $location
}
if $release {
$_release = $release
}
if $repos {
$_repos = $repos
}
if (!($facts['os']['name'] == 'Debian' or $facts['os']['name'] == 'Ubuntu')) {
unless $location and $release and $repos {
fail('If not on Debian or Ubuntu, you must explicitly pass location, release, and repos')
}
}
unless $location {
$_location = $apt::backports['location']
}
unless $release {
if fact('os.distro.codename') {
$_release = "${fact('os.distro.codename')}-backports"
} else {
fail('os.distro.codename fact not available: release parameter required')
}
}
unless $repos {
$_repos = $apt::backports['repos']
}
$_keyring = if $key {
undef
} else {
$keyring
}
if $pin =~ Hash {
$_pin = $pin
} elsif $pin =~ Numeric or $pin =~ String {
$pin_type = $facts['os']['name'] ? {
'Debian' => 'codename',
'Ubuntu' => 'release',
}
$_pin = {
'priority' => $pin,
$pin_type => $_release,
}
} else {
fail('pin must be either a string, number or hash')
}
apt::source { 'backports':
location => $_location,
release => $_release,
repos => $_repos,
include => $include,
key => $key,
keyring => $_keyring,
pin => $_pin,
}
}

View file

@ -0,0 +1,35 @@
# @summary Specifies a custom Apt configuration file.
#
# @param content
# Required unless `ensure` is set to 'absent'. Directly supplies content for the configuration file.
#
# @param ensure
# Specifies whether the configuration file should exist.
#
# @param priority
# Determines the order in which Apt processes the configuration file. Files with lower priority numbers are loaded first.
# Valid options: a string containing an integer or an integer.
#
# @param notify_update
# Specifies whether to trigger an `apt-get update` run.
#
define apt::conf (
Optional[String[1]] $content = undef,
Enum['present', 'absent'] $ensure = present,
Variant[String[1], Integer[0]] $priority = 50,
Optional[Boolean] $notify_update = undef,
) {
unless $ensure == 'absent' {
unless $content {
fail('Need to pass in content parameter')
}
}
$confheadertmp = epp('apt/_conf_header.epp')
apt::setting { "conf-${name}":
ensure => $ensure,
priority => $priority,
content => "${confheadertmp}${content}",
notify_update => $notify_update,
}
}

View file

@ -0,0 +1,457 @@
# @summary Main class, includes all other classes.
#
# @see https://docs.puppetlabs.com/references/latest/function.html#createresources for the create resource function
#
# @param provider
# Specifies the provider that should be used by apt::update.
#
# @param keyserver
# Specifies a keyserver to provide the GPG key. Valid options: a string containing a domain name or a full URL (http://, https://, or
# hkp://).
#
# @param key_options
# Specifies the default options for apt::key resources.
#
# @param ppa_options
# Supplies options to be passed to the `add-apt-repository` command.
#
# @param ppa_package
# Names the package that provides the `apt-add-repository` command.
#
# @param backports
# Specifies some of the default parameters used by apt::backports. Valid options: a hash made up from the following keys:
#
# @option backports [String] :location
# See apt::backports for documentation.
#
# @option backports [String] :repos
# See apt::backports for documentation.
#
# @option backports [String] :key
# See apt::backports for documentation.
#
# @param confs
# Hash of `apt::conf` resources.
#
# @param update
# Configures various update settings. Valid options: a hash made up from the following keys:
#
# @option update [String] :frequency
# Specifies how often to run `apt-get update`. If the exec resource `apt_update` is notified,
# `apt-get update` runs regardless of this value.
# Valid options:
# 'always' (at every Puppet run);
# 'hourly' (if the value of `apt_update_last_success` is less than current epoch time minus 3600);
# 'daily' (if the value of `apt_update_last_success` is less than current epoch time minus 86400);
# 'weekly' (if the value of `apt_update_last_success` is less than current epoch time minus 604800);
# Integer (if the value of `apt_update_last_success` is less than current epoch time minus provided Integer value);
# 'reluctantly' (only if the exec resource `apt_update` is notified).
# Default: 'reluctantly'.
#
# @option update [Integer] :loglevel
# Specifies the log level of logs outputted to the console. Default: undef.
#
# @option update [Integer] :timeout
# Specifies how long to wait for the update to complete before canceling it. Valid options: an integer, in seconds. Default: undef.
#
# @option update [Integer] :tries
# Specifies how many times to retry the update after receiving a DNS or HTTP error. Default: undef.
#
# @param update_defaults
# The default update settings that are combined and merged with the passed `update` value
#
# @param purge
# Specifies whether to purge any existing settings that aren't managed by Puppet. Valid options: a hash made up from the following keys:
#
# @option purge [Boolean] :sources.list
# Specifies whether to purge any unmanaged entries from sources.list. Default false.
#
# @option purge [Boolean] :sources.list.d
# Specifies whether to purge any unmanaged entries from sources.list.d. Default false.
#
# @option purge [Boolean] :preferences
# Specifies whether to purge any unmanaged entries from preferences. Default false.
#
# @option purge [Boolean] :preferences.d.
# Specifies whether to purge any unmanaged entries from preferences.d. Default false.
#
# @param purge_defaults
# The default purge settings that are combined and merged with the passed `purge` value
#
# @param proxy
# Configures Apt to connect to a proxy server. Valid options: a hash matching the locally defined type apt::proxy.
#
# @param proxy_defaults
# The default proxy settings that are combined and merged with the passed `proxy` value
#
# @param sources
# Hash of `apt::source` resources.
#
# @param keys
# Hash of `apt::key` resources.
#
# @param keyrings
# Hash of `apt::keyring` resources.
#
# @param ppas
# Hash of `apt::ppa` resources.
#
# @param pins
# Hash of `apt::pin` resources.
#
# @param settings
# Hash of `apt::setting` resources.
#
# @param manage_auth_conf
# Specifies whether to manage the /etc/apt/auth.conf file. When true, the file will be overwritten with the entries specified in
# the auth_conf_entries parameter. When false, the file will be ignored (note that this does not set the file to absent.
#
# @param auth_conf_entries
# An optional array of login configuration settings (hashes) that are recorded in the file /etc/apt/auth.conf. This file has a netrc-like
# format (similar to what curl uses) and contains the login configuration for APT sources and proxies that require authentication. See
# https://manpages.debian.org/testing/apt/apt_auth.conf.5.en.html for details. If specified each hash must contain the keys machine, login and
# password and no others. Specifying manage_auth_conf and not specifying this parameter will set /etc/apt/auth.conf to absent.
#
# @param auth_conf_owner
# The owner of the file /etc/apt/auth.conf.
#
# @param root
# Specifies root directory of Apt executable.
#
# @param sources_list
# Specifies the path of the sources_list file to use.
#
# @param sources_list_d
# Specifies the path of the sources_list.d file to use.
#
# @param conf_d
# Specifies the path of the conf.d file to use.
#
# @param preferences
# Specifies the path of the preferences file to use.
#
# @param preferences_d
# Specifies the path of the preferences.d file to use.
#
# @param config_files
# A hash made up of the various configuration files used by Apt.
#
# @param sources_list_force
# Specifies whether to perform force purge or delete.
#
# @param include_defaults
# The package types to include by default.
#
# @param apt_conf_d
# The path to the file `apt.conf.d`
#
# @param source_key_defaults
# The fault `source_key` settings
#
class apt (
Hash $update_defaults = {
'frequency' => 'reluctantly',
'loglevel' => undef,
'timeout' => undef,
'tries' => undef,
},
Hash $purge_defaults = {
'sources.list' => false,
'sources.list.d' => false,
'preferences' => false,
'preferences.d' => false,
'apt.conf.d' => false,
},
Hash $proxy_defaults = {
'ensure' => undef,
'host' => undef,
'port' => 8080,
'https' => false,
'https_acng' => false,
'direct' => false,
},
Hash $include_defaults = {
'deb' => true,
'src' => false,
},
Stdlib::Absolutepath $provider = '/usr/bin/apt-get',
Stdlib::Host $keyserver = 'keyserver.ubuntu.com',
Optional[String[1]] $key_options = undef,
Optional[Array[String[1]]] $ppa_options = undef,
Optional[String[1]] $ppa_package = undef,
Optional[Hash] $backports = undef,
Hash $confs = {},
Hash $update = {},
Hash $purge = {},
Apt::Proxy $proxy = {},
Hash $sources = {},
Hash $keys = {},
Hash $keyrings = {},
Hash $ppas = {},
Hash $pins = {},
Hash $settings = {},
Boolean $manage_auth_conf = true,
Array[Apt::Auth_conf_entry] $auth_conf_entries = [],
String[1] $auth_conf_owner = '_apt',
Stdlib::Absolutepath $root = '/etc/apt',
Stdlib::Absolutepath $sources_list = "${root}/sources.list",
Stdlib::Absolutepath $sources_list_d = "${root}/sources.list.d",
Stdlib::Absolutepath $conf_d = "${root}/apt.conf.d",
Stdlib::Absolutepath $preferences = "${root}/preferences",
Stdlib::Absolutepath $preferences_d = "${root}/preferences.d",
Stdlib::Absolutepath $apt_conf_d = "${root}/apt.conf.d",
Hash $config_files = {
'conf' => {
'path' => $conf_d,
'ext' => '',
},
'pref' => {
'path' => $preferences_d,
'ext' => '.pref',
},
'list' => {
'path' => $sources_list_d,
'ext' => '.list',
},
'sources' => {
'path' => $sources_list_d,
'ext' => '.sources',
},
},
Boolean $sources_list_force = false,
Hash $source_key_defaults = {
'server' => $keyserver,
'options' => undef,
'content' => undef,
'source' => undef,
},
) {
if $facts['os']['family'] != 'Debian' {
fail('This module only works on Debian or derivatives like Ubuntu')
}
if $update['frequency'] {
assert_type(
Variant[Enum['always','hourly','daily','weekly','reluctantly'],Integer[60]],
$update['frequency'],
)
}
if $update['timeout'] {
assert_type(Integer, $update['timeout'])
}
if $update['tries'] {
assert_type(Integer, $update['tries'])
}
$_update = $apt::update_defaults + $update
include apt::update
if $purge['sources.list'] {
assert_type(Boolean, $purge['sources.list'])
}
if $purge['sources.list.d'] {
assert_type(Boolean, $purge['sources.list.d'])
}
if $purge['preferences'] {
assert_type(Boolean, $purge['preferences'])
}
if $purge['preferences.d'] {
assert_type(Boolean, $purge['preferences.d'])
}
if $sources_list_force {
assert_type(Boolean, $sources_list_force)
}
if $purge['apt.conf.d'] {
assert_type(Boolean, $purge['apt.conf.d'])
}
$_purge = $apt::purge_defaults + $purge
if $proxy['perhost'] {
$_perhost = $proxy['perhost'].map |$item| {
$_item = $apt::proxy_defaults + $item
$_scheme = $_item['https'] ? {
true => 'https',
default => 'http',
}
$_port = $_item['port'] ? {
Integer => ":${_item['port']}",
default => ''
}
$_target = $_item['direct'] ? {
true => 'DIRECT',
default => "${_scheme}://${_item['host']}${_port}/",
}
$item + { 'scheme' => $_scheme, 'target' => $_target, }
}
} else {
$_perhost = {}
}
$_proxy = $apt::proxy_defaults + $proxy + { 'perhost' => $_perhost }
$confheadertmp = epp('apt/_conf_header.epp')
$proxytmp = epp('apt/proxy.epp', { 'proxies' => $_proxy })
$updatestamptmp = file('apt/15update-stamp')
if $_proxy['ensure'] == 'absent' or $_proxy['host'] {
apt::setting { 'conf-proxy':
ensure => $_proxy['ensure'],
priority => '01',
content => "${confheadertmp}${proxytmp}",
}
}
if $sources_list_force {
$sources_list_ensure = $_purge['sources.list'] ? {
true => absent,
default => file,
}
$sources_list_content = $_purge['sources.list'] ? {
true => nil,
default => undef,
}
} else {
$sources_list_ensure = $_purge['sources.list'] ? {
true => file,
default => file,
}
$sources_list_content = $_purge['sources.list'] ? {
true => "# Repos managed by puppet.\n",
default => undef,
}
}
$preferences_ensure = $_purge['preferences'] ? {
true => absent,
default => file,
}
apt::setting { 'conf-update-stamp':
priority => 15,
content => "${confheadertmp}${updatestamptmp}",
}
file { 'sources.list':
ensure => $sources_list_ensure,
path => $apt::sources_list,
owner => root,
group => root,
content => $sources_list_content,
notify => Class['apt::update'],
}
file { 'sources.list.d':
ensure => directory,
path => $apt::sources_list_d,
owner => root,
group => root,
purge => $_purge['sources.list.d'],
recurse => $_purge['sources.list.d'],
notify => Class['apt::update'],
}
file { 'preferences':
ensure => $preferences_ensure,
path => $apt::preferences,
owner => root,
group => root,
notify => Class['apt::update'],
}
file { 'preferences.d':
ensure => directory,
path => $apt::preferences_d,
owner => root,
group => root,
purge => $_purge['preferences.d'],
recurse => $_purge['preferences.d'],
notify => Class['apt::update'],
}
file { 'apt.conf.d':
ensure => directory,
path => $apt::apt_conf_d,
owner => root,
group => root,
purge => $_purge['apt.conf.d'],
recurse => $_purge['apt.conf.d'],
notify => Class['apt::update'],
}
$confs.each |$key, $value| {
apt::conf { $key:
* => $value,
}
}
$sources.each |$key, $value| {
apt::source { $key:
* => $value,
}
}
$keys.each |$key, $value| {
apt::key { $key:
* => $value,
}
}
$keyrings.each |$key, $data| {
apt::keyring { $key:
* => $data,
}
}
$ppas.each |$key, $value| {
apt::ppa { $key:
* => $value,
}
}
$settings.each |$key, $value| {
apt::setting { $key:
* => $value,
}
}
if $manage_auth_conf {
$auth_conf_ensure = $auth_conf_entries ? {
[] => 'absent',
default => 'present',
}
$auth_conf_tmp = stdlib::deferrable_epp('apt/auth_conf.epp',
{
'auth_conf_entries' => $auth_conf_entries,
},
)
file { '/etc/apt/auth.conf':
ensure => $auth_conf_ensure,
owner => $auth_conf_owner,
group => 'root',
mode => '0600',
content => Sensitive($auth_conf_tmp),
notify => Class['apt::update'],
}
}
$pins.each |$key, $value| {
apt::pin { $key:
* => $value,
}
}
case $facts['os']['name'] {
'Debian': {
stdlib::ensure_packages(['gnupg'])
}
'Ubuntu': {
stdlib::ensure_packages(['gnupg'])
}
default: {
# Nothing in here
}
}
}

View file

@ -0,0 +1,104 @@
# @summary Manages the GPG keys that Apt uses to authenticate packages.
#
# @note
# The apt::key defined type makes use of the apt_key type, but includes extra functionality to help prevent duplicate keys.
#
# @example Declare Apt key for apt.puppetlabs.com source
# apt::key { 'puppetlabs':
# id => '6F6B15509CF8E59E6E469F327F438280EF8D349F',
# server => 'keyserver.ubuntu.com',
# options => 'http-proxy="http://proxyuser:proxypass@example.org:3128"',
# }
#
# @param id
# Specifies a GPG key to authenticate Apt package signatures. Valid options: a string containing a key ID (8 or 16 hexadecimal
# characters, optionally prefixed with "0x") or a full key fingerprint (40 hexadecimal characters).
#
# @param ensure
# Specifies whether the key should exist. Using `refreshed` will make keys
# auto update when they have expired (assuming a new key exists on the key
# server).
#
# @param content
# Supplies the entire GPG key. Useful in case the key can't be fetched from a remote location and using a file resource is inconvenient.
#
# @param source
# Specifies the location of an existing GPG key file to copy. Valid options: a string containing a URL (ftp://, http://, or https://) or
# an absolute path.
#
# @param server
# Specifies a keyserver to provide the GPG key. Valid options: a string containing a domain name or a full URL (http://, https://,
# hkp:// or hkps://). The hkps:// protocol is currently only supported on Ubuntu 18.04.
#
# @param weak_ssl
# Specifies whether strict SSL verification on a https URL should be disabled.
#
# @param options
# Passes additional options to `apt-key adv --keyserver-options`.
#
define apt::key (
Pattern[/\A(0x)?[0-9a-fA-F]{8}\Z/, /\A(0x)?[0-9a-fA-F]{16}\Z/, /\A(0x)?[0-9a-fA-F]{40}\Z/] $id = $title,
Enum['present', 'absent', 'refreshed'] $ensure = present,
Optional[String[1]] $content = undef,
Optional[Pattern[/\Ahttps?:\/\//, /\Aftp:\/\//, /\A\/\w+/]] $source = undef,
Pattern[/\A((hkp|hkps|http|https):\/\/)?([a-z\d])([a-z\d-]{0,61}\.)+[a-z\d]+(:\d{2,5})?(\/[a-zA-Z\d\-_.]+)*\/?$/] $server = $apt::keyserver,
Boolean $weak_ssl = false,
Optional[String[1]] $options = $apt::key_options,
) {
case $ensure {
/^(refreshed|present)$/: {
if defined(Anchor["apt_key ${id} absent"]) {
fail("key with id ${id} already ensured as absent")
}
if !defined(Anchor["apt_key ${id} present"]) {
apt_key { $title:
ensure => present,
refresh => $ensure == 'refreshed',
id => $id,
source => $source,
content => $content,
server => $server,
weak_ssl => $weak_ssl,
options => $options,
} -> anchor { "apt_key ${id} present": }
case $facts['os']['name'] {
'Debian': {
stdlib::ensure_packages(['gnupg'])
Apt::Key<| title == $title |>
}
'Ubuntu': {
stdlib::ensure_packages(['gnupg'])
Apt::Key<| title == $title |>
}
default: {
# Nothing in here
}
}
}
}
/^absent$/: {
if defined(Anchor["apt_key ${id} present"]) {
fail("key with id ${id} already ensured as present")
}
if !defined(Anchor["apt_key ${id} absent"]) {
apt_key { $title:
ensure => $ensure,
id => $id,
source => $source,
content => $content,
server => $server,
weak_ssl => $weak_ssl,
options => $options,
} -> anchor { "apt_key ${id} absent": }
}
}
default: {
fail("Invalid \'ensure\' value \'${ensure}\' for apt::key")
}
}
}

View file

@ -0,0 +1,72 @@
# @summary Manage GPG keyrings for apt repositories
#
# @example Download the puppetlabs apt keyring
# apt::keyring { 'puppetlabs-keyring.gpg':
# source => 'https://apt.puppetlabs.com/keyring.gpg',
# }
# @example Deploy the apt source and associated keyring file
# apt::source { 'puppet8-release':
# location => 'http://apt.puppetlabs.com',
# repos => 'puppet8',
# key => {
# name => 'puppetlabs-keyring.gpg',
# source => 'https://apt.puppetlabs.com/keyring.gpg'
# }
# }
#
# @param dir
# Path to the directory where the keyring will be stored.
#
# @param filename
# Optional filename for the keyring. It should also contain extension along with the filename.
#
# @param mode
# File permissions of the keyring.
#
# @param source
# Source of the keyring file. Mutually exclusive with 'content'.
#
# @param content
# Content of the keyring file. Mutually exclusive with 'source'.
#
# @param ensure
# Ensure presence or absence of the resource.
#
define apt::keyring (
Stdlib::Absolutepath $dir = '/etc/apt/keyrings',
String[1] $filename = $name,
Stdlib::Filemode $mode = '0644',
Optional[Stdlib::Filesource] $source = undef,
Optional[String[1]] $content = undef,
Enum['present','absent'] $ensure = 'present',
) {
ensure_resource('file', $dir, { ensure => 'directory', mode => '0755', })
if $source and $content {
fail("Parameters 'source' and 'content' are mutually exclusive")
} elsif $ensure == 'present' and ! $source and ! $content {
fail("One of 'source' or 'content' parameters are required")
}
$file = "${dir}/${filename}"
case $ensure {
'present': {
file { $file:
ensure => 'file',
mode => $mode,
owner => 'root',
group => 'root',
source => $source,
content => $content,
}
}
'absent': {
file { $file:
ensure => $ensure,
}
}
default: {
fail("Invalid 'ensure' value '${ensure}' for apt::keyring")
}
}
}

View file

@ -0,0 +1,37 @@
# @summary Manages apt-mark settings
#
# @param setting
# Specifies the behavior of apt in case of no more dependencies installed
# https://manpages.debian.org/stable/apt/apt-mark.8.en.html
#
define apt::mark (
Enum['auto','manual','hold','unhold'] $setting,
) {
if $title !~ /^[a-z0-9][a-z0-9.+\-]+$/ {
fail("Invalid package name: ${title}")
}
if $setting == 'unhold' {
$unless_cmd = undef
} else {
$action = "show${setting}"
# It would be ideal if we could break out this command in to an array of args, similar
# to $onlyif_cmd and $command. However, in this case it wouldn't work as expected due
# to the inclusion of a pipe character.
# When passed to the exec function, the posix provider will strip everything to the right of the pipe,
# causing the command to return a full list of packages for the given action.
# The trade off is to use an interpolated string knowing that action is built from an enum value and
# title is pre-validated.
$unless_cmd = ["/usr/bin/apt-mark ${action} ${title} | grep ${title} -q"]
}
$onlyif_cmd = [['/usr/bin/dpkg', '-l', $title]]
$command = ['/usr/bin/apt-mark', $setting, $title]
exec { "apt-mark ${setting} ${title}":
command => $command,
onlyif => $onlyif_cmd,
unless => $unless_cmd,
}
}

View file

@ -0,0 +1,136 @@
# @summary Manages Apt pins. Does not trigger an apt-get update run.
#
# @see https://manpages.debian.org/stable/apt/apt_preferences.5.en.html for context on these parameters
#
# @param ensure
# Specifies whether the pin should exist.
#
# @param explanation
# Supplies a comment to explain the pin. Default: "${caller_module_name}: ${name}".
#
# @param order
# Determines the order in which Apt processes the pin file. Files with lower order numbers are loaded first.
#
# @param packages
# Specifies which package(s) to pin.
#
# @param priority
# Sets the priority of the package. If multiple versions of a given package are available, `apt-get` installs the one with the highest
# priority number (subject to dependency constraints).
#
# @param release
# Tells APT to prefer packages that support the specified release. Typical values include 'stable', 'testing', and 'unstable'.
#
# @param release_version
# Tells APT to prefer packages that support the specified operating system release version (such as Debian release version 7).
#
# @param component
# Names the licensing component associated with the packages in the directory tree of the Release file.
#
# @param originator
# Names the originator of the packages in the directory tree of the Release file.
#
# @param label
# Names the label of the packages in the directory tree of the Release file.
#
# @param origin
# The package origin (the hostname part of the package's sources.list entry)
#
# @param version
# The version of the package
#
# @param codename
# The codename of the release
#
define apt::pin (
Enum['file', 'present', 'absent'] $ensure = present,
Optional[String[1]] $explanation = undef,
Variant[Integer[0]] $order = 50,
Variant[String[1], Array[String[1]]] $packages = '*',
Variant[Integer, String[1]] $priority = 0,
Optional[String[1]] $release = undef, # a=
Optional[String[1]] $origin = undef,
Optional[String[1]] $version = undef,
Optional[String[1]] $codename = undef, # n=
Optional[String[1]] $release_version = undef, # v=
Optional[String[1]] $component = undef, # c=
Optional[String[1]] $originator = undef, # o=
Optional[String[1]] $label = undef, # l=
) {
if $explanation {
$_explanation = $explanation
} else {
if defined('$caller_module_name') { # strict vars check
$_explanation = "${caller_module_name}: ${name}"
} else {
$_explanation = ": ${name}"
}
}
$pin_release_array = [
$release,
$codename,
$release_version,
$component,
$originator,
$label,
]
$pin_release = join($pin_release_array, '')
# Read the manpage 'apt_preferences(5)', especially the chapter
# 'The Effect of APT Preferences' to understand the following logic
# and the difference between specific and general form
if $packages =~ Array {
$packages_string = join($packages, ' ')
} else {
$packages_string = $packages
}
if $packages_string != '*' { # specific form
if ( $pin_release != '' and ( $origin or $version )) or
( $version and ( $pin_release != '' or $origin )) {
fail('parameters release, origin, and version are mutually exclusive')
}
} else { # general form
if $version {
fail('parameter version cannot be used in general form')
}
if ( $pin_release != '' and $origin ) {
fail('parameters release and origin are mutually exclusive')
}
}
# According to man 5 apt_preferences:
# The files have either no or "pref" as filename extension
# and only contain alphanumeric, hyphen (-), underscore (_) and period
# (.) characters. Otherwise APT will print a notice that it has ignored a
# file, unless that file matches a pattern in the
# Dir::Ignore-Files-Silently configuration list - in which case it will
# be silently ignored.
$file_name = regsubst($title, '[^0-9a-z\-_\.]', '_', 'IG')
$headertmp = epp('apt/_header.epp')
$pinpreftmp = epp('apt/pin.pref.epp', {
'name' => $name,
'pin_release' => $pin_release,
'release' => $release,
'codename' => $codename,
'release_version' => $release_version,
'component' => $component,
'originator' => $originator,
'label' => $label,
'version' => $version,
'origin' => $origin,
'explanation' => $_explanation,
'packages_string' => $packages_string,
'priority' => $priority,
})
apt::setting { "pref-${file_name}":
ensure => $ensure,
priority => $order,
content => "${headertmp}${pinpreftmp}",
notify_update => false,
}
}

View file

@ -0,0 +1,128 @@
# @summary Manages PPA repositories using `add-apt-repository`. Not supported on Debian.
#
# @example Declaration of an Apt PPA
# apt::ppa { 'ppa:openstack-ppa/bleeding-edge': }
#
# @param ensure
# Specifies whether the PPA should exist.
#
# @param options
# Supplies options to be passed to the `add-apt-repository` command.
#
# @param release
# Specifies the operating system of your node. Valid options: a string containing a valid LSB distribution codename.
# Optional if `puppet facts show os.distro.codename` returns your correct distribution release codename.
#
# @param dist
# Specifies the distribution of your node. Valid options: a string containing a valid distribution codename.
# Optional if `puppet facts show os.name` returns your correct distribution name.
#
# @param package_name
# Names the package that provides the `apt-add-repository` command.
#
# @param package_manage
# Specifies whether Puppet should manage the package that provides `apt-add-repository`.
#
define apt::ppa (
Enum['present', 'absent'] $ensure = 'present',
Optional[Array[String[1]]] $options = $apt::ppa_options,
Optional[String[1]] $release = fact('os.distro.codename'),
Optional[String[1]] $dist = $facts['os']['name'],
Optional[String[1]] $package_name = $apt::ppa_package,
Boolean $package_manage = false,
) {
unless $release {
fail('os.distro.codename fact not available: release parameter required')
}
if $dist == 'Debian' {
fail('apt::ppa is not currently supported on Debian.')
}
# Validate the resource name
if $name !~ /^ppa:([a-zA-Z0-9\-_.]+)\/([a-zA-z0-9\-_\.]+)$/ {
fail("Invalid PPA name: ${name}")
}
$distid = downcase($dist)
$dash_filename = regsubst($name, '^ppa:([^/]+)/(.+)$', "\\1-${distid}-\\2")
$underscore_filename = regsubst($name, '^ppa:([^/]+)/(.+)$', "\\1_${distid}_\\2")
$dash_filename_no_slashes = regsubst($dash_filename, '/', '-', 'G')
$dash_filename_no_specialchars = regsubst($dash_filename_no_slashes, '[\.\+]', '_', 'G')
$underscore_filename_no_slashes = regsubst($underscore_filename, '/', '-', 'G')
$underscore_filename_no_specialchars = regsubst($underscore_filename_no_slashes, '[\.\+]', '_', 'G')
$sources_list_d_filename = if versioncmp($facts['os']['release']['full'], '23.10') < 0 {
"${dash_filename_no_specialchars}-${release}.list"
} else {
"${dash_filename_no_specialchars}-${release}.sources"
}
if versioncmp($facts['os']['release']['full'], '21.04') < 0 {
$trusted_gpg_d_filename = "${underscore_filename_no_specialchars}.gpg"
} else {
$trusted_gpg_d_filename = "${dash_filename_no_specialchars}.gpg"
}
# This is the location of our main exec script.
$cache_path = $facts['puppet_vardir']
$script_path = "${cache_path}/add-apt-repository-${dash_filename_no_specialchars}-${release}.sh"
if $ensure == 'present' {
if $package_manage {
stdlib::ensure_packages($package_name)
$_require = [File['sources.list.d'], Package[$package_name]]
} else {
$_require = File['sources.list.d']
}
$_proxy = $apt::_proxy
if $_proxy['host'] {
if $_proxy['https'] {
$_proxy_env = ["http_proxy=http://${$_proxy['host']}:${$_proxy['port']}", "https_proxy=https://${$_proxy['host']}:${$_proxy['port']}"]
} else {
$_proxy_env = ["http_proxy=http://${$_proxy['host']}:${$_proxy['port']}"]
}
} else {
$_proxy_env = []
}
unless $sources_list_d_filename in $facts['apt_sources'] {
$script_content = epp('apt/add-apt-repository.sh.epp', {
command => ['/usr/bin/add-apt-repository', shell_join($options), $name],
sources_list_d_path => $apt::sources_list_d,
sources_list_d_filename => $sources_list_d_filename,
}
)
file { "add-apt-repository-script-${name}":
ensure => 'file',
path => $script_path,
content => $script_content,
mode => '0755',
}
exec { "add-apt-repository-${name}":
environment => $_proxy_env,
command => $script_path,
logoutput => 'on_failure',
notify => Class['apt::update'],
require => $_require,
before => File["${apt::sources_list_d}/${sources_list_d_filename}"],
}
}
file { "${apt::sources_list_d}/${sources_list_d_filename}": }
}
else {
tidy { "remove-apt-repository-script-${name}":
path => $script_path,
}
tidy { "remove-apt-repository-${name}":
path => "${apt::sources_list_d}/${sources_list_d_filename}",
notify => Class['apt::update'],
}
}
}

View file

@ -0,0 +1,77 @@
# @summary Manages Apt configuration files.
#
# @see https://www.puppet.com/docs/puppet/latest/types/file.html#file-attributes for more information on source and content parameters
#
# @param priority
# Determines the order in which Apt processes the configuration file. Files with higher priority numbers are loaded first.
#
# @param ensure
# Specifies whether the file should exist.
#
# @param source
# Required, unless `content` is set. Specifies a source file to supply the content of the configuration file. Cannot be used in combination
# with `content`. Valid options: see link above for Puppet's native file type source attribute.
#
# @param content
# Required, unless `source` is set. Directly supplies content for the configuration file. Cannot be used in combination with `source`. Valid
# options: see link above for Puppet's native file type content attribute.
#
# @param notify_update
# Specifies whether to trigger an `apt-get update` run.
#
define apt::setting (
Variant[String[1], Integer[0]] $priority = 50,
Enum['file', 'present', 'absent'] $ensure = file,
Optional[String[1]] $source = undef,
Optional[String[1]] $content = undef,
Boolean $notify_update = true,
) {
if $content and $source {
fail('apt::setting cannot have both content and source')
}
if $ensure != 'absent' {
if !$content and !$source {
fail('apt::setting needs either of content or source')
}
}
$title_array = split($title, '-')
$setting_type = $title_array[0]
$base_name = join(delete_at($title_array, 0), '-')
assert_type(Pattern[/\Aconf\z/, /\Apref\z/, /\Alist\z/, /\Asources\z/], $setting_type) |$a, $b| {
fail("apt::setting resource name/title must start with either 'conf-', 'pref-', 'list-', or 'sources-'")
}
if $priority !~ Integer {
# need this to allow zero-padded priority.
assert_type(Pattern[/^\d+$/], $priority) |$a, $b| {
fail('apt::setting priority must be an integer or a zero-padded integer')
}
}
if $setting_type in ['list', 'pref', 'sources'] {
$_priority = ''
} else {
$_priority = $priority
}
$_path = $apt::config_files[$setting_type]['path']
$_ext = $apt::config_files[$setting_type]['ext']
if $notify_update {
$_notify = Class['apt::update']
} else {
$_notify = undef
}
file { "${_path}/${_priority}${base_name}${_ext}":
ensure => $ensure,
owner => 'root',
group => 'root',
content => $content,
source => $source,
notify => $_notify,
}
}

View file

@ -0,0 +1,363 @@
# @summary Manages the Apt sources in /etc/apt/sources.list.d/.
#
# @example Install the puppetlabs apt source
# apt::source { 'puppetlabs':
# location => 'http://apt.puppetlabs.com',
# repos => 'main',
# key => {
# id => '6F6B15509CF8E59E6E469F327F438280EF8D349F',
# server => 'keyserver.ubuntu.com',
# },
# }
#
# @example Download key behaviour to handle modern apt gpg keyrings. The `name` parameter in the key hash should be given with
# extension. Absence of extension will result in file formation with just name and no extension.
# apt::source { 'puppetlabs':
# location => 'http://apt.puppetlabs.com',
# comment => 'Puppet8',
# key => {
# 'name' => 'puppetlabs.gpg',
# 'source' => 'https://apt.puppetlabs.com/keyring.gpg',
# },
# }
#
# @example Install the puppetlabs apt source (deb822 format)
# apt::source { 'puppetlabs':
# source_format => 'sources'
# location => ['http://apt.puppetlabs.com'],
# repos => ['puppet8'],
# keyring => '/etc/apt/keyrings/puppetlabs.gpg',
# }
#
# @param source_format
# The file format to use for the apt source. See https://wiki.debian.org/SourcesList
#
# @param location
# Required, unless ensure is set to 'absent'. Specifies an Apt repository. Valid options: a string containing a repository URL.
# DEB822: Supports an array of URL values
#
# @param types
# DEB822: The package types this source manages.
#
# @param enabled
# DEB822: Enable or Disable the APT source.
#
# @param comment
# Supplies a comment for adding to the Apt source file.
#
# @param ensure
# Specifies whether the Apt source file should exist.
#
# @param release
# Specifies a distribution of the Apt repository.
# DEB822: Supports an array of values
#
# @param repos
# Specifies a component of the Apt repository.
# DEB822: Supports an array of values
#
# @param include
# Configures include options. Valid options: a hash of available keys.
#
# @option include [Boolean] :deb
# Specifies whether to request the distribution's compiled binaries.
#
# @option include [Boolean] :src
# Specifies whether to request the distribution's uncompiled source code.
#
# @param key
# Creates an `apt::keyring` in `/etc/apt/keyrings` (or anywhere on disk given `filename`) Valid options:
# * a hash of `parameter => value` pairs to be passed to `file`: `name` (title), `content`, `source`, `filename`
#
# The following inputs are valid for the (deprecated) `apt::key` defined type. Valid options:
# * a string to be passed to the `id` parameter of the `apt::key` defined type
# * a hash of `parameter => value` pairs to be passed to `apt::key`: `id`, `server`, `content`, `source`, `weak_ssl`, `options`
#
# @param keyring
# Absolute path to a file containing the PGP keyring used to sign this repository. Value is used to set signed-by on the source entry.
# This is not necessary if the key is installed with `key` param above.
# See https://wiki.debian.org/DebianRepository/UseThirdParty for details.
#
# @param pin
# Creates a declaration of the apt::pin defined type. Valid options: a number or string to be passed to the `priority` parameter of the
# `apt::pin` defined type, or a hash of `parameter => value` pairs to be passed to `apt::pin`'s corresponding parameters.
#
# @param architecture
# Tells Apt to only download information for specified architectures. Valid options: a string containing one or more architecture names,
# separated by commas (e.g., 'i386' or 'i386,alpha,powerpc').
# (if unspecified, Apt downloads information for all architectures defined in the Apt::Architectures option)
# DEB822: Supports an array of values
#
# @param allow_unsigned
# Specifies whether to authenticate packages from this release, even if the Release file is not signed or the signature can't be checked.
#
# @param allow_insecure
# Specifies whether to allow downloads from insecure repositories.
#
# @param notify_update
# Specifies whether to trigger an `apt-get update` run.
#
# @param check_valid_until
# Specifies whether to check if the package release date is valid.
#
define apt::source (
Enum['list', 'sources'] $source_format = 'list',
Array[Enum['deb','deb-src'], 1, 2] $types = ['deb'],
Optional[Variant[String[1], Array[String[1]]]] $location = undef,
String[1] $comment = $name,
Boolean $enabled = true, # deb822
Enum['present', 'absent'] $ensure = present,
Optional[Variant[String[0], Array[String[0]]]] $release = undef,
Variant[String[1], Array[String[1]]] $repos = 'main',
Hash $include = {},
Optional[Variant[String[1], Hash]] $key = undef,
Optional[Stdlib::AbsolutePath] $keyring = undef,
Optional[Variant[Hash, Integer, String[1]]] $pin = undef,
Optional[Variant[String[1], Array[String[1]]]] $architecture = undef,
Optional[Boolean] $allow_unsigned = undef,
Optional[Boolean] $allow_insecure = undef,
Optional[Boolean] $check_valid_until = undef,
Boolean $notify_update = true,
) {
include apt
$_before = Apt::Setting["list-${title}"]
case $source_format {
'list': {
$_file_suffix = $source_format
if !$release {
if fact('os.distro.codename') {
$_release = fact('os.distro.codename')
} else {
fail('os.distro.codename fact not available: release parameter required')
}
} else {
$_release = $release
}
if $release =~ Pattern[/\/$/] {
$_components = $_release
} elsif $repos =~ Array {
$_components = join([$_release] + $repos, ' ')
} else {
$_components = "${_release} ${repos}"
}
if $ensure == 'present' {
if ! $location {
fail('cannot create a source entry without specifying a location')
}
elsif ($apt::proxy['https_acng']) and ($location =~ /(?i:^https:\/\/)/) {
$_location = regsubst($location, 'https://','http://HTTPS///')
}
else {
$_location = $location
}
} else {
$_location = undef
}
$includes = $apt::include_defaults + $include
if $keyring {
if $key {
fail('parameters key and keyring are mutually exclusive')
} else {
$_list_keyring = $keyring
}
} elsif $key {
if $key =~ Hash {
unless $key['name'] or $key['id'] {
fail('key hash must contain a key name (for apt::keyring) or an id (for apt::key)')
}
if $key['id'] {
# defaults like keyserver are only relevant to apt::key
$_key = $apt::source_key_defaults + $key
} else {
$_key = $key
}
} else {
$_key = { 'id' => assert_type(String[1], $key) }
}
if $_key['ensure'] {
$_key_ensure = $_key['ensure']
} else {
$_key_ensure = $ensure
}
# Old keyserver keys handled by apt-key
if $_key =~ Hash and $_key['id'] {
# We do not want to remove keys when the source is absent.
if $ensure == 'present' {
apt::key { "Add key: ${$_key['id']} from Apt::Source ${title}":
ensure => $_key_ensure,
id => $_key['id'],
server => $_key['server'],
content => $_key['content'],
source => $_key['source'],
options => $_key['options'],
weak_ssl => $_key['weak_ssl'],
before => $_before,
}
}
$_list_keyring = undef
}
# Modern apt keyrings
elsif $_key =~ Hash and $_key['name'] {
apt::keyring { $_key['name']:
ensure => $_key_ensure,
content => $_key['content'],
source => $_key['source'],
dir => $_key['dir'],
filename => $_key['filename'],
mode => $_key['mode'],
before => $_before,
}
$_list_keyring = if $_key['dir'] and $_key['filename'] {
"${_key['dir']}${_key['filename']}"
} elsif $_key['filename'] {
"/etc/apt/keyrings/${_key['filename']}"
} elsif $_key['dir'] {
"${_key['dir']}${_key['name']}"
} else {
"/etc/apt/keyrings/${_key['name']}"
}
}
} else {
# No `key` nor `keyring` provided
$_list_keyring = undef
}
$header = epp('apt/_header.epp')
if $architecture {
$_architecture = regsubst($architecture, '\baarch64\b', 'arm64')
} else {
$_architecture = undef
}
$source_content = epp('apt/source.list.epp', {
'comment' => $comment,
'includes' => $includes,
'options' => delete_undef_values({
'arch' => $_architecture,
'trusted' => $allow_unsigned ? { true => 'yes', false => undef, default => undef },
'allow-insecure' => $allow_insecure ? { true => 'yes', false => undef, default => undef },
'signed-by' => $_list_keyring,
'check-valid-until' => $check_valid_until? { true => undef, false => 'false', default => undef },
},
),
'location' => $_location,
'components' => $_components,
}
)
if $pin {
if $pin =~ Hash {
$_pin = $pin + { 'ensure' => $ensure, 'before' => $_before }
} elsif ($pin =~ Numeric or $pin =~ String) {
$url_split = split($location, '[:\/]+')
$host = $url_split[1]
$_pin = {
'ensure' => $ensure,
'priority' => $pin,
'before' => $_before,
'origin' => $host,
}
} else {
fail('Received invalid value for pin parameter')
}
apt::pin { $name:
* => $_pin,
}
}
}
'sources': {
$_file_suffix = $source_format
if $pin {
warning("'pin' parameter is not supported with deb822 format.")
}
if $key {
warning("'key' parameter is not supported with deb822 format.")
}
if $ensure == 'present' {
if ! $location {
fail('cannot create a source entry without specifying a location')
}
}
if $location !~ Array {
warning('For deb822 sources, location must be specified as an array.')
$_location = [$location]
} else {
$_location = $location
}
if !$release {
if fact('os.distro.codename') {
$_release = [fact('os.distro.codename')]
} else {
fail('os.distro.codename fact not available: release parameter required')
}
} elsif $release !~ Array {
warning("For deb822 sources, 'release' must be specified as an array. Converting to array.")
$_release = [$release]
} else {
$_release = $release
}
if $repos !~ Array {
warning("For deb822 sources, 'repos' must be specified as an array. Converting to array.")
$_repos = split($repos, /\s+/)
} else {
$_repos = $repos
}
if $architecture and $architecture !~ Array {
warning("For deb822 sources, 'architecture' must be specified as an array. Converting to array.")
$_architecture = split($architecture, '[,]')
} else {
$_architecture = $architecture
}
case $ensure {
'present': {
$header = epp('apt/_header.epp')
$source_content = epp('apt/source_deb822.epp', delete_undef_values({
'uris' => $_location,
'suites' => $_release,
'components' => $_repos,
'types' => $types,
'comment' => $comment,
'enabled' => $enabled ? { true => 'yes', false => 'no' },
'architectures' => $_architecture,
'allow_insecure' => $allow_insecure ? { true => 'yes', false => 'no', default => undef },
'repo_trusted' => $allow_unsigned ? { true => 'yes', false => 'no', default => undef },
'check_valid_until' => $check_valid_until ? { true => 'yes', false => 'no', default => undef },
'signed_by' => $keyring,
}
)
)
}
'absent': {
$header = undef
$source_content = undef
}
default: {
fail('Unexpected value for $ensure parameter.')
}
}
}
default: {
fail("Unexpected APT source format: ${source_format}")
}
}
apt::setting { "${_file_suffix}-${name}":
ensure => $ensure,
content => "${header}${source_content}",
notify_update => $notify_update,
}
}

View file

@ -0,0 +1,98 @@
# @summary Updates the list of available packages using apt-get update.
#
# @api private
#
class apt::update {
assert_private()
#TODO: to catch if apt_update_last_success has the value of -1 here. If we
#opt to do this, a info/warn would likely be all you'd need likely to happen
#on the first run, but if it's not run in awhile something is likely borked
#with apt and we'd want to know about it.
case $apt::_update['frequency'] {
'always': {
$_kick_apt = true
}
Integer[60]:{
#compare current date with the apt_update_last_success fact to determine
#if we should kick apt_update.
$int_threshold = (Integer(Timestamp().strftime('%s')) - Integer($apt::_update['frequency']))
if $facts['apt_update_last_success'] {
if $facts['apt_update_last_success'] + 0 < $int_threshold {
$_kick_apt = true
} else {
$_kick_apt = false
}
} else {
#if apt-get update has not successfully run, we should kick apt_update
$_kick_apt = true
}
}
'hourly':{
#compare current date with the apt_update_last_success fact to determine
#if we should kick apt_update.
$hourly_threshold = (Integer(Timestamp().strftime('%s')) - 3600)
if $facts['apt_update_last_success'] {
if $facts['apt_update_last_success'] + 0 < $hourly_threshold {
$_kick_apt = true
} else {
$_kick_apt = false
}
} else {
#if apt-get update has not successfully run, we should kick apt_update
$_kick_apt = true
}
}
'daily': {
#compare current date with the apt_update_last_success fact to determine
#if we should kick apt_update.
$daily_threshold = (Integer(Timestamp().strftime('%s')) - 86400)
if $facts['apt_update_last_success'] {
if $facts['apt_update_last_success'] + 0 < $daily_threshold {
$_kick_apt = true
} else {
$_kick_apt = false
}
} else {
#if apt-get update has not successfully run, we should kick apt_update
$_kick_apt = true
}
}
'weekly':{
#compare current date with the apt_update_last_success fact to determine
#if we should kick apt_update.
$weekly_threshold = (Integer(Timestamp().strftime('%s')) - 604800)
if $facts['apt_update_last_success'] {
if $facts['apt_update_last_success'] + 0 < $weekly_threshold {
$_kick_apt = true
} else {
$_kick_apt = false
}
} else {
#if apt-get update has not successfully run, we should kick apt_update
$_kick_apt = true
}
}
default: {
#catches 'reluctantly', and any other value (which should not occur).
#do nothing.
$_kick_apt = false
}
}
if $_kick_apt {
$_refresh = false
} else {
$_refresh = true
}
exec { 'apt_update':
command => "${apt::provider} update",
loglevel => $apt::_update['loglevel'],
logoutput => 'on_failure',
refreshonly => $_refresh,
timeout => $apt::_update['timeout'],
tries => $apt::_update['tries'],
try_sleep => 1,
}
}