When automating filesystem mounts through Puppet, we encounter a fundamental tension between mount point directory management and actual mounted filesystem attributes. The mount
resource type handles fstab entries perfectly, but the accompanying file
resource for mount point directories often causes undesired permission changes.
Here's what happens with a standard implementation:
file { "/mnt/data":
ensure => directory,
owner => "root",
group => "root",
mode => "0755",
seltype => "mnt_t"
}
mount { "/mnt/data":
ensure => mounted,
device => "/dev/sdb1",
fstype => "xfs",
options => "defaults",
require => File["/mnt/data"]
}
After mounting, Puppet will persistently try to reset the directory attributes to match the file
resource definition, regardless of the mounted filesystem's actual permissions.
For modern Puppet versions (4.8+), we can use the noop
meta-parameter for mount points:
file { "/mnt/data":
ensure => directory,
noop => true, # Puppet won't enforce attributes
before => Mount["/mnt/data"]
}
For SELinux contexts, we need a more nuanced approach:
file { "/mnt/data":
ensure => directory,
selrange => 's0',
selrole => 'object_r',
seltype => 'mnt_t',
seluser => 'system_u',
before => Mount["/mnt/data"]
}
mount { "/mnt/data":
ensure => mounted,
device => "/dev/sdb1",
fstype => "xfs",
options => "context=system_u:object_r:public_content_t:s0",
remounts => false,
subscribe => File["/mnt/data"]
}
Here's a refined version of the original implementation:
define managed_mount(
String $device,
String $fstype,
String $mountpoint,
String $options = 'defaults',
Boolean $manage_attributes = false,
Optional[String] $seltype = undef
) {
if $manage_attributes {
file { $mountpoint:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
seltype => $seltype,
before => Mount[$mountpoint]
}
} else {
file { $mountpoint:
ensure => directory,
noop => true,
before => Mount[$mountpoint]
}
}
mount { $mountpoint:
ensure => mounted,
device => $device,
fstype => $fstype,
options => $options,
atboot => true,
dump => 0,
pass => 2
}
}
For NFS mounts or special filesystems, consider these patterns:
# For NFS with specific context requirements
managed_mount { 'nfs_share':
device => 'nfs-server:/export/share',
fstype => 'nfs4',
mountpoint => '/mnt/nfs_share',
options => 'context=system_u:object_r:nfs_t:s0,ro',
seltype => 'nfs_t',
manage_attributes => false
}
# For tmpfs with size limits
managed_mount { 'tmp_work':
device => 'tmpfs',
fstype => 'tmpfs',
mountpoint => '/tmp/work',
options => 'size=1G,context=system_u:object_r:tmp_t:s0'
}
When automating mount point management with Puppet, we encounter a fundamental tension between declarative configuration and filesystem realities. The mount
resource type handles /etc/fstab
perfectly, but the file
resource for creating mount directories often clashes with actual mounted filesystem permissions.
# Problematic scenario:
file { '/mnt/data':
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755'
}
mount { '/mnt/data':
ensure => mounted,
device => '/dev/sdb1',
fstype => 'ext4',
options => 'defaults'
}
When a filesystem gets mounted, it brings its own ownership and permissions. Puppet's file resource will attempt to enforce its declared state, causing:
- Unnecessary permission changes during each Puppet run
- Potential service disruptions when mounted FS permissions differ
- SELinux context conflicts (especially problematic in RedHat systems)
Here are three battle-tested approaches:
1. The Minimalist Mount Point
define safe_mount(
$device,
$fstype,
$options = 'defaults',
$point = $title
) {
file { $point:
ensure => directory,
# Critical: Don't manage permissions
backup => false
}
mount { $point:
ensure => mounted,
device => $device,
fstype => $fstype,
options => $options,
require => File[$point]
}
}
2. SELinux-Aware Implementation
define selinux_mount(
$device,
$fstype,
$seltype = undef,
$point = "/mnt/${title}"
) {
file { $point:
ensure => directory,
seltype => $seltype, # Let mounted FS determine context if undef
before => Mount[$point]
}
mount { $point:
ensure => mounted,
device => $device,
fstype => $fstype,
options => 'context="system_u:object_r:default_t:s0"',
require => File[$point]
}
}
3. Conditional Permission Management
define smart_mount(
$device,
$fstype,
$manage_perms = false,
$point = $title
) {
$file_params = $manage_perms ? {
true => { owner => 'root', group => 'root', mode => '0755' },
default => { backup => false }
}
file { $point:
ensure => directory,
* => $file_params
}
mount { $point:
# ... standard mount params ...
require => File[$point]
}
}
For particularly tricky situations, consider these patterns:
# Only create directory if not exists, never touch afterwards
exec { "create_${mount_point}":
command => "mkdir -p ${mount_point}",
unless => "test -d ${mount_point}",
before => Mount[$mount_point]
}
# Or using modern Puppet's sensitive type
file { $mount_point:
ensure => directory,
show_diff => false,
replace => false
}
- Avoid managing mount point permissions unless absolutely necessary
- Use
backup => false
to prevent Puppet from saving original permissions - For SELinux, either set explicit context or leave undefined
- Consider using exec resources for mount point creation in complex environments
- Test thoroughly with
--noop
before applying changes