When trying to automate shell configuration management with Puppet, you might hit a fundamental limitation: Puppet's exec
type can't directly execute shell built-in commands. The source
command is particularly problematic since it's not an executable binary but a Bash internal function.
# This WON'T work:
exec { "load_bashrc":
command => "source /root/.bashrc", # Will throw "command not found"
path => ["/bin", "/usr/bin"] # Irrelevant for built-ins
}
Here are three reliable methods to achieve the same result:
Method 1: Invoke Through Bash
exec { "source_bashrc":
command => "/bin/bash -c 'source /root/.bashrc'",
subscribe => File["/root/.bashrc"],
refreshonly => true,
environment => ["HOME=/root"],
user => "root"
}
Method 2: Using Dot Notation
exec { "dot_bashrc":
command => "/bin/bash -c '. /root/.bashrc'",
subscribe => File["/root/.bashrc"],
refreshonly => true
}
Method 3: Re-executing Shell
For login shells, you might need:
exec { "reload_bashrc":
command => "exec /bin/bash --login",
subscribe => File["/root/.bashrc"],
refreshonly => true,
user => "root",
environment => ["HOME=/root"]
}
1. Always include refreshonly => true
when using subscribe
to prevent unnecessary executions.
2. For system-wide changes, consider using /etc/profile.d/
instead of modifying individual .bashrc
files.
3. Environment variables may need explicit declaration:
exec { "env_bashrc":
environment => [
"HOME=/root",
"USER=root",
"SHELL=/bin/bash"
],
...
}
For complex shell configurations, consider:
augeas { "modify_bashrc":
context => "/files/root/.bashrc",
changes => [
"set PS1 '\"\$$\\e[1;32m\$$\\u@\\h:\\w\\$\$$\\e[0m\$$ \"'",
"set PATH[. = '\$PATH'] '\$PATH:/custom/path'",
],
notify => Exec["reload_bashrc"]
}
When executing shell commands as root:
- Always specify the full path to binaries
- Consider using
onlyif
orunless
to prevent redundant executions - Audit commands with Puppet's
noop
mode first
When attempting to source ~/.bashrc
through Puppet's exec
resource, you'll encounter a fundamental limitation - Puppet executes commands directly via /bin/sh
rather than through an interactive shell session. This means built-in shell commands like source
, alias
, or cd
won't work as expected.
The error message Could not find command 'source'
occurs because:
- Shell built-ins don't exist as standalone executables
- Puppet's
exec
doesn't load the user's shell environment - The
source
command requires bash-specific functionality
Here are three proven approaches to handle .bashrc sourcing in Puppet:
1. Using the Shell Provider Explicitly
exec { "source_bashrc":
command => "/bin/bash -c 'source /root/.bashrc'",
subscribe => File["/root/.bashrc"],
refreshonly => true
}
2. Creating an Intermediate Script
file { "/usr/local/bin/refresh_bashrc":
ensure => present,
mode => 0755,
content => "#!/bin/bash\nsource /root/.bashrc"
}
exec { "run_refresh_script":
command => "/usr/local/bin/refresh_bashrc",
subscribe => File["/root/.bashrc"],
refreshonly => true
}
3. Environment Variable Alternative
exec { "update_environment":
command => "echo 'BASH_RELOADED=$(date +%s)' >> /root/.bashrc",
subscribe => File["/root/.bashrc"],
refreshonly => true
}
- Use
refreshonly => true
to prevent unnecessary executions - Consider whether immediate sourcing is truly needed (most changes don't require it)
- For system-wide changes,
/etc/profile.d/
scripts may be more appropriate
For complex scenarios, combine with Puppet's exported resources:
@@exec { "global_bashrc_refresh_${::fqdn}":
command => "/bin/bash -c 'source /root/.bashrc'",
tag => "bashrc_refresh",
refreshonly => true
}
File["/root/.bashrc"] ~> Exec["global_bashrc_refresh_${::fqdn}"]
This creates a more robust relationship between the file modification and the sourcing operation.