Page MenuHomePhorge

No OneTemporary

diff --git a/CHANGELOG b/CHANGELOG
index cfe6657bf..d615ed843 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,930 +1,934 @@
+ Fixed #989 -- missing CRL files are correctly ignored, and the
+ value should be set to 'false' to explicitly not look for these
+ files.
+
Fixed #1017 -- environment-specific modulepath is no longer ignored.
Fixing #794 -- consolidating the gentoo configuration files.
Fixing #976 -- both the full name of qualified classes and
the class parts are now added as tags. I've also
created a Tagging module that we should push throughout
the rest of the system that uses tags.
Fixing #995 -- puppetd no longer dies at startup if the server
is not running.
Fixing #977 -- the rundir is again set to 1777.
Fixed #971 -- classes can once again be included multiple
times.
Added builtin support for Nagios types using
Naginator to parse and generate the files.
0.24.1
Updated vim filetype detection. (#900 and #963)
Default resources like schedules no longer conflict with
managed resources. (#965)
Removing the ability to disable http keep-alive, since
it didn't really work anyway and it should no longer
be necessary.
Refactored http keep-alive so it actually works again.
This should be sufficient enough that we no longer need the
ability to disable keep-alive. There is now a central
module responsible for managing HTTP instances, along with
all certificates in those instances.
Fixed a backward compatibility issue when running 0.23.x
clients against 0.24.0 servers -- relationships would
consistently not work. (#967)
Closing existing http connections when opening a new one,
and closing all connections after each run. (#961)
Removed warning about deprecated explicit plugins mounts.
0.24.0 (misspiggy)
Modifying the behaviour of the certdnsnames setting. It now defaults
to an empty string, and will only be used if it is set to something
else. If it is set, then the host's FQDN will also be added as
an alias. The default behaviour is now to add 'puppet' and
'puppet.$domain' as DNS aliases when the name for the cert being
signed is equal to the signing machine's name, which will only
be the case for CA servers. This should result in servers always
having the alias set up and no one else, but you can still override
the aliases if you want.
External node support now requires that you set the 'node_terminus'
setting to 'exec'. See the IndirectionReference on the wiki for more
information.
http_enable_post_connection_check added as a configuration
option for puppetd. This defaults to true, which validates the server
SSL certificate against the requested host name in new versions of ruby.
See #896 for more information.
Mounts no longer remount swap filesystems.
Slightly modifying how services manage their list of paths
(and adding documention for it). Services now default
to the paths specified by the provider classes.
Removed 'type' as a valid attribute for services, since it's been
deprecated since the creation of providers.
Removed 'running' as a valid attribute for services, since it's
been deprecated since February 2006.
Added modified patch by Matt Palmer which adds a 'plugins' mount,
fixing #891. See PluginsInModules on the wiki for information on
usage.
Empty dbserver and dbpassword settings will now be ignored when
initializing Rails connections (patch by womble).
Configuration settings can now be blank (patch by womble).
Added calls to endpwent/endgrent when searching for user and group IDs,
which fixes #791.
Obviated 'target' in interfaces, as all file paths were automatically
calculated anyway. The parameter is still there, but it's
not used and just generates a warning.
Fixing some of the problems with interface management on Red Hat.
Puppet now uses the :netmask property and does not try to set
the bootproto (#762).
You now must specify an environment and you are required to specify
the valid environments for your site. (#911)
Certificates now always specify a subjectAltName, but it defaults
to '*', meaning that it doesn't require DNS names to match. You
can override that behaviour by specifying a value for
'certdnsnames', which will then require that hostname as a match (#896).
Relationship metaparams (:notify, :require, :subscribe, and
:before) now stack when they are collecting metaparam values
from their containers (#446). For instance, if a resource
inside a definition has a value set for 'require', and you call
the definition with 'require', the resource gets both requires,
where before it would only retain its initial value.
Changed the behavior of --debug to include Mongrel client
debugging information. Mongrel output will be written to
the terminal only, not to the puppet debug log. This should
help anyone working with reverse HTTP SSL proxies. (#905)
Fixed #800 -- invalid configurations are no longer
cached. This was done partially by adding a relationship
validation step once the entire configuration is created,
but it also required the previously-mentioned changes
to how the configuration retrieval process works.
Removed some functionality from the Master client,
since the local functionality has been replaced
with the Indirector already, and rearranging how configuration
retrieval is done to fix ordering and caching bugs.
The node scope is now above all other scopes besides
the 'main' scope, which should help make its variables
visible to other classes, assuming those classes were
not included in the node's parent.
Replaced GRATR::Digraph with Puppet::SimpleGraph as
the base class for Puppet's graphing. Functionality
should be equivalent but with dramatically better
performance.
The --use-nodes and --no-nodes options are now obsolete.
Puppet automatically detects when nodes are defined, and if
they are defined it will require that a node be found,
else it will not look for a node nor will it fail if it
fails to find one.
Fixed #832. Added the '--no-daemonize' option to puppetd and
puppetmasterd. NOTE: The default behavior of 'verbose' and
'debug' no longer cause puppetd and puppetmasterd to not
daemonize.
Added k5login type. (#759)
Fixed CA race condition. (#693)
Added shortname support to config.rb and refactored addargs
0.23.2
Fixed the problem in cron jobs where environment settings
tended to multiple. (#749)
Collection of resources now correctly only collects exported
resources again. This was broken in 0.23.0. (#731)
'gen_config' now generates a configuration with
all parameters under a heading that matches the
process name, rather than keeping section headings.
Refactored how the parser and interpreter relate,
so parsing is now effectively an atomic process (thus
fixing #314 and #729). This makes the interpreter less
prone to error and less prone to show the error to the
clients. Note that this means that if a configuration
fails to parse, then the previous, parseable configuration
will be used instead, so the client will not know that
the configuration failed to parse.
Added support for managing interfaces, thanks to work
by Paul Rose.
Fixed #652, thanks to a patch by emerose; --fqdn again
works with puppetd.
Added an extra check to the Mongrel support so that
Apache can be used with optional cert checking, instead
of mandatory, thus allowing Mongrel to function as the CA.
This is thanks to work done by Marcin Owsiany.
0.23.1 (beaker)
You can now specify relationships to classes, which work
exactly like relationships to defined types:
require => Class[myclass]
This works with qualified classes, too.
You can now do simple queries in a collection of
exported resources. You still cannot do multi-condition queries,
though. (#703)
puppetca now exits with a non-zero code if it cannot
find any host certificates to clean. (Patch by Dean
Wilson.)
Fully-qualified resources can now have defaults. (#589)
Resource references can now be fully-qualified names,
meaning you can list definitions with a namespace as
dependencies. (#468)
Files modified using a FileType instance, as ParsedFile
does, will now automatically get backed up to the filebucket
named "puppet".
Added a 'maillist' type for managing mailing lists.
Added a 'mailalias' type for managing mail aliases.
Added patch by Valentin Vidic that adds the '+>' syntax to
resources, so parameter values can be added to.
The configuration client now pulls libraries down to $libdir,
and all autoloading is done from there with full support
for any reloadable file, such as types and providers. (#621)
Note that this is not backward compatible -- if you're using
pluginsync right now, you'll need to disable it on your clients
until you can upgrade them.
The Rails log level can now be set via (shockingly!) the
'rails_loglevel' parameter (#710). Note that this isn't
exactly the feature asked for, but I could not find a
way to directly copy ActiveRecord's concept of an environment.
External node sources can now return undefined classes (#687).
Puppet clients now have http proxy support (#701).
The parser now throws an error when a resource reference
is created for an unknown type. Also, resource references
look up defined types and translate their type accordingly. (#706)
Hostnames can now be double quoted.
Adding module autoloading (#596) -- you can now 'include' classes
from modules without ever needing to specifically load them.
Class names and node names now conflict (#620).
0.23.0
Modified the fileserver to cache file information, so that
each file isn't being read on every connection. Also,
added londo's patch from #678 to avoid reading entire files
into memory.
Fixed environment handling in the crontab provider (#669).
Added patch by trombik in #572, supporting old-style
freebsd init scripts with '.sh' endings.
Added fink package provider (#642), as provided by 'do'.
Marked the dpkg package provider as versionable (#647).
Applied patches by trombik to fix FreeBSD ports (#624 and #628).
Fixed the CA server so that it refuses to send back a certificate
whose public key doesn't match the CSR. Instead, it tells the
user to run 'puppetca --clean'.
Invalid certificates are no longer written to disk (#578).
Added a package provider (appdmg) able to install .app packages
on .dmg files on OS X (#641).
Applied the patch from #667 to hopefully kill the client hanging
problems (permanently, this time).
Fixed functions so that they accept most other rvalues as valid values
(#548).
COMPATIBILITY ALERT:
Significantly reworked external node support, in a way that's NOT
backward-compatible:
Only ONE node source can be used -- you can use LDAP, code, or
an external node program, but not more than one.
LDAP node support has two changes: First, the "ldapattrs" attribute is
now used for setting the attributes to retrieve from the server (in
addition to required attriutes), and second, all retrieved attributes
are set as variables in the top scope. This means you can set attributes
on your LDAP nodes and they will automatically appear as variables
in your configurations.
External node support has been completely rewritten. These programs must
now generate a YAML dump of a hash, with "classes" and "parameters" keys.
The classes should be an array, and the parameters should be a hash. The
external node program has no support for parent nodes -- the script must
handle that on its own.
Reworked the database schema used to store configurations with the
storeconfigs option.
Replaced the obsolete RRD ruby library with the maintained
RubyRRDtool library (which requires rrdtool2) (#659).
The Portage package provider now calls eix-update automatically
when eix's database is absent or out of sync (#666).
Mounts now correctly handle existing fstabs with no pass or dump values
(#550).
Mounts now default to 0 for pass and dump (#112).
Added urpmi support (#592).
Finishing up the type => provider interface work. Basically, package
providers now return lists of provider instances. In the proces,
I rewrote the interface between package types and providers, and also
enabled prefetching on all packages. This should significantly speed
up most package operations.
Hopefully fixing the file descriptor/open port problems, with patches
from Valentin Vidic.
Significantly reworked the type => provider interface with respect to
listing existing provider instances. The class method on both
class heirarchies has been renamed to 'instances', to start. Providers
are now expected to return provider instances, instead of creating
resources, and the resource's 'instances' method is expected to
find the matching resource, if any, and set the resource's
provider appropriately. This *significantly* reduces the reliance on
effectively global state (resource references in the resource classes).
This global state will go away soon.
Along with this change, the 'prefetch' class method on providers now
accepts the list of resources for prefetching. This again reduces
reliance on global state, and makes the execution path much easier
to follow.
Fixed #532 -- reparsing config files now longer throws an exception.
Added some warnings and logs to the service type so
users will be encouraged to specify either "ensure"
or "enabled" and added debugging to indicate why
restarting is skipped when it is.
Changed the location of the classes.txt to the state
directory.
Added better error reporting on unmatched brackets.
Moved puppetd and puppetmasterd to sbin in svn and fixed install.rb
to copy them into sbin on the local system appropriately. (#323)
Added a splay option (#501). It's disabled when running under
--test in puppetd. The value is random but cached. It defaults
to the runinterval but can be tuned with --splaylimit
Changing the notify type so that it always uses
the loglevel.
Fixing #568 - nodes can inherit from quoted node names.
Tags (and thus definitions and classes) can now be a single
character. (#566)
Added an 'undef' keyword (#629), which will evaluate to ""
within strings but when used as a resource parameter value
will cause that parameter to be evaluated as undefined.
Changed the topological sort algorithm (#507) so it will always
fail on cycles.
Added a 'dynamicfacts' configuration option; any facts in that
comma-separated list will be ignored when comparing facts to
see if they have changed and thus whether a recompile is necessary.
Renamed some poorly named internal variables:
@models in providers are now either @resource or
@resource_type (#605).
@children is no longer used except by components (#606).
@parent is now @resource within parameters (#607).
The old variables are still set for backward compatibility.
Significantly reworking configuration parsing. Executables all now
look for 'puppet.conf' (#206), although they will parse the old-style
configuration files if they are present, although they throw a deprecation
warning. Also, file parameters (owner, mode, group) are now set on the
same line as the parameter, in brackets. (#422)
Added transaction summaries (available with the --summarize option),
useful for getting a quick idea of what happened in a transaction.
Currently only useful on the client or with the puppet interpreter.
Changed the interal workings for retrieve and removed the :is attribute
from Property. The retrieve methods now return the current value of
the property for the system.
Removed acts_as_taggable from the rails models.
0.22.4
Execs now autorequire the user they run as, as long as the user
is specified by name. (#430)
Files on the local machine but not on the remote server during
a source copy are now purged if purge => true. (#594)
Providers can now specify that some commands are optional (#585).
Also, the 'command' method returns nil on missing commands,
rather than throwing an error, so the presence of commands
be tested.
The 'useradd' provider for Users can now manage passwords.
No other providers can, at this point.
Parameters can now declare a dependency on specific
features, and parameters that require missing features
will not be instantiated. This is most useful for
properties.
FileParsing classes can now use instance_eval to add
many methods at once to a record type.
Modules no longer return directories in the list of found
manifests (#588).
The crontab provider now defaults to root when there is no
USER set in the environment.
Puppetd once again correctly responds to HUP.
Added a syntax for referring to variables defined in
other classes (e.g., $puppet::server).
STDIN, STDOUT, STDERR are now redirected to /dev/null in
service providers descending from base.
Certificates are now valid starting one day before they are
created, to help handle small amounts of clock skew.
Files are no longer considered out of sync if some properties
are out of sync but they have no properties that can create
the file.
0.22.3
Fixed backward compatibility for logs and metrics from older clients.
Fixed the location of the authconfig parameters so there aren't
loading order issues.
Enabling attribute validation on the providers that subclass
'nameservice', so we can verify that an integer is passed to
UID and GID.
Added a stand-alone filebucket client, named 'filebucket'.
Fixed the new nested paths for filebuckets; the entire md5 sum was
not being stored.
Fixing #553; -M is no longer added when home directories are being
managed on Red Hat.
0.22.2 (grover)
Users can now manage their home directories, using the managehome
parameter, partially using patches provided by Tim Stoop and
Matt Palmer. (#432)
Added 'ralsh' (formerly x2puppet) to the svn tree. When possible it
should be added to the packages.
The 'notify' type now defaults to its message being the same as its name.
Reopening $stdin to read from /dev/null during execution, in hopes that
init scripts will stop hanging.
Changed the 'servername' fact set on the server to use the server's fqdn,
instead of the short-name.
Changing the location of the configuration cache. It now defaults to being
in the state directory, rather than in the configuration directory.
All parameter instances are stored in a single @parameters instance variable
hash within resource type instances. We used to use separate hashes for
each parameter type.
Added the concept of provider features. Eventually these should be able
to express the full range of provider functionality, but for now they can
test a provider to see what methods it has set and determine what features it
provides as a result. These features are integrated into the doc generation
system so that you get feature documentation automatically.
Switched apt/aptitide to using "apt-cache policy" instead of "apt-cache showpkg"
for determining the latest available version. (#487)
FileBuckets now use a deeply nested structure for storing files, so
you do not end up with hundreds or thousands of files in the same
directory. (#447)
Facts are now cached in the state file, and when they change the configuration
is always recompiled. (#519)
Added 'ignoreimport' setting for use in commit hooks. This causes the
parser to ignore import statements so a single file can be parse-checked. (#544)
Import statements can now specify multiple comma-separated arguments.
Definitions now support both 'name' and 'title', just like any other
resource type. (#539)
Added a generate() command, which sets values to the result of an external
command. (#541)
Added a file() command to read in files with no interpolation. The first
found file has its content returned.
puppetd now exits if no cert is present in onetime mode. (#533)
The client configuration cache can be safely removed and the client
will correctly realize the client is not in sync.
Resources can now be freely deleted, thus fixing many problems introduced
when deletion of required resources was forbidden when purging was introduced.
Only resources being purged will not be deleted.
Facts and plugins now download even in noop mode (#540).
Resources in noop mode now log when they would have responded to an event (#542).
Refactored cron support entirely. Cron now uses providers, and there
is a single 'crontab' provider that handles user crontabs. While this
refactor does not include providers for /etc/crontab or cron.d, it should
now be straightforward to write those providers.
Changed the parameter sorting so that the provider parameter comes
right after name, so the provider is available when the other parameters
and properties are being created.
Redid some of the internals of the ParsedFile provider base class.
It now passes a FileRecord around instead of a hash.
Fixing a bug related to link recursion that caused link directories
to always be considered out of sync.
The bind address for puppetmasterd can now be specified with
--bindaddress.
Added (probably experimental) mongrel support. At this point you're
still responsible for starting each individual process, and you have to
set up a proxy in front of it.
Redesigned the 'network' tree to support multiple web servers, including
refactoring most of the structural code so it's much clearer and more
reusable now.
Set up the CA client to default to ca_server and ca_port, so you can
easily run a separate CA.
Supporting hosts with no domain name, thanks to a patch from
Dennis Jacobfeuerborn.
Added an 'ignorecache' option to tell puppetd to force a recompile, thanks to
a patch by Chris McEniry.
Made up2date the default for RHEL < 4 and yum the default for the rest.
The yum provider now supports versions.
Case statements correctly match when multiple values are provided,
thanks to a patch by David Schmitt.
Functions can now be called with no arguments.
String escapes parse correctly in all cases now, thanks to a patch by
cstorey.
Subclasses again search parent classes for defaults.
You can now purge apt and dpkg packages.
When doing file recursion, 'ensure' only affects the top-level directory.
States have been renamed to Properties.
0.22.1 (kermit) -- Mostly a bugfix release
Compile times now persist between restarts of puppetd.
Timeouts have been added to many parts of Puppet, reducing the likelihood
if it hanging forever on broken scripts or servers.
All of the documentation and recipes have been moved to the wiki by Peter
Abrahamsen and Ben Kite has moved the FAQ to the wiki.
Explicit relationships now override automatic relationships, allowing you
to manually specify deletion order when removing resources.
Resources with dependencies can now be deleted as long as all of their
dependencies are also being deleted.
Namespaces for both classes and definitions now work much more consistently.
You should now be able to specify a class or definition with a namespace
everywhere you would normally expect to be able to specify one without.
Downcasing of facts can be selectively disabled.
Cyclic dependency graphs are now checked for and forbidden.
The netinfo mounts provider was commented out, because it really doesn't
work at all. Stupid NetInfo stores mount information with the device as
the key, which doesn't work with my current NetInfo code.
Otherwise, lots and lots of bugfixes. Check the tickets associated with the
'kermit' milestone.
0.22.0
Integrated the GRATR graph library into Puppet, for handling resource
relationships.
Lots of bug-fixes (see bugs tickets associated with the 'minor' milestone).
Added new 'resources' metatype, which currently only includes the ability
to purge unmanaged resources.
Added better ability to generate new resource objects during transactions
(using 'generate' and 'eval_generate' methods).
Rewrote all Rails support with a much better database design. Export/collect
now works, although the database is incompatible with previous versions.
Removed downcasing of facts and made most of the language case-insensitive.
Added support for printing the graphs built during transactions.
Reworked how paths are built for logging.
Switched all providers to directly executing commands instead of going through
a subshell, which removes the need to quote or escape arguments.
0.20.1
Mostly a bug-fix release, with the most important fix being the
multiple-definition error.
Completely rewrote the ParsedFile system; each provider is now much
shorter and much more maintainable. However, fundamental problems
were found with the 'port' type, so it was disabled. Also, added
a NetInfo provider for 'host' and an experimental NetInfo provider
for 'mount'.
Made the RRDGraph report *much* better and added reference
generation for reports and functions.
0.20.0
Significantly refactored the parser. Resource overrides now consistently
work anywhere in a class hierarchy.
The language was also modified somewhat. The previous export/collect syntax
is now used for handling virtual objects, and export/collect (which is still
experimental) now uses double sigils (@@ and <<| |>>).
Resource references (e.g., File["/etc/passwd"]) now have to be capitalized,
in fitting in with capitalizing type operations.
As usual, lots of other smaller fixes, but most of the work was in the language.
0.19.3
Fixing a bug in server/master.rb that causes the hostname
not to be available in locally-executed manifests.
0.19.2
Fixing a few smaller bugs, notably in the reports system.
Refreshed objects now generate an event, which can result in further
refreshes of other objects.
0.19.1
Fixing two critical bugs: User management works again and cron jobs are
no longer added to all user accounts.
0.19.0
Added provider support.
Added support for %h, %H, and %d expansion in fileserver.conf.
Added Certificate Revocation support.
Made dynamic loading pervasive -- nearly every aspect of Puppet will now
automatically load new instances (e.g., types, providers, and reports).
Added support for automatic distribution of facts and plugins (custom types).
0.18.4
Another bug-fix release. The most import bug fixed is that
cronjobs again work even with initially empty crontabs.
0.18.3
Mostly a bug-fix release; fixed small bugs in the functionality added in
0.18.2.
0.18.2
Added templating support.
Added reporting.
Added gem and blastwave packaging support.
0.18.1
Added signal handlers for HUP, so both client and server deal correctly with it.
Added signal handler for USR1, which triggers a run on the client.
As usual, fixed many bugs.
Significant fixes to puppetrun -- it should behave much more correctly now.
Added "fail" function which throws a syntax error if it's encountered.
Added plugin downloading from the central server to the client. It must be
enabled with --pluginsync.
Added support for FreeBSD's special "@daily" cron schedules.
Correctly handling spaces in file sources.
Moved documentation into svn tree.
0.18.0
Added support for a "default" node.
When multiple nodes are specified, they must now be comma-separated (this
introduces a language incompatibility).
Failed dependencies cause dependent objects within the same transaction
not to run.
Many updates to puppetrun
Many bug fixes
Function names are no longer reserved words.
Links can now replace files.
0.17.2
Added "puppetrun" application and associated runner server and client classes.
Fixed cron support so it better supports valid values and environment settings.
0.17.1
Fixing a bug requiring rails on all Debian boxes
Fixing a couple of other small bugs
0.17.0
Adding ActiveRecord integration on the server
Adding export/collect functionality
Fixing many bugs
0.16.5
Fixing a critical bug in importing classes from other files
Fixing nodename handling to actually allow dashes
0.16.4
Fixing a critical bug in puppetd when acquiring a certificate for the first
time
0.16.3
Some significant bug fixes
Modified puppetd so that it can now function as an agent independent
of a puppetmasterd process, e.g., using the PuppetShow web application.
0.16.2
Modified some of the AST classes so that class names, definition names, and
node names are all set within the code being evaluated, so 'tagged(name)' returns
true while evaluating 'name', for instance.
Added '--clean' argument to puppetca to remove all traces of a given
client.
0.16.1
Added 'tagged' and 'defined' functions.
Moved all functions to a general framework that makes it very easy to add new
functions.
0.16.0
Added 'tag' keyword/function.
Added FreeBSD Ports support
Added 'pelement' server for sending or receiving Puppet objects, although
none of the executables use it yet.
0.15.3
Fixed many bugs in :exec, including adding support for arrays of checks
Added autoloading for types and service variants (e.g., you can now
just create a new type in the appropriate location and use it in Puppet,
without modifying the core Puppet libs).
0.15.2
Added darwinport, Apple .pkg, and freebsd package types
Added 'mount type
Host facts are now set at the top scope (Bug #103)
Added -e (inline exection) flag to 'puppet' executable
Many small bug fixes
0.15.1
Fixed 'yum' installs so that they successfully upgrade packages.
Fixed puppetmasterd.conf file so group settings take.
0.15.0
Upped the minor release because the File server is incompatible with 0.14,
because it now handles links.
The 'symlink' type is deprecated (but still present), in favor of using
files with the 'target' parameter.
Unset variables no longer throw an error, they just return an empty string
You can now specify tags to restrict which objects run during a given run.
You can also specify to skip running against the cached copy when there's
a failure, which is useful for testing new configurations.
RPMs and Sun packages can now install, as long as they specify a package
location, and they'll automatically upgrade if you point them to a new
file with an upgrade.
Multiple bug fixes.
0.14.1
Fixed a couple of small logging bugs
Fixed a bug with handling group ownership of links
0.14.0
Added some ability to selectively manage symlinks when doing file management
Many bug fixes
Variables can now be used as the test values in case statements and selectors
Bumping a minor release number because 0.13.4 introduced a protocol
incompatibility and should have had a minor rev bump
0.13.6
Many, many small bug fixes
FreeBSD user/group support has been added
The configuration system has been rewritten so that daemons can now generate
and repair the files and directories they need. (Fixed bug #68.)
Fixed the element override issues; now only subclasses can override values.
0.13.5
Fixed packages so types can be specified
Added 'enable' state to services, although it does not work everywhere yet
0.13.4
A few important bug fixes, mostly in the parser.
0.13.3
Changed transactions to be one-stage instead of two
Changed all types to use self[:name] instead of self.name, to support
the symbolic naming implemented in 0.13.1
0.13.2
Changed package[answerfile] to package[adminfile], and added package[responsefile]
Fixed a bunch of internal functions to behave more consistently and usefully
0.13.1
Fixed RPM spec files to create puppet user and group (lutter)
Fixed crontab reading and writing (luke)
Added symbolic naming in the language (luke)
0.13.0
Added support for configuration files.
Even more bug fixes, including the infamous 'frozen object' bug, which was a
problem with 'waitforcert'.
David Lutterkort got RPM into good shape.
0.12.0
Added Scheduling, and many bug fixes, of course.
0.11.2
Fixed bugs related to specifying arrays of requirements
Fixed a key bug in retrieving checksums
Fixed lots of usability bugs
Added 'fail' methods that automatically add file and line info when possible,
and converted many errors to use that method
0.11.1
Fixed bug with recursive copying with 'ignore' set.
Added OpenBSD package support.
0.11.0
Added 'ensure' state to many elements.
Modified puppetdoc to correctly handle indentation and such.
Significantly rewrote much of the builtin documentation to take advantage
of the new features in puppetdoc, including many examples.
0.10.2
Added SMF support
Added autorequire functionality, with specific support for exec and file
Exec elements autorequire any mentioned files, including the scripts,
along with their CWDs.
Files autorequire any parent directories.
Added 'alias' metaparam.
Fixed dependencies so they don't depend on file order.
0.10.1
Added Solaris package support and changed puppetmasterd to run as
a non-root user.
0.10.0
Significant refactoring of how types, states, and parameters work, including
breaking out parameters into a separate class. This refactoring did not
introduce much new functionality, but made extension of Puppet significantly
easier
Also, fixed the bug with 'waitforcert' in puppetd.
0.9.4
Small fix to wrap the StatusServer class in the checks for required classes.
0.9.3
Fixed some significant bugs in cron job management.
0.9.2
Second Public Beta
0.9.0
First Public Beta
diff --git a/bin/puppetd b/bin/puppetd
index 297d4876d..e993d3aa8 100755
--- a/bin/puppetd
+++ b/bin/puppetd
@@ -1,451 +1,451 @@
#!/usr/bin/env ruby
# == Synopsis
#
# Retrieve the client configuration from the central puppet server and apply
# it to the local host.
#
# Currently must be run out periodically, using cron or something similar.
#
# = Usage
#
# puppetd [-D|--daemonize|--no-daemonize] [-d|--debug] [--disable] [--enable]
# [-h|--help] [--fqdn <host name>] [-l|--logdest syslog|<file>|console]
# [-o|--onetime] [--serve <handler>] [-t|--test]
# [-V|--version] [-v|--verbose] [-w|--waitforcert <seconds>]
#
# = Description
#
# This is the main puppet client. Its job is to retrieve the local machine's
# configuration from a remote server and apply it. In order to successfully
# communicate with the remote server, the client must have a certificate signed
# by a certificate authority that the server trusts; the recommended method
# for this, at the moment, is to run a certificate authority as part of the
# puppet server (which is the default). The client will connect and request
# a signed certificate, and will continue connecting until it receives one.
#
# Once the client has a signed certificate, it will retrieve its configuration
# and apply it.
#
# = Usage Notes
#
# +puppetd+ does its best to find a compromise between interactive use and
# daemon use. Run with no arguments and no configuration, it will go into the
# backgroun, attempt to get a signed certificate, and retrieve and apply its
# configuration every 30 minutes.
#
# Some flags are meant specifically for interactive use -- in particular,
# +test+ and +tags+ are useful. +test+ enables verbose logging, causes
# the daemon to stay in the foreground, exits if the server's configuration is
# invalid (this happens if, for instance, you've left a syntax error on the
# server), and exits after running the configuration once (rather than hanging
# around as a long-running process).
#
# +tags+ allows you to specify what portions of a configuration you want to apply.
# Puppet elements are tagged with all of the class or definition names that
# contain them, and you can use the +tags+ flag to specify one of these names,
# causing only configuration elements contained within that class or definition
# to be applied. This is very useful when you are testing new configurations --
# for instance, if you are just starting to manage +ntpd+, you would put all of
# the new elements into an +ntpd+ class, and call puppet with +--tags ntpd+,
# which would only apply that small portion of the configuration during your
# testing, rather than applying the whole thing.
#
# = Options
#
# Note that any configuration parameter that's valid in the configuration file
# is also a valid long argument. For example, 'server' is a valid configuration
# parameter, so you can specify '--server <servername>' as an argument.
#
# See the configuration file documentation at
# http://reductivelabs.com/projects/puppet/reference/configref.html for
# the full list of acceptable parameters. A commented list of all
# configuration options can also be generated by running puppetd with
# '--genconfig'.
#
# daemonize::
# Send the process into the background. This is the default.
#
# no-daemonize::
# Do not send the process into the background.
#
# debug::
# Enable full debugging.
#
# disable::
# Disable working on the local system. This puts a lock file in place,
# causing +puppetd+ not to work on the system until the lock file is removed.
# This is useful if you are testing a configuration and do not want the central
# configuration to override the local state until everything is tested and
# committed.
#
# +puppetd+ uses the same lock file while it is running, so no more than one
# +puppetd+ process is working at a time.
#
# +puppetd+ exits after executing this.
#
# enable::
# Enable working on the local system. This removes any lock file, causing
# +puppetd+ to start managing the local system again (although it will continue
# to use its normal scheduling, so it might not start for another half hour).
#
# +puppetd+ exits after executing this.
#
# fqdn::
# Set the fully-qualified domain name of the client. This is only used for
# certificate purposes, but can be used to override the discovered hostname.
# If you need to use this flag, it is generally an indication of a setup problem.
#
# help::
# Print this help message
#
# logdest::
# Where to send messages. Choose between syslog, the console, and a log file.
# Defaults to sending messages to syslog, or the console if debugging or
# verbosity is enabled.
#
# no-client::
# Do not create a config client. This will cause the daemon to run
# without ever checking for its configuration automatically, and only
# makes sense when used in conjunction with --listen.
#
# onetime::
# Run the configuration once, rather than as a long-running daemon. This is
# useful for interactively running puppetd.
#
# serve::
# Start another type of server. By default, +puppetd+ will start
# a service handler that allows authenticated and authorized remote nodes to
# trigger the configuration to be pulled down and applied. You can specify
# any handler here that does not require configuration, e.g., filebucket, ca,
# or resource. The handlers are in +lib/puppet/network/handler+, and the names
# must match exactly, both in the call to +serve+ and in +namespaceauth.conf+.
#
# test::
# Enable the most common options used for testing. These are +onetime+,
# +verbose+, +ignorecache, and +no-usecacheonfailure+.
#
# verbose::
# Turn on verbose reporting.
#
# version::
# Print the puppet version number and exit.
#
# waitforcert::
# This option only matters for daemons that do not yet have certificates
# and it is enabled by default, with a value of 120 (seconds). This causes
# +puppetd+ to connect to the server every 2 minutes and ask it to sign a
# certificate request. This is useful for the initial setup of a puppet
# client. You can turn off waiting for certificates by specifying a time
# of 0.
#
# = Example
#
# puppetd --server puppet.domain.com
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005, 2006 Reductive Labs, LLC
# Licensed under the GNU Public License
# Do an initial trap, so that cancels don't get a stack trace.
trap(:INT) do
$stderr.puts "Cancelling startup"
exit(0)
end
require 'puppet'
require 'puppet/network/client'
require 'getoptlong'
options = [
[ "--centrallogging", GetoptLong::NO_ARGUMENT ],
[ "--disable", GetoptLong::NO_ARGUMENT ],
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
[ "--enable", GetoptLong::NO_ARGUMENT ],
[ "--fqdn", "-f", GetoptLong::REQUIRED_ARGUMENT ],
[ "--help", "-h", GetoptLong::NO_ARGUMENT ],
[ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ],
[ "--onetime", "-o", GetoptLong::NO_ARGUMENT ],
[ "--test", "-t", GetoptLong::NO_ARGUMENT ],
[ "--serve", "-s", GetoptLong::REQUIRED_ARGUMENT ],
[ "--no-client", GetoptLong::NO_ARGUMENT ],
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT ],
[ "--version", "-V", GetoptLong::NO_ARGUMENT ],
[ "--waitforcert", "-w", GetoptLong::REQUIRED_ARGUMENT ]
]
# Add all of the config parameters as valid options.
Puppet.settings.addargs(options)
result = GetoptLong.new(*options)
args = {}
options = {
:waitforcert => 120, # Default to checking for certs every 5 minutes
:onetime => false,
:centrallogs => false,
:setdest => false,
:enable => false,
:disable => false,
:client => true,
:fqdn => nil,
:serve => {}
}
begin
explicit_waitforcert = false
result.each { |opt,arg|
case opt
# First check to see if the argument is a valid configuration parameter;
# if so, set it. NOTE: there is a catch-all at the bottom for defaults.rb
when "--disable"
options[:disable] = true
when "--serve"
if Puppet::Network::Handler.handler(arg)
options[:serve][arg.to_sym] = {}
else
raise "Could not find handler for %s" % arg
end
when "--enable"
options[:enable] = true
when "--test"
options[:test] = true
when "--centrallogging"
options[:centrallogs] = true
when "--help"
if Puppet.features.usage?
RDoc::usage && exit
else
puts "No help available unless you have RDoc::usage installed"
exit
end
when "--version"
puts "%s" % Puppet.version
exit
when "--verbose"
Puppet::Util::Log.level = :info
Puppet::Util::Log.newdestination(:console)
when "--debug"
Puppet::Util::Log.level = :debug
Puppet::Util::Log.newdestination(:console)
when "--fqdn"
options[:fqdn] = arg
when "--no-client"
options[:client] = false
when "--onetime"
options[:onetime] = true
options[:waitforcert] = 0 unless explicit_waitforcert
when "--port"
args[:Port] = arg
when "--logdest"
begin
Puppet::Util::Log.newdestination(arg)
options[:setdest] = true
rescue => detail
$stderr.puts detail.to_s
end
when "--waitforcert"
options[:waitforcert] = arg.to_i
explicit_waitforcert = true
else
Puppet.settings.handlearg(opt, arg)
end
}
rescue GetoptLong::InvalidOption => detail
$stderr.puts detail
$stderr.puts "Try '#{$0} --help'"
exit(1)
end
# Now parse the config
Puppet.parse_config
if options[:test]
# Enable all of the most common test options.
Puppet.settings.handlearg("--ignorecache")
Puppet.settings.handlearg("--no-usecacheonfailure")
Puppet.settings.handlearg("--no-splay")
Puppet.settings.handlearg("--show_diff")
Puppet.settings.handlearg("--no-daemonize")
options[:onetime] = true
options[:waitforcert] = 0
unless Puppet::Util::Log.level == :debug
Puppet::Util::Log.level = :info
end
Puppet::Util::Log.newdestination(:console)
end
Puppet.genconfig
Puppet.genmanifest
# If noop is set, then also enable diffs
if Puppet[:noop]
Puppet[:show_diff] = true
end
unless options[:setdest]
Puppet::Util::Log.newdestination(:syslog)
end
args[:Server] = Puppet[:server]
if options[:fqdn]
args[:FQDN] = options[:fqdn]
Puppet[:certname] = options[:fqdn]
end
if options[:centrallogs]
logdest = args[:Server]
if args.include?(:Port)
logdest += ":" + args[:Port]
end
Puppet::Util::Log.newdestination(logdest)
end
# We need tomake the client either way, we just don't start it
# if --no-client is set.
client = Puppet::Network::Client.master.new(args)
if options[:enable]
client.enable
elsif options[:disable]
client.disable
end
if options[:enable] or options[:disable]
exit(0)
end
server = nil
# It'd be nice to daemonize later, but we have to daemonize before the
# waitforcert happens.
if Puppet[:daemonize]
client.daemonize
end
unless Puppet::Network::HttpPool.read_cert
# If we don't already have the certificate, then create a client to
# request one. Use the special ca stuff, don't use the normal server and port.
caclient = Puppet::Network::Client.ca.new()
if options[:waitforcert] > 0
begin
while ! caclient.request_cert do
Puppet.notice "Did not receive certificate"
sleep options[:waitforcert]
end
rescue => detail
Puppet.err "Could not request certificate: %s" % detail.to_s
exit(23)
end
else
unless caclient.request_cert
Puppet.notice "No certificates; exiting"
exit(1)
end
end
# Now read the new cert in.
if Puppet::Network::HttpPool.read_cert
# If we read it in, then get rid of our existing http connection.
client.recycle_connection
Puppet.notice "Got signed certificate"
else
Puppet.err "Could not read certificates after retrieving them"
exit(34)
end
end
objects = []
# This has to go after the certs are dealt with.
if Puppet[:listen] and ! options[:onetime]
unless FileTest.exists?(Puppet[:authconfig])
Puppet.err "Will not start without authorization file %s" %
Puppet[:authconfig]
exit(14)
end
# FIXME: we should really figure out how to distribute the CRL
# to clients. In the meantime, we just disable CRL checking if
# the CRL file doesn't exist
unless File::exist?(Puppet[:cacrl])
- Puppet[:cacrl] = 'none'
+ Puppet[:cacrl] = 'false'
end
handlers = nil
if options[:serve].empty?
handlers = {:Runner => {}}
else
handlers = options[:serve]
end
handlers.each do |name, hash|
Puppet.info "Starting handler for %s" % name
end
args[:Handlers] = handlers
args[:Port] = Puppet[:puppetport]
require 'puppet/network/http_server/webrick'
begin
server = Puppet::Network::HTTPServer::WEBrick.new(args)
rescue => detail
$stderr.puts detail
puts detail.backtrace
exit(1)
end
objects << server
elsif options[:onetime] and Puppet[:listen]
Puppet.notice "Ignoring --listen on onetime run"
end
if options[:client]
objects << client
end
# Set traps for INT and TERM
Puppet.settraps
# If --onetime is specified, we don't run 'start', which means we don't
# create a pidfile.
if options[:onetime]
unless options[:client]
$stderr.puts "onetime is specified but there is no client"
exit(43)
end
# Add the service, so the traps work correctly.
Puppet.newservice(client)
begin
client.run
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
Puppet.err detail.to_s
end
exit(0)
else
if server
Puppet.newservice(server)
end
if options[:client]
Puppet.notice "Starting Puppet client version %s" % [Puppet.version]
Puppet.newservice(client)
end
Puppet.settraps
Puppet.start
end
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 0c8ac3f82..520a18d1a 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -1,676 +1,676 @@
# The majority of the system configuration parameters are set in this file.
module Puppet
# If we're running the standalone puppet process as a non-root user,
# use basedirs that are in the user's home directory.
conf = nil
var = nil
name = $0.gsub(/.+#{File::SEPARATOR}/,'').sub(/\.rb$/, '')
# Make File.expand_path happy
require 'etc'
ENV["HOME"] ||= Etc.getpwuid(Process.uid).dir
if name != "puppetmasterd" and Puppet::Util::SUIDManager.uid != 0
conf = File.expand_path("~/.puppet")
var = File.expand_path("~/.puppet/var")
else
# Else, use system-wide directories.
conf = "/etc/puppet"
var = "/var/puppet"
end
self.setdefaults(:main,
:confdir => [conf, "The main Puppet configuration directory. The default for this parameter is calculated based on the user. If the process
is runnig as root or the user that ``puppetmasterd`` is supposed to run as, it defaults to a system directory, but if it's running as any other user,
it defaults to being in ``~``."],
:vardir => [var, "Where Puppet stores dynamic and growing data. The default for this parameter is calculated specially, like `confdir`_."],
:name => [name, "The name of the service, if we are running as one. The
default is essentially $0 without the path or ``.rb``."]
)
if name == "puppetmasterd"
logopts = {:default => "$vardir/log",
:mode => 0750,
:owner => "$user",
:group => "$group",
:desc => "The Puppet log directory."
}
else
logopts = ["$vardir/log", "The Puppet log directory."]
end
setdefaults(:main, :logdir => logopts)
# This name hackery is necessary so that the rundir is set reasonably during
# unit tests.
if Process.uid == 0 and %w{puppetd puppetmasterd}.include?(self.name)
rundir = "/var/run/puppet"
else
rundir = "$vardir/run"
end
self.setdefaults(:main,
:trace => [false, "Whether to print stack traces on some errors"],
:autoflush => [false, "Whether log files should always flush to disk."],
:syslogfacility => ["daemon", "What syslog facility to use when logging to
syslog. Syslog has a fixed list of valid facilities, and you must
choose one of those; you cannot just make one up."],
:statedir => { :default => "$vardir/state",
:mode => 01755,
:desc => "The directory where Puppet state is stored. Generally,
this directory can be removed without causing harm (although it
might result in spurious service restarts)."
},
:ssldir => {
:default => "$confdir/ssl",
:mode => 0771,
:owner => "root",
:desc => "Where SSL certificates are kept."
},
:rundir => {
:default => rundir,
:mode => 01777,
:owner => "$user",
:group => "$group",
:desc => "Where Puppet PID files are kept."
},
:genconfig => [false,
"Whether to just print a configuration to stdout and exit. Only makes
sense when used interactively. Takes into account arguments specified
on the CLI."],
:genmanifest => [false,
"Whether to just print a manifest to stdout and exit. Only makes
sense when used interactively. Takes into account arguments specified
on the CLI."],
:configprint => ["",
"Print the value of a specific configuration parameter. If a
parameter is provided for this, then the value is printed and puppet
exits. Comma-separate multiple values. For a list of all values,
specify 'all'. This feature is only available in Puppet versions
higher than 0.18.4."],
:color => ["ansi", "Whether to use colors when logging to the console.
Valid values are ``ansi`` (equivalent to ``true``), ``html`` (mostly
used during testing with TextMate), and ``false``, which produces
no color."],
:mkusers => [false,
"Whether to create the necessary user and group that puppetd will
run as."],
:path => {:default => "none",
:desc => "The shell search path. Defaults to whatever is inherited
from the parent process.",
:hook => proc do |value|
ENV["PATH"] = value unless value == "none"
end
},
:libdir => {:default => "$vardir/lib",
:desc => "An extra search path for Puppet. This is only useful
for those files that Puppet will load on demand, and is only
guaranteed to work for those cases. In fact, the autoload
mechanism is responsible for making sure this directory
is in Ruby's search path",
:call_on_define => true, # Call our hook with the default value, so we always get the libdir set.
:hook => proc do |value|
if defined? @oldlibdir and $:.include?(@oldlibdir)
$:.delete(@oldlibdir)
end
@oldlibdir = value
$: << value
end
},
:ignoreimport => [false, "A parameter that can be used in commit
hooks, since it enables you to parse-check a single file rather
than requiring that all files exist."],
:authconfig => [ "$confdir/namespaceauth.conf",
"The configuration file that defines the rights to the different
namespaces and methods. This can be used as a coarse-grained
authorization system for both ``puppetd`` and ``puppetmasterd``."
],
:environments => ["production,development", "The valid environments for Puppet clients.
This is more useful as a server-side setting than client, but any
environment chosen must be in this list. Values should be
separated by a comma."],
:environment => {:default => "development", :desc => "The environment Puppet is running in. For clients
(e.g., ``puppetd``) this determines the environment itself, which
is used to find modules and much more. For servers (i.e.,
``puppetmasterd``) this provides the default environment for nodes
we know nothing about.",
:hook => proc { |value| raise(ArgumentError, "Invalid environment %s" % value) unless Puppet::Node::Environment.valid?(value) }
},
:diff_args => ["", "Which arguments to pass to the diff command when printing differences between files."],
:diff => ["diff", "Which diff command to use when printing differences between files."],
:show_diff => [false, "Whether to print a contextual diff when files are being replaced. The diff
is printed on stdout, so this option is meaningless unless you are running Puppet interactively.
This feature currently requires the ``diff/lcs`` Ruby library."],
:yamldir => {:default => "$vardir/yaml", :owner => "$user", :group => "$user", :mode => "750",
:desc => "The directory in which YAML data is stored, usually in a subdirectory."},
:daemonize => { :default => true,
:desc => "Send the process into the background. This is the default.",
:short => "D"
},
:maximum_uid => [4294967290, "The maximum allowed UID. Some platforms use negative UIDs
but then ship with tools that do not know how to handle signed ints, so the UIDs show up as
huge numbers that can then not be fed back into the system. This is a hackish way to fail in a
slightly more useful way when that happens."],
:node_terminus => ["plain", "Where to find information about nodes."]
)
hostname = Facter["hostname"].value
domain = Facter["domain"].value
if domain and domain != ""
fqdn = [hostname, domain].join(".")
else
fqdn = hostname
end
Puppet.setdefaults(:ssl,
:certname => [fqdn, "The name to use when handling certificates. Defaults
to the fully qualified domain name."],
:certdnsnames => ['', "The DNS names on the Server certificate as a colon-separated list.
If it's anything other than an empty string, it will be used as an alias in the created
certificate. By default, only the server gets an alias set up, and only for 'puppet'."],
:certdir => ["$ssldir/certs", "The certificate directory."],
:publickeydir => ["$ssldir/public_keys", "The public key directory."],
:privatekeydir => { :default => "$ssldir/private_keys",
:mode => 0750,
:desc => "The private key directory."
},
:privatedir => { :default => "$ssldir/private",
:mode => 0750,
:desc => "Where the client stores private certificate information."
},
:passfile => { :default => "$privatedir/password",
:mode => 0640,
:desc => "Where puppetd stores the password for its private key.
Generally unused."
},
:hostcsr => { :default => "$ssldir/csr_$certname.pem",
:mode => 0644,
:desc => "Where individual hosts store and look for their certificates."
},
:hostcert => { :default => "$certdir/$certname.pem",
:mode => 0644,
:desc => "Where individual hosts store and look for their certificates."
},
:hostprivkey => { :default => "$privatekeydir/$certname.pem",
:mode => 0600,
:desc => "Where individual hosts store and look for their private key."
},
:hostpubkey => { :default => "$publickeydir/$certname.pem",
:mode => 0644,
:desc => "Where individual hosts store and look for their public key."
},
:localcacert => { :default => "$certdir/ca.pem",
:mode => 0644,
:desc => "Where each client stores the CA certificate."
}
)
setdefaults(:ca,
:cadir => { :default => "$ssldir/ca",
:owner => "$user",
:group => "$group",
:mode => 0770,
:desc => "The root directory for the certificate authority."
},
:cacert => { :default => "$cadir/ca_crt.pem",
:owner => "$user",
:group => "$group",
:mode => 0660,
:desc => "The CA certificate."
},
:cakey => { :default => "$cadir/ca_key.pem",
:owner => "$user",
:group => "$group",
:mode => 0660,
:desc => "The CA private key."
},
:capub => { :default => "$cadir/ca_pub.pem",
:owner => "$user",
:group => "$group",
:desc => "The CA public key."
},
:cacrl => { :default => "$cadir/ca_crl.pem",
:owner => "$user",
:group => "$group",
:mode => 0664,
- :desc => "The certificate revocation list (CRL) for the CA. Set this to 'none' if you do not want to use a CRL."
+ :desc => "The certificate revocation list (CRL) for the CA. Set this to 'false' if you do not want to use a CRL."
},
:caprivatedir => { :default => "$cadir/private",
:owner => "$user",
:group => "$group",
:mode => 0770,
:desc => "Where the CA stores private certificate information."
},
:csrdir => { :default => "$cadir/requests",
:owner => "$user",
:group => "$group",
:desc => "Where the CA stores certificate requests"
},
:signeddir => { :default => "$cadir/signed",
:owner => "$user",
:group => "$group",
:mode => 0770,
:desc => "Where the CA stores signed certificates."
},
:capass => { :default => "$caprivatedir/ca.pass",
:owner => "$user",
:group => "$group",
:mode => 0660,
:desc => "Where the CA stores the password for the private key"
},
:serial => { :default => "$cadir/serial",
:owner => "$user",
:group => "$group",
:desc => "Where the serial number for certificates is stored."
},
:autosign => { :default => "$confdir/autosign.conf",
:mode => 0644,
:desc => "Whether to enable autosign. Valid values are true (which
autosigns any key request, and is a very bad idea), false (which
never autosigns any key request), and the path to a file, which
uses that configuration file to determine which keys to sign."},
:ca_days => ["", "How long a certificate should be valid.
This parameter is deprecated, use ca_ttl instead"],
:ca_ttl => ["5y", "The default TTL for new certificates; valid values
must be an integer, optionally followed by one of the units
'y' (years of 365 days), 'd' (days), 'h' (hours), or
's' (seconds). The unit defaults to seconds. If this parameter
is set, ca_days is ignored. Examples are '3600' (one hour)
and '1825d', which is the same as '5y' (5 years) "],
:ca_md => ["md5", "The type of hash used in certificates."],
:req_bits => [2048, "The bit length of the certificates."],
:keylength => [1024, "The bit length of keys."],
:cert_inventory => {
:default => "$cadir/inventory.txt",
:mode => 0644,
:owner => "$user",
:group => "$group",
:desc => "A Complete listing of all certificates"
}
)
# Define the config default.
self.setdefaults(self.settings[:name],
:config => ["$confdir/puppet.conf",
"The configuration file for #{Puppet[:name]}."],
:pidfile => ["", "The pid file"],
:bindaddress => ["", "The address to bind to. Mongrel servers
default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."],
:servertype => ["webrick", "The type of server to use. Currently supported
options are webrick and mongrel. If you use mongrel, you will need
a proxy in front of the process or processes, since Mongrel cannot
speak SSL."]
)
self.setdefaults(:puppetmasterd,
:user => ["puppet", "The user puppetmasterd should run as."],
:group => ["puppet", "The group puppetmasterd should run as."],
:manifestdir => ["$confdir/manifests",
"Where puppetmasterd looks for its manifests."],
:manifest => ["$manifestdir/site.pp",
"The entry-point manifest for puppetmasterd."],
:code => ["", "Code to parse directly. This is essentially only used
by ``puppet``, and should only be set if you're writing your own Puppet
executable"],
:masterlog => { :default => "$logdir/puppetmaster.log",
:owner => "$user",
:group => "$group",
:mode => 0660,
:desc => "Where puppetmasterd logs. This is generally not used,
since syslog is the default log destination."
},
:masterhttplog => { :default => "$logdir/masterhttp.log",
:owner => "$user",
:group => "$group",
:mode => 0660,
:create => true,
:desc => "Where the puppetmasterd web server logs."
},
:masterport => [8140, "Which port puppetmasterd listens on."],
:parseonly => [false, "Just check the syntax of the manifests."],
:node_name => ["cert", "How the puppetmaster determines the client's identity
and sets the 'hostname' fact for use in the manifest, in particular
for determining which 'node' statement applies to the client.
Possible values are 'cert' (use the subject's CN in the client's
certificate) and 'facter' (use the hostname that the client
reported in its facts)"],
:bucketdir => {
:default => "$vardir/bucket",
:mode => 0750,
:owner => "$user",
:group => "$group",
:desc => "Where FileBucket files are stored."
},
:ca => [true, "Wether the master should function as a certificate authority."],
:modulepath => [ "$confdir/modules:/usr/share/puppet/modules",
"The search path for modules as a colon-separated list of
directories." ],
:ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated
client's SSL DN. Only used with Mongrel. This header must be set by the proxy
to the authenticated client's SSL DN (e.g., ``/CN=puppet.reductivelabs.com``).
See the `UsingMongrel`:trac: wiki page for more information."],
:ssl_client_verify_header => ["HTTP_X_CLIENT_VERIFY", "The header containing the status
message of the client verification. Only used with Mongrel. This header must be set by the proxy
to 'SUCCESS' if the client successfully authenticated, and anything else otherwise.
See the `UsingMongrel`:trac: wiki page for more information."]
)
self.setdefaults(:puppetd,
:localconfig => { :default => "$statedir/localconfig",
:owner => "root",
:mode => 0660,
:desc => "Where puppetd caches the local configuration. An
extension indicating the cache format is added automatically."},
:statefile => { :default => "$statedir/state.yaml",
:mode => 0660,
:desc => "Where puppetd and puppetmasterd store state associated
with the running configuration. In the case of puppetmasterd,
this file reflects the state discovered through interacting
with clients."
},
:classfile => { :default => "$statedir/classes.txt",
:owner => "root",
:mode => 0644,
:desc => "The file in which puppetd stores a list of the classes
associated with the retrieved configuration. Can be loaded in
the separate ``puppet`` executable using the ``--loadclasses``
option."},
:puppetdlog => { :default => "$logdir/puppetd.log",
:owner => "root",
:mode => 0640,
:desc => "The log file for puppetd. This is generally not used."
},
:httplog => { :default => "$logdir/http.log",
:owner => "root",
:mode => 0640,
:desc => "Where the puppetd web server logs."
},
:http_proxy_host => ["none",
"The HTTP proxy host to use for outgoing connections. Note: You
may need to use a FQDN for the server hostname when using a proxy."],
:http_proxy_port => [3128,
"The HTTP proxy port to use for outgoing connections"],
:http_enable_post_connection_check => [true,
"Boolean; wheter or not puppetd should validate the server
SSL certificate against the request hostname."],
:server => ["puppet",
"The server to which server puppetd should connect"],
:ignoreschedules => [false,
"Boolean; whether puppetd should ignore schedules. This is useful
for initial puppetd runs."],
:puppetport => [8139, "Which port puppetd listens on."],
:noop => [false, "Whether puppetd should be run in noop mode."],
:runinterval => [1800, # 30 minutes
"How often puppetd applies the client configuration; in seconds."],
:listen => [false, "Whether puppetd should listen for
connections. If this is true, then by default only the
``runner`` server is started, which allows remote authorized
and authenticated nodes to connect and trigger ``puppetd``
runs."],
:ca_server => ["$server", "The server to use for certificate
authority requests. It's a separate server because it cannot
and does not need to horizontally scale."],
:ca_port => ["$masterport", "The port to use for the certificate authority."]
)
self.setdefaults(:filebucket,
:clientbucketdir => {
:default => "$vardir/clientbucket",
:mode => 0750,
:desc => "Where FileBucket files are stored locally."
}
)
self.setdefaults(:fileserver,
:fileserverconfig => ["$confdir/fileserver.conf",
"Where the fileserver configuration is stored."]
)
self.setdefaults(:reporting,
:reports => ["store",
"The list of reports to generate. All reports are looked for
in puppet/reports/<name>.rb, and multiple report names should be
comma-separated (whitespace is okay)."
],
:reportdir => {:default => "$vardir/reports",
:mode => 0750,
:owner => "$user",
:group => "$group",
:desc => "The directory in which to store reports
received from the client. Each client gets a separate
subdirectory."}
)
self.setdefaults(:puppetd,
:puppetdlockfile => [ "$statedir/puppetdlock",
"A lock file to temporarily stop puppetd from doing anything."],
:usecacheonfailure => [true,
"Whether to use the cached configuration when the remote
configuration will not compile. This option is useful for testing
new configurations, where you want to fix the broken configuration
rather than reverting to a known-good one."
],
:ignorecache => [false,
"Ignore cache and always recompile the configuration. This is
useful for testing new configurations, where the local cache may in
fact be stale even if the timestamps are up to date - if the facts
change or if the server changes."
],
:downcasefacts => [false,
"Whether facts should be made all lowercase when sent to the server."],
:dynamicfacts => ["memorysize,memoryfree,swapsize,swapfree",
"Facts that are dynamic; these facts will be ignored when deciding whether
changed facts should result in a recompile. Multiple facts should be
comma-separated."],
:splaylimit => ["$runinterval",
"The maximum time to delay before runs. Defaults to being the same as the
run interval."],
:splay => [false,
"Whether to sleep for a pseudo-random (but consistent) amount of time before
a run."]
)
self.setdefaults(:puppetd,
:configtimeout => [120,
"How long the client should wait for the configuration to be retrieved
before considering it a failure. This can help reduce flapping if too
many clients contact the server at one time."
],
:reportserver => ["$server",
"The server to which to send transaction reports."
],
:report => [false,
"Whether to send reports after every transaction."
]
)
# Plugin information.
self.setdefaults(:main,
:pluginpath => ["$vardir/plugins",
"Where Puppet should look for plugins. Multiple directories should
be colon-separated, like normal PATH variables. As of 0.23.1, this
option is deprecated; download your custom libraries to the $libdir
instead."],
:plugindest => ["$libdir",
"Where Puppet should store plugins that it pulls down from the central
server."],
:pluginsource => ["puppet://$server/plugins",
"From where to retrieve plugins. The standard Puppet ``file`` type
is used for retrieval, so anything that is a valid file source can
be used here."],
:pluginsync => [false,
"Whether plugins should be synced with the central server."],
:pluginsignore => [".svn CVS",
"What files to ignore when pulling down plugins."]
)
# Central fact information.
self.setdefaults(:main,
:factpath => ["$vardir/facts",
"Where Puppet should look for facts. Multiple directories should
be colon-separated, like normal PATH variables."],
:factdest => ["$vardir/facts",
"Where Puppet should store facts that it pulls down from the central
server."],
:factsource => ["puppet://$server/facts",
"From where to retrieve facts. The standard Puppet ``file`` type
is used for retrieval, so anything that is a valid file source can
be used here."],
:factsync => [false,
"Whether facts should be synced with the central server."],
:factsignore => [".svn CVS",
"What files to ignore when pulling down facts."]
)
self.setdefaults(:tagmail,
:tagmap => ["$confdir/tagmail.conf",
"The mapping between reporting tags and email addresses."],
:sendmail => [%x{which sendmail 2>/dev/null}.chomp,
"Where to find the sendmail binary with which to send email."],
:reportfrom => ["report@" + [Facter["hostname"].value, Facter["domain"].value].join("."),
"The 'from' email address for the reports."],
:smtpserver => ["none",
"The server through which to send email reports."]
)
self.setdefaults(:rails,
:dblocation => { :default => "$statedir/clientconfigs.sqlite3",
:mode => 0660,
:owner => "$user",
:group => "$group",
:desc => "The database cache for client configurations. Used for
querying within the language."
},
:dbadapter => [ "sqlite3", "The type of database to use." ],
:dbmigrate => [ false, "Whether to automatically migrate the database." ],
:dbname => [ "puppet", "The name of the database to use." ],
:dbserver => [ "localhost", "The database server for Client caching. Only
used when networked databases are used."],
:dbuser => [ "puppet", "The database user for Client caching. Only
used when networked databases are used."],
:dbpassword => [ "puppet", "The database password for Client caching. Only
used when networked databases are used."],
:dbsocket => [ "", "The database socket location. Only used when networked
databases are used. Will be ignored if the value is an empty string."],
:railslog => {:default => "$logdir/rails.log",
:mode => 0600,
:owner => "$user",
:group => "$group",
:desc => "Where Rails-specific logs are sent"
},
:rails_loglevel => ["info", "The log level for Rails connections. The value must be
a valid log level within Rails. Production environments normally use ``info``
and other environments normally use ``debug``."]
)
setdefaults(:graphing,
:graph => [false, "Whether to create dot graph files for the different
configuration graphs. These dot files can be interpreted by tools
like OmniGraffle or dot (which is part of ImageMagick)."],
:graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."]
)
setdefaults(:transaction,
:tags => ["", "Tags to use to find resources. If this is set, then
only resources tagged with the specified tags will be applied.
Values must be comma-separated."],
:evaltrace => [false, "Whether each resource should log when it is
being evaluated. This allows you to interactively see exactly
what is being done."],
:summarize => [false,
"Whether to print a transaction summary."
]
)
setdefaults(:parser,
:typecheck => [true, "Whether to validate types during parsing."],
:paramcheck => [true, "Whether to validate parameters during parsing."]
)
setdefaults(:main,
:casesensitive => [false,
"Whether matching in case statements and selectors
should be case-sensitive. Case insensitivity is
handled by downcasing all values before comparison."],
:external_nodes => ["none",
"An external command that can produce node information. The output
must be a YAML dump of a hash, and that hash must have one or both of
``classes`` and ``parameters``, where ``classes`` is an array and
``parameters`` is a hash. For unknown nodes, the commands should
exit with a non-zero exit code.
This command makes it straightforward to store your node mapping
information in other data sources like databases."])
setdefaults(:ldap,
:ldapnodes => [false,
"Whether to search for node configurations in LDAP. See
`LdapNodes`:trac: for more information."],
:ldapssl => [false,
"Whether SSL should be used when searching for nodes.
Defaults to false because SSL usually requires certificates
to be set up on the client side."],
:ldaptls => [false,
"Whether TLS should be used when searching for nodes.
Defaults to false because TLS usually requires certificates
to be set up on the client side."],
:ldapserver => ["ldap",
"The LDAP server. Only used if ``ldapnodes`` is enabled."],
:ldapport => [389,
"The LDAP port. Only used if ``ldapnodes`` is enabled."],
:ldapstring => ["(&(objectclass=puppetClient)(cn=%s))",
"The search string used to find an LDAP node."],
:ldapclassattrs => ["puppetclass",
"The LDAP attributes to use to define Puppet classes. Values
should be comma-separated."],
:ldapattrs => ["all",
"The LDAP attributes to include when querying LDAP for nodes. All
returned attributes are set as variables in the top-level scope.
Multiple values should be comma-separated. The value 'all' returns
all attributes."],
:ldapparentattr => ["parentnode",
"The attribute to use to define the parent node."],
:ldapuser => ["",
"The user to use to connect to LDAP. Must be specified as a
full DN."],
:ldappassword => ["",
"The password to use to connect to LDAP."],
:ldapbase => ["",
"The search base for LDAP searches. It's impossible to provide
a meaningful default here, although the LDAP libraries might
have one already set. Generally, it should be the 'ou=Hosts'
branch under your main directory."]
)
setdefaults(:puppetmasterd,
:storeconfigs => [false,
"Whether to store each client's configuration. This
requires ActiveRecord from Ruby on Rails."]
)
# This doesn't actually work right now.
setdefaults(:parser,
:lexical => [false, "Whether to use lexical scoping (vs. dynamic)."],
:templatedir => ["$vardir/templates",
"Where Puppet looks for template files."
]
)
setdefaults(:main,
:filetimeout => [ 15,
"The minimum time to wait (in seconds) between checking for updates in
configuration files. This timeout determines how quickly Puppet checks whether
a file (such as manifests or templates) has changed on disk."
]
)
setdefaults(:metrics,
:rrddir => {:default => "$vardir/rrd",
:owner => "$user",
:group => "$group",
:desc => "The directory where RRD database files are stored.
Directories for each reporting host will be created under
this directory."
},
:rrdgraph => [false, "Whether RRD information should be graphed."],
:rrdinterval => ["$runinterval", "How often RRD should expect data.
This should match how often the hosts report back to the server."]
)
end
diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb
index b35adae66..9983c34d2 100644
--- a/lib/puppet/metatype/metaparams.rb
+++ b/lib/puppet/metatype/metaparams.rb
@@ -1,423 +1,423 @@
require 'puppet'
require 'puppet/type'
class Puppet::Type
# Add all of the meta parameters.
#newmetaparam(:onerror) do
# desc "How to handle errors -- roll back innermost
# transaction, roll back entire transaction, ignore, etc. Currently
# non-functional."
#end
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true": @resource.noop = true
when false, :false, "false": @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
that for your schedule::
schedule { daily:
period => daily,
range => \"2-4\"
}
exec { \"/usr/bin/apt-get update\":
schedule => daily
}
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:check) do
desc "Propertys which should have their values retrieved
but which should not actually be modified. This is currently used
internally, but will eventually be used for querying, so that you
could specify that you wanted to check the install state of all
packages, and then query the Puppet client daemon to get reports
on all packages."
munge do |args|
# If they've specified all, collect all known properties
if args == :all
args = @resource.class.properties.find_all do |property|
# Only get properties supported by our provider
if @resource.provider
@resource.provider.class.supports_parameter?(property)
else
true
end
end.collect do |property|
property.name
end
end
unless args.is_a?(Array)
args = [args]
end
unless defined? @resource
self.devfail "No parent for %s, %s?" %
[self.class, self.name]
end
args.each { |property|
unless property.is_a?(Symbol)
property = property.intern
end
next if @resource.propertydefined?(property)
unless propertyklass = @resource.class.validproperty?(property)
if @resource.class.validattr?(property)
next
else
raise Puppet::Error, "%s is not a valid attribute for %s" %
[property, self.class.name]
end
end
next unless propertyklass.checkable?
@resource.newattr(property)
}
end
end
# We've got four relationship metaparameters, so this method is used
# to reduce code duplication between them.
def munge_relationship(param, values)
# We need to support values passed in as an array or as a
# resource reference.
result = []
# 'values' could be an array or a reference. If it's an array,
# it could be an array of references or an array of arrays.
if values.is_a?(Puppet::Type)
result << [values.class.name, values.title]
else
unless values.is_a?(Array)
devfail "Relationships must be resource references"
end
if values[0].is_a?(String) or values[0].is_a?(Symbol)
# we're a type/title array reference
values[0] = symbolize(values[0])
result << values
else
# we're an array of stuff
values.each do |value|
if value.is_a?(Puppet::Type)
result << [value.class.name, value.title]
elsif value.is_a?(Array)
value[0] = symbolize(value[0])
result << value
else
devfail "Invalid relationship %s" % value.inspect
end
end
end
end
if existing = self[param]
result = existing + result
end
result
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
provide a symbolic name::
file { sshdconfig:
path => $operatingsystem ? {
solaris => \"/usr/local/etc/ssh/sshd_config\",
default => \"/etc/ssh/sshd_config\"
},
source => \"...\"
}
service { sshd:
subscribe => file[sshdconfig]
}
When you use this feature, the parser sets ``sshdconfig`` as the name,
and the library sets that as an alias for the file so the dependency
lookup for ``sshd`` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
the following code will not work::
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
file { sshdconfig:
mode => 644
}
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
See the `LanguageTutorial language tutorial`:trac: for more information.
"
munge do |aliases|
unless aliases.is_a?(Array)
aliases = [aliases]
end
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
@resource.info "Adding aliases %s" % aliases.collect { |a| a.inspect }.join(", ")
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("%s can not create alias %s: object already exists" % [@resource.title, other])
end
next
end
# LAK:FIXME Old-school, add the alias to the class.
@resource.class.alias(other, @resource)
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
host's configuration::
puppetd --test --tags mytag
This way, when you're testing a configuration you can run just the
portion you're testing."
munge do |tags|
tags = [tags] unless tags.is_a? Array
tags.each do |tag|
@resource.tag(tag)
end
end
end
class RelationshipMetaparam < Puppet::Parameter
class << self
attr_accessor :direction, :events, :callback, :subclasses
end
@subclasses = []
def self.inherited(sub)
@subclasses << sub
end
def munge(rels)
@resource.munge_relationship(self.class.name, rels)
end
def validate_relationship
@value.each do |value|
unless @resource.catalog.resource(*value)
description = self.class.direction == :in ? "dependency" : "dependent"
- raise Puppet::Error, "Could not find #{description} %s[%s]" % [value[0].to_s.capitalize, value[1]]
+ fail Puppet::Error, "Could not find #{description} %s[%s] for %s" % [value[0].to_s.capitalize, value[1], resource.ref]
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |value|
# we just have a name and a type, and we need to convert it
# to an object...
tname, name = value
reference = Puppet::ResourceReference.new(tname, name)
# Either of the two retrieval attempts could have returned
# nil.
unless object = reference.resolve
self.fail "Could not retrieve dependency '%s' of %s" % [reference, @resource.ref]
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = object
target = @resource
else
source = @resource
target = object
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to %s" % [object.ref])
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires %s" % [object.ref])
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
desc "One or more objects that this object depends on.
This is used purely for guaranteeing that changes to required objects
happen before the dependent object. For instance::
# Create the destination directory before you copy things down
file { \"/usr/local/scripts\":
ensure => directory
}
file { \"/usr/local/scripts/myscript\":
source => \"puppet://server/module/myscript\",
mode => 755,
require => File[\"/usr/local/scripts\"]
}
Multiple dependencies can be specified by providing a comma-seperated list
of resources, enclosed in square brackets::
require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
command. For instance, if you had an ``exec`` command that ran
the ``myscript`` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
``exec`` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "One or more objects that this object depends on. Changes in the
subscribed to objects result in the dependent objects being
refreshed (e.g., a service will get restarted). For instance::
class nagios {
file { \"/etc/nagios/nagios.conf\":
source => \"puppet://server/module/nagios.conf\",
alias => nagconf # just to make things easier for me
}
service { nagios:
running => true,
subscribe => file[nagconf]
}
}
Currently the ``exec``, ``mount`` and ``service`` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object::
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => exec["nagios-rebuid"]
}
exec { "nagios-rebuild":
command => "/usr/bin/make",
cwd => "/var/nagios/configuration"
}
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object::
file { "/etc/sshd_config":
source => "....",
notify => service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
end
end # Puppet::Type
diff --git a/lib/puppet/network/http_server/webrick.rb b/lib/puppet/network/http_server/webrick.rb
index 3c9f72e17..e4f00dd73 100644
--- a/lib/puppet/network/http_server/webrick.rb
+++ b/lib/puppet/network/http_server/webrick.rb
@@ -1,168 +1,168 @@
require 'puppet'
require 'puppet/daemon'
require 'webrick'
require 'webrick/https'
require 'fcntl'
require 'puppet/sslcertificates/support'
require 'puppet/network/xmlrpc/webrick_servlet'
require 'puppet/network/http_server'
require 'puppet/network/client'
module Puppet
class ServerError < RuntimeError; end
module Network
# The old-school, pure ruby webrick server, which is the default serving
# mechanism.
class HTTPServer::WEBrick < WEBrick::HTTPServer
include Puppet::Daemon
include Puppet::SSLCertificates::Support
# Read the CA cert and CRL and populate an OpenSSL::X509::Store
# with them, with flags appropriate for checking client
# certificates for revocation
def x509store
- if Puppet[:cacrl] == 'none'
+ if Puppet[:cacrl] == 'false'
# No CRL, no store needed
return nil
end
unless File.exist?(Puppet[:cacrl])
- raise Puppet::Error, "Could not find CRL; set 'cacrl' to 'none' to disable CRL usage"
+ raise Puppet::Error, "Could not find CRL; set 'cacrl' to 'false' to disable CRL usage"
end
crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl]))
store = OpenSSL::X509::Store.new
store.purpose = OpenSSL::X509::PURPOSE_ANY
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
unless self.ca_cert
raise Puppet::Error, "Could not find CA certificate"
end
store.add_file(Puppet[:localcacert])
store.add_crl(crl)
return store
end
# Set up the http log.
def httplog
args = []
# yuck; separate http logs
file = nil
Puppet.settings.use(:main, :ssl, Puppet[:name])
if Puppet[:name] == "puppetmasterd"
file = Puppet[:masterhttplog]
else
file = Puppet[:httplog]
end
# open the log manually to prevent file descriptor leak
file_io = open(file, "a+")
file_io.sync
file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
args << file_io
if Puppet[:debug]
args << WEBrick::Log::DEBUG
end
log = WEBrick::Log.new(*args)
return log
end
# Create our server, yo.
def initialize(hash = {})
Puppet.info "Starting server for Puppet version %s" % Puppet.version
if handlers = hash[:Handlers]
handler_instances = setup_handlers(handlers)
else
raise ServerError, "A server must have handlers"
end
unless self.read_cert
if ca = handler_instances.find { |handler| handler.is_a?(Puppet::Network::Handler.ca) }
request_cert(ca)
else
raise Puppet::Error, "No certificate and no CA; cannot get cert"
end
end
setup_webrick(hash)
begin
super(hash)
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::Error, "Could not start WEBrick: %s" % detail
end
# make sure children don't inherit the sockets
listeners.each { |sock|
sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
}
Puppet.info "Listening on port %s" % hash[:Port]
# this creates a new servlet for every connection,
# but all servlets have the same list of handlers
# thus, the servlets can have their own state -- passing
# around the requests and such -- but the handlers
# have a global state
# mount has to be called after the server is initialized
servlet = Puppet::Network::XMLRPC::WEBrickServlet.new(
handler_instances)
self.mount("/RPC2", servlet)
end
# Create a ca client to set up our cert for us.
def request_cert(ca)
client = Puppet::Network::Client.ca.new(:CA => ca)
unless client.request_cert
raise Puppet::Error, "Could get certificate"
end
end
# Create all of our handler instances.
def setup_handlers(handlers)
unless handlers.is_a?(Hash)
raise ServerError, "Handlers must have arguments"
end
handlers.collect { |handler, args|
hclass = nil
unless hclass = Handler.handler(handler)
raise ServerError, "Invalid handler %s" % handler
end
hclass.new(args)
}
end
# Handle all of the many webrick arguments.
def setup_webrick(hash)
hash[:Port] ||= Puppet[:masterport]
hash[:Logger] ||= self.httplog
hash[:AccessLog] ||= [
[ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
[ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
]
hash[:SSLCertificateStore] = x509store
hash[:SSLCertificate] = self.cert
hash[:SSLPrivateKey] = self.key
hash[:SSLStartImmediately] = true
hash[:SSLEnable] = true
hash[:SSLCACertificateFile] = Puppet[:localcacert]
hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER
hash[:SSLCertName] = nil
if addr = Puppet[:bindaddress] and addr != ""
hash[:BindAddress] = addr
end
end
end
end
end
diff --git a/lib/puppet/resource_reference.rb b/lib/puppet/resource_reference.rb
index 3e92662b2..771a91be7 100644
--- a/lib/puppet/resource_reference.rb
+++ b/lib/puppet/resource_reference.rb
@@ -1,79 +1,79 @@
#
# Created by Luke Kanies on 2007-11-28.
# Copyright (c) 2007. All rights reserved.
require 'puppet'
# A simple class to canonize how we refer to and retrieve
# resources.
class Puppet::ResourceReference
attr_reader :type
attr_accessor :title, :catalog
def initialize(type, title)
# This will set @type if it looks like a resource reference.
self.title = title
# Don't override whatever was done by setting the title.
self.type = type if self.type.nil?
@builtin_type = nil
end
# Find our resource.
def resolve
if catalog
return catalog.resource(to_s)
end
# If it's builtin, then just ask for it directly from the type.
if t = builtin_type
t[@title]
else # Else, look for a component with the full reference as the name.
Puppet::Type::Component[to_s]
end
end
# If the title has square brackets, treat it like a reference and
# set things appropriately; else, just set it.
def title=(value)
- if value =~ /^(.+)\[(.+)\]$/
+ if value =~ /^([^\[\]]+)\[(.+)\]$/
self.type = $1
@title = $2
else
@title = value
end
end
# Canonize the type so we know it's always consistent.
def type=(value)
if value.nil? or value.to_s.downcase == "component"
@type = "Class"
else
@type = value.to_s.split("::").collect { |s| s.capitalize }.join("::")
end
end
# Convert to the standard way of referring to resources.
def to_s
"%s[%s]" % [@type, @title]
end
private
def builtin_type?
builtin_type ? true : false
end
def builtin_type
if @builtin_type.nil?
if @type =~ /::/
@builtin_type = false
elsif klass = Puppet::Type.type(@type.to_s.downcase)
@builtin_type = klass
else
@builtin_type = false
end
end
@builtin_type
end
end
diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb
index a3edd2cb4..888bcf5b2 100644
--- a/lib/puppet/sslcertificates/ca.rb
+++ b/lib/puppet/sslcertificates/ca.rb
@@ -1,426 +1,426 @@
require 'sync'
class Puppet::SSLCertificates::CA
include Puppet::Util::Warnings
Certificate = Puppet::SSLCertificates::Certificate
attr_accessor :keyfile, :file, :config, :dir, :cert, :crl
def certfile
@config[:cacert]
end
# Remove all traces of a given host. This is kind of hackish, but, eh.
def clean(host)
host = host.downcase
[:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name|
dir = Puppet[name]
file = File.join(dir, host + ".pem")
if FileTest.exists?(file)
begin
if Puppet[:name] == "puppetca"
puts "Removing %s" % file
else
Puppet.info "Removing %s" % file
end
File.unlink(file)
rescue => detail
raise Puppet::Error, "Could not delete %s: %s" %
[file, detail]
end
end
end
end
def host2csrfile(hostname)
File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join("."))
end
# this stores signed certs in a directory unrelated to
# normal client certs
def host2certfile(hostname)
File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join("."))
end
# Turn our hostname into a Name object
def thing2name(thing)
thing.subject.to_a.find { |ary|
ary[0] == "CN"
}[1]
end
def initialize(hash = {})
Puppet.settings.use(:main, :ca, :ssl)
self.setconfig(hash)
if Puppet[:capass]
if FileTest.exists?(Puppet[:capass])
#puts "Reading %s" % Puppet[:capass]
#system "ls -al %s" % Puppet[:capass]
#File.read Puppet[:capass]
@config[:password] = self.getpass
else
# Don't create a password if the cert already exists
unless FileTest.exists?(@config[:cacert])
@config[:password] = self.genpass
end
end
end
self.getcert
init_crl
unless FileTest.exists?(@config[:serial])
Puppet.settings.write(:serial) do |f|
f << "%04X" % 1
end
end
end
# Generate a new password for the CA.
def genpass
pass = ""
20.times { pass += (rand(74) + 48).chr }
begin
Puppet.settings.write(:capass) { |f| f.print pass }
rescue Errno::EACCES => detail
raise Puppet::Error, detail.to_s
end
return pass
end
# Get the CA password.
def getpass
if @config[:capass] and File.readable?(@config[:capass])
return File.read(@config[:capass])
else
raise Puppet::Error, "Could not read CA passfile %s" % @config[:capass]
end
end
# Get the CA cert.
def getcert
if FileTest.exists?(@config[:cacert])
@cert = OpenSSL::X509::Certificate.new(
File.read(@config[:cacert])
)
else
self.mkrootcert
end
end
# Retrieve a client's CSR.
def getclientcsr(host)
csrfile = host2csrfile(host)
unless File.exists?(csrfile)
return nil
end
return OpenSSL::X509::Request.new(File.read(csrfile))
end
# Retrieve a client's certificate.
def getclientcert(host)
certfile = host2certfile(host)
unless File.exists?(certfile)
return [nil, nil]
end
return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
end
# List certificates waiting to be signed. This returns a list of hostnames, not actual
# files -- the names can be converted to full paths with host2csrfile.
def list
return Dir.entries(Puppet[:csrdir]).find_all { |file|
file =~ /\.pem$/
}.collect { |file|
file.sub(/\.pem$/, '')
}
end
# List signed certificates. This returns a list of hostnames, not actual
# files -- the names can be converted to full paths with host2csrfile.
def list_signed
return Dir.entries(Puppet[:signeddir]).find_all { |file|
file =~ /\.pem$/
}.collect { |file|
file.sub(/\.pem$/, '')
}
end
# Create the root certificate.
def mkrootcert
# Make the root cert's name the FQDN of the host running the CA.
name = Facter["hostname"].value
if domain = Facter["domain"].value
name += "." + domain
end
cert = Certificate.new(
:name => name,
:cert => @config[:cacert],
:encrypt => @config[:capass],
:key => @config[:cakey],
:selfsign => true,
:ttl => ttl,
:type => :ca
)
# This creates the cakey file
Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do
@cert = cert.mkselfsigned
end
Puppet.settings.write(:cacert) do |f|
f.puts @cert.to_pem
end
Puppet.settings.write(:capub) do |f|
f.puts @cert.public_key
end
return cert
end
def removeclientcsr(host)
csrfile = host2csrfile(host)
unless File.exists?(csrfile)
raise Puppet::Error, "No certificate request for %s" % host
end
File.unlink(csrfile)
end
# Revoke the certificate with serial number SERIAL issued by this
# CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons
def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
- if @config[:cacrl] == 'none'
- raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'none'"
+ if @config[:cacrl] == 'false'
+ raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'false'"
end
time = Time.now
revoked = OpenSSL::X509::Revoked.new
revoked.serial = serial
revoked.time = time
enum = OpenSSL::ASN1::Enumerated(reason)
ext = OpenSSL::X509::Extension.new("CRLReason", enum)
revoked.add_extension(ext)
@crl.add_revoked(revoked)
store_crl
end
# Take the Puppet config and store it locally.
def setconfig(hash)
@config = {}
Puppet.settings.params("ca").each { |param|
param = param.intern if param.is_a? String
if hash.include?(param)
@config[param] = hash[param]
Puppet[param] = hash[param]
hash.delete(param)
else
@config[param] = Puppet[param]
end
}
if hash.include?(:password)
@config[:password] = hash[:password]
hash.delete(:password)
end
if hash.length > 0
raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",")
end
[:cadir, :csrdir, :signeddir].each { |dir|
unless @config[dir]
raise Puppet::DevError, "%s is undefined" % dir
end
}
end
# Create an exclusive lock for reading and writing, and do the
# writing in a tmp file.
def readwritelock(file, mode = 0600)
tmpfile = file + ".tmp"
sync = Sync.new
unless FileTest.directory?(File.dirname(tmpfile))
raise Puppet::DevError, "Cannot create %s; directory %s does not exist" %
[file, File.dirname(file)]
end
sync.synchronize(Sync::EX) do
File.open(file, "r+", mode) do |rf|
rf.lock_exclusive do
File.open(tmpfile, "w", mode) do |tf|
yield tf
end
begin
File.rename(tmpfile, file)
rescue => detail
Puppet.err "Could not rename %s to %s: %s" %
[file, tmpfile, detail]
end
end
end
end
end
# Sign a given certificate request.
def sign(csr)
unless csr.is_a?(OpenSSL::X509::Request)
raise Puppet::Error,
"CA#sign only accepts OpenSSL::X509::Request objects, not %s" %
csr.class
end
unless csr.verify(csr.public_key)
raise Puppet::Error, "CSR sign verification failed"
end
serial = nil
readwritelock(@config[:serial]) { |f|
serial = File.read(@config[:serial]).chomp.hex
# increment the serial
f << "%04X" % (serial + 1)
}
newcert = Puppet::SSLCertificates.mkcert(
:type => :server,
:name => csr.subject,
:ttl => ttl,
:issuer => @cert,
:serial => serial,
:publickey => csr.public_key
)
sign_with_key(newcert)
self.storeclientcert(newcert)
return [newcert, @cert]
end
# Store the client's CSR for later signing. This is called from
# server/ca.rb, and the CSRs are deleted once the certificate is actually
# signed.
def storeclientcsr(csr)
host = thing2name(csr)
csrfile = host2csrfile(host)
if File.exists?(csrfile)
raise Puppet::Error, "Certificate request for %s already exists" % host
end
Puppet.settings.writesub(:csrdir, csrfile) do |f|
f.print csr.to_pem
end
end
# Store the certificate that we generate.
def storeclientcert(cert)
host = thing2name(cert)
certfile = host2certfile(host)
if File.exists?(certfile)
Puppet.notice "Overwriting signed certificate %s for %s" %
[certfile, host]
end
Puppet::SSLCertificates::Inventory::add(cert)
Puppet.settings.writesub(:signeddir, certfile) do |f|
f.print cert.to_pem
end
end
# TTL for new certificates in seconds. If config param :ca_ttl is set,
# use that, otherwise use :ca_days for backwards compatibility
def ttl
days = @config[:ca_days]
if days && days.size > 0
warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead."
return @config[:ca_days] * 24 * 60 * 60
else
ttl = @config[:ca_ttl]
if ttl.is_a?(String)
unless ttl =~ /^(\d+)(y|d|h|s)$/
raise ArgumentError, "Invalid ca_ttl #{ttl}"
end
case $2
when 'y'
unit = 365 * 24 * 60 * 60
when 'd'
unit = 24 * 60 * 60
when 'h'
unit = 60 * 60
when 's'
unit = 1
else
raise ArgumentError, "Invalid unit for ca_ttl #{ttl}"
end
return $1.to_i * unit
else
return ttl
end
end
end
private
def init_crl
if FileTest.exists?(@config[:cacrl])
@crl = OpenSSL::X509::CRL.new(
File.read(@config[:cacrl])
)
- elsif @config[:cacrl] == 'none'
+ elsif @config[:cacrl] == 'false'
@crl = nil
else
# Create new CRL
@crl = OpenSSL::X509::CRL.new
@crl.issuer = @cert.subject
@crl.version = 1
store_crl
@crl
end
end
def store_crl
# Increment the crlNumber
e = @crl.extensions.find { |e| e.oid == 'crlNumber' }
ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' }
crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0)
ext << OpenSSL::X509::Extension.new("crlNumber", crlNum)
@crl.extensions = ext
# Set last/next update
now = Time.now
@crl.last_update = now
# Keep CRL valid for 5 years
@crl.next_update = now + 5 * 365*24*60*60
sign_with_key(@crl)
Puppet.settings.write(:cacrl) do |f|
f.puts @crl.to_pem
end
end
def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new)
cakey = nil
if @config[:password]
cakey = OpenSSL::PKey::RSA.new(
File.read(@config[:cakey]), @config[:password]
)
else
cakey = OpenSSL::PKey::RSA.new(
File.read(@config[:cakey])
)
end
unless @cert.check_private_key(cakey)
raise Puppet::Error, "CA Certificate is invalid"
end
signable.sign(cakey, digest)
end
end
diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb
index 5bb3158c4..7d3b1abe1 100755
--- a/lib/puppet/type/exec.rb
+++ b/lib/puppet/type/exec.rb
@@ -1,611 +1,620 @@
module Puppet
newtype(:exec) do
include Puppet::Util::Execution
require 'timeout'
@doc = "Executes external commands. It is critical that all commands
executed using this mechanism can be run multiple times without
harm, i.e., they are *idempotent*. One useful way to create idempotent
commands is to use the checks like ``creates`` to avoid running the
command unless some condition is met.
Note also that you can restrict an ``exec`` to only run when it receives
events by using the ``refreshonly`` parameter; this is a useful way to
have your configuration respond to events with arbitrary commands.
It is worth noting that ``exec`` is special, in that it is not
currently considered an error to have multiple ``exec`` instances
with the same name. This was done purely because it had to be this
way in order to get certain functionality, but it complicates things.
In particular, you will not be able to use ``exec`` instances that
share their commands with other instances as a dependency, since
Puppet has no way of knowing which instance you mean.
For example::
# defined in the production class
exec { \"make\":
cwd => \"/prod/build/dir\",
path => \"/usr/bin:/usr/sbin:/bin\"
}
. etc. .
# defined in the test class
exec { \"make\":
cwd => \"/test/build/dir\",
path => \"/usr/bin:/usr/sbin:/bin\"
}
Any other type would throw an error, complaining that you had
the same instance being managed in multiple places, but these are
obviously different images, so ``exec`` had to be treated specially.
It is recommended to avoid duplicate names whenever possible.
Note that if an ``exec`` receives an event from another resource,
it will get executed again (or execute the command specified in
``refresh``, if there is one).
There is a strong tendency to use ``exec`` to do whatever work Puppet
can't already do; while this is obviously acceptable (and unavoidable)
in the short term, it is highly recommended to migrate work from ``exec``
to native Puppet types as quickly as possible. If you find that
you are doing a lot of work with ``exec``, please at least notify
us at Reductive Labs what you are doing, and hopefully we can work with
you to get a native resource type for the work you are doing."
require 'open3'
# Create a new check mechanism. It's basically just a parameter that
# provides one extra 'check' method.
def self.newcheck(name, &block)
@checks ||= {}
check = newparam(name, &block)
@checks[name] = check
end
def self.checks
@checks.keys
end
newproperty(:returns) do |property|
include Puppet::Util::Execution
munge do |value|
value.to_s
end
defaultto "0"
attr_reader :output
desc "The expected return code. An error will be returned if the
executed command returns something else. Defaults to 0."
# Make output a bit prettier
def change_to_s(currentvalue, newvalue)
return "executed successfully"
end
# First verify that all of our checks pass.
def retrieve
# Default to somethinng
if @resource.check
return :notrun
else
return self.should
end
end
# Actually execute the command.
def sync
olddir = nil
# We need a dir to change to, even if it's just the cwd
dir = self.resource[:cwd] || Dir.pwd
event = :executed_command
begin
@output, status = @resource.run(self.resource[:command])
rescue Timeout::Error
self.fail "Command exceeded timeout" % value.inspect
end
if log = @resource[:logoutput]
case log
when :true
log = @resource[:loglevel]
when :on_failure
if status.exitstatus.to_s != self.should.to_s
log = @resource[:loglevel]
else
log = :false
end
end
unless log == :false
@output.split(/\n/).each { |line|
self.send(log, line)
}
end
end
if status.exitstatus.to_s != self.should.to_s
self.fail("%s returned %s instead of %s" %
[self.resource[:command], status.exitstatus, self.should.to_s])
end
return event
end
end
newparam(:command) do
isnamevar
desc "The actual command to execute. Must either be fully qualified
or a search path for the command must be provided. If the command
succeeds, any output produced will be logged at the instance's
normal log level (usually ``notice``), but if the command fails
(meaning its return code does not match the specified code) then
any output is logged at the ``err`` log level."
end
newparam(:path) do
desc "The search path used for command execution.
Commands must be fully qualified if no path is specified. Paths
can be specified as an array or as a colon-separated list."
# Support both arrays and colon-separated fields.
def value=(*values)
@value = values.flatten.collect { |val|
val.split(":")
}.flatten
end
end
newparam(:user) do
desc "The user to run the command as. Note that if you
use this then any error output is not currently captured. This
is because of a bug within Ruby. If you are using Puppet to
create this user, the exec will automatically require the user,
as long as it is specified by name."
# Most validation is handled by the SUIDManager class.
validate do |user|
unless Puppet.features.root?
self.fail "Only root can execute commands as other users"
end
end
end
newparam(:group) do
desc "The group to run the command as. This seems to work quite
haphazardly on different platforms -- it is a platform issue
not a Ruby or Puppet one, since the same variety exists when
running commnands as different users in the shell."
# Validation is handled by the SUIDManager class.
end
newparam(:cwd) do
desc "The directory from which to run the command. If
this directory does not exist, the command will fail."
validate do |dir|
unless dir =~ /^#{File::SEPARATOR}/
self.fail("CWD must be a fully qualified path")
end
end
munge do |dir|
if dir.is_a?(Array)
dir = dir[0]
end
dir
end
end
newparam(:logoutput) do
desc "Whether to log output. Defaults to logging output at the
loglevel for the ``exec`` resource. Use *on_failure* to only
log the output when the command reports an error. Values are
**true**, *false*, *on_failure*, and any legal log level."
values = [:true, :false, :on_failure]
# And all of the log levels
Puppet::Util::Log.eachlevel { |level| values << level }
newvalues(*values)
end
newparam(:refresh) do
desc "How to refresh this command. By default, the exec is just
called again when it receives an event from another resource,
but this parameter allows you to define a different command
for refreshing."
validate do |command|
@resource.validatecmd(command)
end
end
newparam(:env) do
+ desc "This parameter is deprecated. Use 'environment' instead."
+
+ munge do |value|
+ warning "'env' is deprecated on exec; use 'environment' instead."
+ resource[:environment] = value
+ end
+ end
+
+ newparam(:environment) do
desc "Any additional environment variables you want to set for a
command. Note that if you use this to set PATH, it will override
the ``path`` attribute. Multiple environment variables should be
specified as an array."
validate do |values|
values = [values] unless values.is_a? Array
values.each do |value|
unless value =~ /\w+=/
raise ArgumentError, "Invalid environment setting '%s'" % value
end
end
end
end
newparam(:timeout) do
desc "The maximum time the command should take. If the command takes
longer than the timeout, the command is considered to have failed
and will be stopped. Use any negative number to disable the timeout.
The time is specified in seconds."
munge do |value|
value = value.shift if value.is_a?(Array)
if value.is_a?(String)
unless value =~ /^[-\d.]+$/
raise ArgumentError, "The timeout must be a number."
end
Float(value)
else
value
end
end
defaultto 300
end
newcheck(:refreshonly) do
desc "The command should only be run as a
refresh mechanism for when a dependent object is changed. It only
makes sense to use this option when this command depends on some
other object; it is useful for triggering an action::
# Pull down the main aliases file
file { \"/etc/aliases\":
source => \"puppet://server/module/aliases\"
}
# Rebuild the database, but only when the file changes
exec { newaliases:
path => [\"/usr/bin\", \"/usr/sbin\"],
subscribe => file[\"/etc/aliases\"],
refreshonly => true
}
Note that only ``subscribe`` and ``notify`` can trigger actions, not ``require``,
so it only makes sense to use ``refreshonly`` with ``subscribe`` or ``notify``."
newvalues(:true, :false)
# We always fail this test, because we're only supposed to run
# on refresh.
def check(value)
# We have to invert the values.
if value == :true
false
else
true
end
end
end
newcheck(:creates) do
desc "A file that this command creates. If this
parameter is provided, then the command will only be run
if the specified file does not exist::
exec { \"tar xf /my/tar/file.tar\":
cwd => \"/var/tmp\",
creates => \"/var/tmp/myfile\",
path => [\"/usr/bin\", \"/usr/sbin\"]
}
"
# FIXME if they try to set this and fail, then we should probably
# fail the entire exec, right?
validate do |files|
files = [files] unless files.is_a? Array
files.each do |file|
self.fail("'creates' must be set to a fully qualified path") unless file
unless file =~ %r{^#{File::SEPARATOR}}
self.fail "'creates' files must be fully qualified."
end
end
end
# If the file exists, return false (i.e., don't run the command),
# else return true
def check(value)
return ! FileTest.exists?(value)
end
end
newcheck(:unless) do
desc "If this parameter is set, then this ``exec`` will run unless
the command returns 0. For example::
exec { \"/bin/echo root >> /usr/lib/cron/cron.allow\":
path => \"/usr/bin:/usr/sbin:/bin\",
unless => \"grep root /usr/lib/cron/cron.allow 2>/dev/null\"
}
This would add ``root`` to the cron.allow file (on Solaris) unless
``grep`` determines it's already there.
Note that this command follows the same rules as the main command,
which is to say that it must be fully qualified if the path is not set.
"
validate do |cmds|
cmds = [cmds] unless cmds.is_a? Array
cmds.each do |cmd|
@resource.validatecmd(cmd)
end
end
# Return true if the command does not return 0.
def check(value)
begin
output, status = @resource.run(value, true)
rescue Timeout::Error
err "Check %s exceeded timeout" % value.inspect
return false
end
return status.exitstatus != 0
end
end
newcheck(:onlyif) do
desc "If this parameter is set, then this ``exec`` will only run if
the command returns 0. For example::
exec { \"logrotate\":
path => \"/usr/bin:/usr/sbin:/bin\",
onlyif => \"test `du /var/log/messages | cut -f1` -gt 100000\"
}
This would run ``logrotate`` only if that test returned true.
Note that this command follows the same rules as the main command,
which is to say that it must be fully qualified if the path is not set.
"
validate do |cmds|
cmds = [cmds] unless cmds.is_a? Array
cmds.each do |cmd|
@resource.validatecmd(cmd)
end
end
# Return true if the command returns 0.
def check(value)
begin
output, status = @resource.run(value, true)
rescue Timeout::Error
err "Check %s exceeded timeout" % value.inspect
return false
end
return status.exitstatus == 0
end
end
# Exec names are not isomorphic with the objects.
@isomorphic = false
validate do
validatecmd(self[:command])
end
# FIXME exec should autorequire any exec that 'creates' our cwd
autorequire(:file) do
reqs = []
# Stick the cwd in there if we have it
if self[:cwd]
reqs << self[:cwd]
end
self[:command].scan(/^(#{File::SEPARATOR}\S+)/) { |str|
reqs << str
}
[:onlyif, :unless].each { |param|
next unless tmp = self[param]
tmp = [tmp] unless tmp.is_a? Array
tmp.each do |line|
# And search the command line for files, adding any we
# find. This will also catch the command itself if it's
# fully qualified. It might not be a bad idea to add
# unqualified files, but, well, that's a bit more annoying
# to do.
reqs += line.scan(%r{(#{File::SEPARATOR}\S+)})
end
}
# For some reason, the += isn't causing a flattening
reqs.flatten!
reqs
end
autorequire(:user) do
# Autorequire users if they are specified by name
if user = self[:user] and user !~ /^\d+$/
user
end
end
def self.instances
[]
end
# Verify that we pass all of the checks. The argument determines whether
# we skip the :refreshonly check, which is necessary because we now check
# within refresh()
def check(refreshing = false)
self.class.checks.each { |check|
next if refreshing and check == :refreshonly
if @parameters.include?(check)
val = @parameters[check].value
val = [val] unless val.is_a? Array
val.each do |value|
unless @parameters[check].check(value)
return false
end
end
end
}
return true
end
# Verify that we have the executable
def checkexe(cmd)
if cmd =~ /^\//
exe = cmd.split(/ /)[0]
unless FileTest.exists?(exe)
raise ArgumentError, "Could not find executable %s" % exe
end
unless FileTest.executable?(exe)
raise ArgumentError,
"%s is not executable" % exe
end
elsif path = self[:path]
exe = cmd.split(/ /)[0]
withenv :PATH => self[:path].join(":") do
path = %{which #{exe}}.chomp
if path == ""
raise ArgumentError,
"Could not find command '%s'" % exe
end
end
else
raise ArgumentError,
"%s is somehow not qualified with no search path" %
self[:command]
end
end
def output
if self.property(:returns).nil?
return nil
else
return self.property(:returns).output
end
end
# Run the command, or optionally run a separately-specified command.
def refresh
if self.check(true)
if cmd = self[:refresh]
self.run(cmd)
else
self.property(:returns).sync
end
end
end
# Run a command.
def run(command, check = false)
output = nil
status = nil
dir = nil
checkexe(command)
if dir = self[:cwd]
unless File.directory?(dir)
if check
dir = nil
else
self.fail "Working directory '%s' does not exist" % dir
end
end
end
dir ||= Dir.pwd
if check
debug "Executing check '#{command}'"
else
debug "Executing '#{command}'"
end
begin
# Do our chdir
Dir.chdir(dir) do
- env = {}
+ environment = {}
if self[:path]
- env[:PATH] = self[:path].join(":")
+ environment[:PATH] = self[:path].join(":")
end
- if envlist = self[:env]
+ if envlist = self[:environment]
envlist = [envlist] unless envlist.is_a? Array
envlist.each do |setting|
if setting =~ /^(\w+)=((.|\n)+)$/
name = $1
value = $2
- if env.include? name
+ if environment.include? name
warning(
"Overriding environment setting '%s' with '%s'" %
[name, value]
)
end
- env[name] = value
+ environment[name] = value
else
- warning "Cannot understand env setting %s" % setting.inspect
+ warning "Cannot understand environment setting %s" % setting.inspect
end
end
end
- withenv env do
+ withenv environment do
Timeout::timeout(self[:timeout]) do
output, status = Puppet::Util::SUIDManager.run_and_capture(
[command], self[:user], self[:group]
)
end
# The shell returns 127 if the command is missing.
if status.exitstatus == 127
raise ArgumentError, output
end
end
end
rescue Errno::ENOENT => detail
self.fail detail.to_s
end
return output, status
end
def validatecmd(cmd)
# if we're not fully qualified, require a path
if cmd !~ /^\//
if self[:path].nil?
self.fail "'%s' is both unqualifed and specified no search path" % cmd
end
end
end
end
end
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index ff019edb8..cf15d3194 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -1,1241 +1,1243 @@
require 'puppet'
require 'sync'
require 'puppet/transportable'
require 'getoptlong'
# The class for handling configuration files.
class Puppet::Util::Settings
include Enumerable
include Puppet::Util
@@sync = Sync.new
attr_accessor :file
attr_reader :timer
# Retrieve a config value
def [](param)
value(param)
end
# Set a config value. This doesn't set the defaults, it sets the value itself.
def []=(param, value)
@@sync.synchronize do # yay, thread-safe
param = symbolize(param)
unless element = @config[param]
raise ArgumentError,
"Attempt to assign a value to unknown configuration parameter %s" % param.inspect
end
if element.respond_to?(:munge)
value = element.munge(value)
end
if element.respond_to?(:handle)
element.handle(value)
end
# Reset the name, so it's looked up again.
if param == :name
@name = nil
end
@values[:memory][param] = value
@cache.clear
end
return value
end
# A simplified equality operator.
# LAK: For some reason, this causes mocha to not be able to mock
# the 'value' method, and it's not used anywhere.
# def ==(other)
# self.each { |myname, myobj|
# unless other[myname] == value(myname)
# return false
# end
# }
#
# return true
# end
# Generate the list of valid arguments, in a format that GetoptLong can
# understand, and add them to the passed option list.
def addargs(options)
# Hackish, but acceptable. Copy the current ARGV for restarting.
Puppet.args = ARGV.dup
# Add all of the config parameters as valid options.
self.each { |name, element|
element.getopt_args.each { |args| options << args }
}
return options
end
# Turn the config into a Puppet configuration and apply it
def apply
trans = self.to_transportable
begin
config = trans.to_catalog
config.store_state = false
config.apply
config.clear
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
Puppet.err "Could not configure myself: %s" % detail
end
end
# Is our parameter a boolean parameter?
def boolean?(param)
param = symbolize(param)
if @config.include?(param) and @config[param].kind_of? CBoolean
return true
else
return false
end
end
# Remove all set values, potentially skipping cli values.
def clear(exceptcli = false)
@config.each { |name, obj|
unless exceptcli and obj.setbycli
obj.clear
end
}
@values.each do |name, values|
next if name == :cli and exceptcli
@values.delete(name)
end
# Don't clear the 'used' in this case, since it's a config file reparse,
# and we want to retain this info.
unless exceptcli
@used = []
end
@cache.clear
@name = nil
end
# This is mostly just used for testing.
def clearused
@cache.clear
@used = []
end
# Do variable interpolation on the value.
def convert(value)
return value unless value
return value unless value.is_a? String
newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
varname = $2 || $1
if pval = self.value(varname)
pval
else
raise Puppet::DevError, "Could not find value for %s" % parent
end
end
return newval
end
# Return a value's description.
def description(name)
if obj = @config[symbolize(name)]
obj.desc
else
nil
end
end
def each
@config.each { |name, object|
yield name, object
}
end
# Iterate over each section name.
def eachsection
yielded = []
@config.each do |name, object|
section = object.section
unless yielded.include? section
yield section
yielded << section
end
end
end
# Return an object by name.
def element(param)
param = symbolize(param)
@config[param]
end
# Handle a command-line argument.
def handlearg(opt, value = nil)
@cache.clear
value = munge_value(value) if value
str = opt.sub(/^--/,'')
bool = true
newstr = str.sub(/^no-/, '')
if newstr != str
str = newstr
bool = false
end
str = str.intern
if self.valid?(str)
if self.boolean?(str)
@values[:cli][str] = bool
else
@values[:cli][str] = value
end
else
raise ArgumentError, "Invalid argument %s" % opt
end
end
def include?(name)
name = name.intern if name.is_a? String
@config.include?(name)
end
# check to see if a short name is already defined
def shortinclude?(short)
short = short.intern if name.is_a? String
@shortnames.include?(short)
end
# Create a new config object
def initialize
@config = {}
@shortnames = {}
@created = []
@searchpath = nil
# Keep track of set values.
@values = Hash.new { |hash, key| hash[key] = {} }
# And keep a per-environment cache
@cache = Hash.new { |hash, key| hash[key] = {} }
# A central concept of a name.
@name = nil
end
# Return a given object's file metadata.
def metadata(param)
if obj = @config[symbolize(param)] and obj.is_a?(CFile)
return [:owner, :group, :mode].inject({}) do |meta, p|
if v = obj.send(p)
meta[p] = v
end
meta
end
else
nil
end
end
# Make a directory with the appropriate user, group, and mode
def mkdir(default)
obj = nil
unless obj = @config[default]
raise ArgumentError, "Unknown default %s" % default
end
unless obj.is_a? CFile
raise ArgumentError, "Default %s is not a file" % default
end
Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do
mode = obj.mode || 0750
Dir.mkdir(obj.value, mode)
end
end
# Figure out our name.
def name
unless @name
unless @config[:name]
return nil
end
searchpath.each do |source|
next if source == :name
break if @name = @values[source][:name]
end
unless @name
@name = convert(@config[:name].default).intern
end
end
@name
end
# Return all of the parameters associated with a given section.
def params(section = nil)
if section
section = section.intern if section.is_a? String
@config.find_all { |name, obj|
obj.section == section
}.collect { |name, obj|
name
}
else
@config.keys
end
end
# Parse the configuration file.
def parse(file)
clear(true)
parse_file(file).each do |area, values|
@values[area] = values
end
# Determine our environment, if we have one.
if @config[:environment]
env = self.value(:environment).to_sym
else
env = "none"
end
# Call any hooks we should be calling.
settings_with_hooks.each do |setting|
each_source(env) do |source|
if value = @values[source][setting.name]
# We still have to use value() to retrieve the value, since
# we want the fully interpolated value, not $vardir/lib or whatever.
# This results in extra work, but so few of the settings
# will have associated hooks that it ends up being less work this
# way overall.
setting.handle(self.value(setting.name, env))
break
end
end
end
# We have to do it in the reverse of the search path,
# because multiple sections could set the same value
# and I'm too lazy to only set the metadata once.
searchpath.reverse.each do |source|
if meta = @values[source][:_meta]
set_metadata(meta)
end
end
end
# Parse the configuration file. As of May 2007, this is a backward-compatibility method and
# will be deprecated soon.
def old_parse(file)
text = nil
if file.is_a? Puppet::Util::LoadedFile
@file = file
else
@file = Puppet::Util::LoadedFile.new(file)
end
# Don't create a timer for the old style parsing.
# settimer()
begin
text = File.read(@file.file)
rescue Errno::ENOENT
raise Puppet::Error, "No such file %s" % file
rescue Errno::EACCES
raise Puppet::Error, "Permission denied to file %s" % file
end
@values = Hash.new { |names, name|
names[name] = {}
}
# Get rid of the values set by the file, keeping cli values.
self.clear(true)
section = "puppet"
metas = %w{owner group mode}
values = Hash.new { |hash, key| hash[key] = {} }
text.split(/\n/).each { |line|
case line
when /^\[(\w+)\]$/: section = $1 # Section names
when /^\s*#/: next # Skip comments
when /^\s*$/: next # Skip blanks
when /^\s*(\w+)\s*=\s*(.+)$/: # settings
var = $1.intern
if var == :mode
value = $2
else
value = munge_value($2)
end
# Only warn if we don't know what this config var is. This
# prevents exceptions later on.
unless @config.include?(var) or metas.include?(var.to_s)
Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect
next # Skip this line.
end
# Mmm, "special" attributes
if metas.include?(var.to_s)
unless values.include?(section)
values[section] = {}
end
values[section][var.to_s] = value
# If the parameter is valid, then set it.
if section == Puppet[:name] and @config.include?(var)
#@config[var].value = value
@values[:main][var] = value
end
next
end
# Don't override set parameters, since the file is parsed
# after cli arguments are handled.
unless @config.include?(var) and @config[var].setbycli
Puppet.debug "%s: Setting %s to '%s'" % [section, var, value]
@values[:main][var] = value
end
@config[var].section = symbolize(section)
metas.each { |meta|
if values[section][meta]
if @config[var].respond_to?(meta + "=")
@config[var].send(meta + "=", values[section][meta])
end
end
}
else
raise Puppet::Error, "Could not match line %s" % line
end
}
end
# Create a new element. The value is passed in because it's used to determine
# what kind of element we're creating, but the value itself might be either
# a default or a value, so we can't actually assign it.
def newelement(hash)
value = hash[:value] || hash[:default]
klass = nil
if hash[:section]
hash[:section] = symbolize(hash[:section])
end
case value
when true, false, "true", "false":
klass = CBoolean
when /^\$\w+\//, /^\//:
klass = CFile
when String, Integer, Float: # nothing
klass = CElement
else
raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]]
end
hash[:parent] = self
element = klass.new(hash)
return element
end
# This has to be private, because it doesn't add the elements to @config
private :newelement
# Iterate across all of the objects in a given section.
def persection(section)
section = symbolize(section)
self.each { |name, obj|
if obj.section == section
yield obj
end
}
end
# Reparse our config file, if necessary.
def reparse
if defined? @file and @file.changed?
Puppet.notice "Reparsing %s" % @file.file
@@sync.synchronize do
parse(@file)
end
reuse()
end
end
def reuse
return unless defined? @used
@@sync.synchronize do # yay, thread-safe
@used.each do |section|
@used.delete(section)
self.use(section)
end
end
end
# The order in which to search for values.
def searchpath(environment = nil)
if environment
[:cli, :memory, environment, :name, :main]
else
[:cli, :memory, :name, :main]
end
end
# Get a list of objects per section
def sectionlist
sectionlist = []
self.each { |name, obj|
section = obj.section || "puppet"
sections[section] ||= []
unless sectionlist.include?(section)
sectionlist << section
end
sections[section] << obj
}
return sectionlist, sections
end
# Convert a single section into transportable objects.
def section_to_transportable(section, done = nil)
done ||= Hash.new { |hash, key| hash[key] = {} }
objects = []
persection(section) do |obj|
if @config[:mkusers] and value(:mkusers)
objects += add_user_resources(section, obj, done)
end
+ value = obj.value
+
# Only files are convertable to transportable resources.
- next unless obj.respond_to? :to_transportable
- next if value(obj.name) =~ /^\/dev/
- next if Puppet::Type::File[obj.value] # skip files that are in our global resource list.
+ next unless obj.respond_to? :to_transportable and transobjects = obj.to_transportable
- transobjects = obj.to_transportable
transobjects = [transobjects] unless transobjects.is_a? Array
transobjects.each do |trans|
# transportable could return nil
next unless trans
unless done[:file].include? trans.name
@created << trans.name
objects << trans
done[:file][trans.name] = trans
end
end
end
bucket = Puppet::TransBucket.new
bucket.type = "Settings"
bucket.name = section
bucket.push(*objects)
bucket.keyword = "class"
return bucket
end
# Set a bunch of defaults in a given section. The sections are actually pretty
# pointless, but they help break things up a bit, anyway.
def setdefaults(section, defs)
section = symbolize(section)
call = []
defs.each { |name, hash|
if hash.is_a? Array
unless hash.length == 2
raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription"
end
tmp = hash
hash = {}
[:default, :desc].zip(tmp).each { |p,v| hash[p] = v }
end
name = symbolize(name)
hash[:name] = name
hash[:section] = section
name = hash[:name]
if @config.include?(name)
raise ArgumentError, "Parameter %s is already defined" % name
end
tryconfig = newelement(hash)
if short = tryconfig.short
if other = @shortnames[short]
raise ArgumentError, "Parameter %s is already using short name '%s'" % [other.name, short]
end
@shortnames[short] = tryconfig
end
@config[name] = tryconfig
# Collect the settings that need to have their hooks called immediately.
# We have to collect them so that we can be sure we're fully initialized before
# the hook is called.
call << tryconfig if tryconfig.call_on_define
}
call.each { |setting| setting.handle(self.value(setting.name)) }
end
# Create a timer to check whether the file should be reparsed.
def settimer
if Puppet[:filetimeout] > 0
@timer = Puppet.newtimer(
:interval => Puppet[:filetimeout],
:tolerance => 1,
:start? => true
) do
self.reparse()
end
end
end
# Convert our list of objects into a component that can be applied.
def to_configuration
transport = self.to_transportable
return transport.to_catalog
end
# Convert our list of config elements into a configuration file.
def to_config
str = %{The configuration file for #{Puppet[:name]}. Note that this file
is likely to have unused configuration parameters in it; any parameter that's
valid anywhere in Puppet can be in any config file, even if it's not used.
Every section can specify three special parameters: owner, group, and mode.
These parameters affect the required permissions of any files specified after
their specification. Puppet will sometimes use these parameters to check its
own configured state, so they can be used to make Puppet a bit more self-managing.
Note also that the section names are entirely for human-level organizational
purposes; they don't provide separate namespaces. All parameters are in a
single namespace.
Generated on #{Time.now}.
}.gsub(/^/, "# ")
# Add a section heading that matches our name.
if @config.include?(:name)
str += "[%s]\n" % self[:name]
end
eachsection do |section|
persection(section) do |obj|
str += obj.to_config + "\n"
end
end
return str
end
# Convert our configuration into a list of transportable objects.
def to_transportable(*sections)
done = Hash.new { |hash, key|
hash[key] = {}
}
topbucket = Puppet::TransBucket.new
if defined? @file.file and @file.file
topbucket.name = @file.file
else
topbucket.name = "top"
end
topbucket.type = "Settings"
topbucket.top = true
# Now iterate over each section
if sections.empty?
eachsection do |section|
sections << section
end
end
sections.each do |section|
obj = section_to_transportable(section, done)
topbucket.push obj
end
topbucket
end
# Convert to a parseable manifest
def to_manifest
transport = self.to_transportable
manifest = transport.to_manifest + "\n"
eachsection { |section|
manifest += "include #{section}\n"
}
return manifest
end
# Create the necessary objects to use a section. This is idempotent;
# you can 'use' a section as many times as you want.
def use(*sections)
@@sync.synchronize do # yay, thread-safe
unless defined? @used
@used = []
end
bucket = to_transportable(*sections)
config = bucket.to_catalog
config.host_config = false
config.apply do |transaction|
if failures = transaction.any_failed?
raise "Could not configure for running; got %s failure(s)" % failures
end
end
config.clear
sections.each { |s| @used << s }
@used.uniq
end
end
def valid?(param)
param = symbolize(param)
@config.has_key?(param)
end
# Find the correct value using our search path. Optionally accept an environment
# in which to search before the other configuration sections.
def value(param, environment = nil)
param = symbolize(param)
environment = symbolize(environment) if environment
# Short circuit to nil for undefined parameters.
return nil unless @config.include?(param)
# Yay, recursion.
self.reparse() unless param == :filetimeout
# Check the cache first. It needs to be a per-environment
# cache so that we don't spread values from one env
# to another.
if cached = @cache[environment||"none"][param]
return cached
end
# See if we can find it within our searchable list of values
val = nil
each_source(environment) do |source|
# Look for the value. We have to test the hash for whether
# it exists, because the value might be false.
if @values[source].include?(param)
val = @values[source][param]
break
end
end
# If we didn't get a value, use the default
val = @config[param].default if val.nil?
# Convert it if necessary
val = convert(val)
# And cache it
@cache[environment||"none"][param] = val
return val
end
# Open a file with the appropriate user, group, and mode
def write(default, *args)
obj = nil
unless obj = @config[default]
raise ArgumentError, "Unknown default %s" % default
end
unless obj.is_a? CFile
raise ArgumentError, "Default %s is not a file" % default
end
chown = nil
if Puppet::Util::SUIDManager.uid == 0
chown = [obj.owner, obj.group]
else
chown = [nil, nil]
end
Puppet::Util::SUIDManager.asuser(*chown) do
mode = obj.mode || 0640
if args.empty?
args << "w"
end
args << mode
File.open(value(obj.name), *args) do |file|
yield file
end
end
end
# Open a non-default file under a default dir with the appropriate user,
# group, and mode
def writesub(default, file, *args)
obj = nil
unless obj = @config[default]
raise ArgumentError, "Unknown default %s" % default
end
unless obj.is_a? CFile
raise ArgumentError, "Default %s is not a file" % default
end
chown = nil
if Puppet::Util::SUIDManager.uid == 0
chown = [obj.owner, obj.group]
else
chown = [nil, nil]
end
Puppet::Util::SUIDManager.asuser(*chown) do
mode = obj.mode || 0640
if args.empty?
args << "w"
end
args << mode
# Update the umask to make non-executable files
Puppet::Util.withumask(File.umask ^ 0111) do
File.open(file, *args) do |file|
yield file
end
end
end
end
private
# Create the transportable objects for users and groups.
def add_user_resources(section, obj, done)
resources = []
[:owner, :group].each do |attr|
type = nil
if attr == :owner
type = :user
else
type = attr
end
# If a user and/or group is set, then make sure we're
# managing that object
if obj.respond_to? attr and name = obj.send(attr)
# Skip root or wheel
next if %w{root wheel}.include?(name.to_s)
# Skip owners and groups we've already done, but tag
# them with our section if necessary
if done[type].include?(name)
tags = done[type][name].tags
unless tags.include?(section)
done[type][name].tags = tags << section
end
else
newobj = Puppet::TransObject.new(name, type.to_s)
newobj.tags = ["puppet", "configuration", section]
newobj[:ensure] = :present
if type == :user
newobj[:comment] ||= "%s user" % name
end
# Set the group appropriately for the user
if type == :user
newobj[:gid] = Puppet[:group]
end
done[type][name] = newobj
resources << newobj
end
end
end
resources
end
# Yield each search source in turn.
def each_source(environment)
searchpath(environment).each do |source|
# Modify the source as necessary.
source = self.name if source == :name
yield source
end
end
# Return all elements that have associated hooks; this is so
# we can call them after parsing the configuration file.
def settings_with_hooks
@config.values.find_all { |setting| setting.respond_to?(:handle) }
end
# Extract extra setting information for files.
def extract_fileinfo(string)
result = {}
value = string.sub(/\{\s*([^}]+)\s*\}/) do
params = $1
params.split(/\s*,\s*/).each do |str|
if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/
param, value = $1.intern, $2
result[param] = value
unless [:owner, :mode, :group].include?(param)
raise ArgumentError, "Invalid file option '%s'" % param
end
if param == :mode and value !~ /^\d+$/
raise ArgumentError, "File modes must be numbers"
end
else
raise ArgumentError, "Could not parse '%s'" % string
end
end
''
end
result[:value] = value.sub(/\s*$/, '')
return result
return nil
end
# Convert arguments into booleans, integers, or whatever.
def munge_value(value)
# Handle different data types correctly
return case value
when /^false$/i: false
when /^true$/i: true
when /^\d+$/i: Integer(value)
else
value.gsub(/^["']|["']$/,'').sub(/\s+$/, '')
end
end
# This is an abstract method that just turns a file in to a hash of hashes.
# We mostly need this for backward compatibility -- as of May 2007 we need to
# support parsing old files with any section, or new files with just two
# valid sections.
def parse_file(file)
text = read_file(file)
# Create a timer so that this file will get checked automatically
# and reparsed if necessary.
settimer()
result = Hash.new { |names, name|
names[name] = {}
}
count = 0
# Default to 'main' for the section.
section = :main
result[section][:_meta] = {}
text.split(/\n/).each { |line|
count += 1
case line
when /^\s*\[(\w+)\]$/:
section = $1.intern # Section names
# Add a meta section
result[section][:_meta] ||= {}
when /^\s*#/: next # Skip comments
when /^\s*$/: next # Skip blanks
when /^\s*(\w+)\s*=\s*(.*)$/: # settings
var = $1.intern
# We don't want to munge modes, because they're specified in octal, so we'll
# just leave them as a String, since Puppet handles that case correctly.
if var == :mode
value = $2
else
value = munge_value($2)
end
# Check to see if this is a file argument and it has extra options
begin
if value.is_a?(String) and options = extract_fileinfo(value)
value = options[:value]
options.delete(:value)
result[section][:_meta][var] = options
end
result[section][var] = value
rescue Puppet::Error => detail
detail.file = file
detail.line = line
raise
end
else
error = Puppet::Error.new("Could not match line %s" % line)
error.file = file
error.line = line
raise error
end
}
return result
end
# Read the file in.
def read_file(file)
if file.is_a? Puppet::Util::LoadedFile
@file = file
else
@file = Puppet::Util::LoadedFile.new(file)
end
begin
return File.read(@file.file)
rescue Errno::ENOENT
raise ArgumentError, "No such file %s" % file
rescue Errno::EACCES
raise ArgumentError, "Permission denied to file %s" % file
end
end
# Set file metadata.
def set_metadata(meta)
meta.each do |var, values|
values.each do |param, value|
@config[var].send(param.to_s + "=", value)
end
end
end
# The base element type.
class CElement
attr_accessor :name, :section, :default, :parent, :setbycli, :call_on_define
attr_reader :desc, :short
# Unset any set value.
def clear
@value = nil
end
def desc=(value)
@desc = value.gsub(/^\s*/, '')
end
# get the arguments in getopt format
def getopt_args
if short
[["--#{name}", "-#{short}", GetoptLong::REQUIRED_ARGUMENT]]
else
[["--#{name}", GetoptLong::REQUIRED_ARGUMENT]]
end
end
def hook=(block)
meta_def :handle, &block
end
# Create the new element. Pretty much just sets the name.
def initialize(args = {})
if args.include?(:parent)
self.parent = args[:parent]
args.delete(:parent)
end
args.each do |param, value|
method = param.to_s + "="
unless self.respond_to? method
raise ArgumentError, "%s does not accept %s" % [self.class, param]
end
self.send(method, value)
end
unless self.desc
raise ArgumentError, "You must provide a description for the %s config option" % self.name
end
end
def iscreated
@iscreated = true
end
def iscreated?
if defined? @iscreated
return @iscreated
else
return false
end
end
def set?
if defined? @value and ! @value.nil?
return true
else
return false
end
end
# short name for the celement
def short=(value)
if value.to_s.length != 1
raise ArgumentError, "Short names can only be one character."
end
@short = value.to_s
end
# Convert the object to a config statement.
def to_config
str = @desc.gsub(/^/, "# ") + "\n"
# Add in a statement about the default.
if defined? @default and @default
str += "# The default value is '%s'.\n" % @default
end
# If the value has not been overridden, then print it out commented
# and unconverted, so it's clear that that's the default and how it
# works.
value = @parent.value(self.name)
if value != @default
line = "%s = %s" % [@name, value]
else
line = "# %s = %s" % [@name, @default]
end
str += line + "\n"
str.gsub(/^/, " ")
end
# Retrieves the value, or if it's not set, retrieves the default.
def value
@parent.value(self.name)
end
end
# A file.
class CFile < CElement
attr_writer :owner, :group
attr_accessor :mode, :create
def group
if defined? @group
return @parent.convert(@group)
else
return nil
end
end
def owner
if defined? @owner
return @parent.convert(@owner)
else
return nil
end
end
# Set the type appropriately. Yep, a hack. This supports either naming
# the variable 'dir', or adding a slash at the end.
def munge(value)
# If it's not a fully qualified path...
- if value.is_a?(String) and value !~ /^\$/ and value !~ /^\//
+ if value.is_a?(String) and value !~ /^\$/ and value !~ /^\// and value != 'false'
# Make it one
value = File.join(Dir.getwd, value)
end
if value.to_s =~ /\/$/
@type = :directory
return value.sub(/\/$/, '')
end
return value
end
# Return the appropriate type.
def type
value = @parent.value(self.name)
if @name.to_s =~ /dir/
return :directory
elsif value.to_s =~ /\/$/
return :directory
elsif value.is_a? String
return :file
else
return nil
end
end
# Convert the object to a TransObject instance.
def to_transportable
type = self.type
return nil unless type
- path = @parent.value(self.name).split(File::SEPARATOR)
- path.shift # remove the leading nil
- objects = []
path = self.value
+ return nil unless path.is_a?(String)
+ return nil if path =~ /^\/dev/
+ return nil if Puppet::Type::File[path] # skip files that are in our global resource list.
+
+ objects = []
+
# Skip plain files that don't exist, since we won't be managing them anyway.
return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create
obj = Puppet::TransObject.new(path, "file")
# Only create directories, or files that are specifically marked to
# create.
if type == :directory or self.create
obj[:ensure] = type
end
[:mode].each { |var|
if value = self.send(var)
# Don't bother converting the mode, since the file type
# can handle it any old way.
obj[var] = value
end
}
# Only chown or chgrp when root
if Puppet.features.root?
[:group, :owner].each { |var|
if value = self.send(var)
obj[var] = value
end
}
end
# And set the loglevel to debug for everything
obj[:loglevel] = "debug"
# We're not actually modifying any files here, and if we allow a
# filebucket to get used here we get into an infinite recursion
# trying to set the filebucket up.
obj[:backup] = false
if self.section
obj.tags += ["puppet", "configuration", self.section, self.name]
end
objects << obj
objects
end
# Make sure any provided variables look up to something.
def validate(value)
return true unless value.is_a? String
value.scan(/\$(\w+)/) { |name|
name = $1
unless @parent.include?(name)
raise ArgumentError,
"Settings parameter '%s' is undefined" %
name
end
}
end
end
# A simple boolean.
class CBoolean < CElement
# get the arguments in getopt format
def getopt_args
if short
[["--#{name}", "-#{short}", GetoptLong::NO_ARGUMENT],
["--no-#{name}", GetoptLong::NO_ARGUMENT]]
else
[["--#{name}", GetoptLong::NO_ARGUMENT],
["--no-#{name}", GetoptLong::NO_ARGUMENT]]
end
end
def munge(value)
case value
when true, "true": return true
when false, "false": return false
else
raise ArgumentError, "Invalid value '%s' for %s" %
[value.inspect, @name]
end
end
end
end
diff --git a/spec/unit/resource_reference.rb b/spec/unit/resource_reference.rb
index ef172d80a..cbbd6ef51 100755
--- a/spec/unit/resource_reference.rb
+++ b/spec/unit/resource_reference.rb
@@ -1,67 +1,73 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/resource_reference'
describe Puppet::ResourceReference do
it "should have a :title attribute" do
Puppet::ResourceReference.new(:file, "foo").title.should == "foo"
end
it "should canonize types to capitalized strings" do
Puppet::ResourceReference.new(:file, "foo").type.should == "File"
end
it "should canonize qualified types so all strings are capitalized" do
Puppet::ResourceReference.new("foo::bar", "foo").type.should == "Foo::Bar"
end
it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do
ref = Puppet::ResourceReference.new(:component, "foo")
ref.type.should == "Class"
ref.title.should == "foo"
end
it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do
ref = Puppet::ResourceReference.new(:component, "foo::bar[yay]")
ref.type.should == "Foo::Bar"
ref.title.should == "yay"
end
it "should set the type to 'Class' if it is nil and the title contains no square brackets" do
ref = Puppet::ResourceReference.new(nil, "yay")
ref.type.should == "Class"
ref.title.should == "yay"
end
it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do
ref = Puppet::ResourceReference.new(nil, "foo::bar[yay]")
ref.type.should == "Foo::Bar"
ref.title.should == "yay"
end
+
+ it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do
+ ref = Puppet::ResourceReference.new(nil, "foo::bar[baz[yay]]")
+ ref.type.should == "Foo::Bar"
+ ref.title.should =="baz[yay]"
+ end
end
describe Puppet::ResourceReference, "when resolving resources without a catalog" do
it "should be able to resolve builtin resources from their types" do
Puppet::Type.type(:file).expects(:[]).with("myfile").returns(:myfile)
Puppet::ResourceReference.new(:file, "myfile").resolve.should == :myfile
end
it "should be able to resolve defined resources from Components" do
Puppet::Type.type(:component).expects(:[]).with("Foo::Bar[yay]").returns(:mything)
Puppet::ResourceReference.new("foo::bar", "yay").resolve.should == :mything
end
end
describe Puppet::ResourceReference, "when resolving resources with a catalog" do
it "should resolve all resources using the catalog" do
config = mock 'catalog'
ref = Puppet::ResourceReference.new("foo::bar", "yay")
ref.catalog = config
config.expects(:resource).with("Foo::Bar[yay]").returns(:myresource)
ref.resolve.should == :myresource
end
end
diff --git a/spec/unit/util/settings.rb b/spec/unit/util/settings.rb
index f00afd1b7..b44a30eb2 100755
--- a/spec/unit/util/settings.rb
+++ b/spec/unit/util/settings.rb
@@ -1,648 +1,654 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Util::Settings, " when specifying defaults" do
before do
@settings = Puppet::Util::Settings.new
end
it "should start with no defined parameters" do
@settings.params.length.should == 0
end
it "should allow specification of default values associated with a section as an array" do
@settings.setdefaults(:section, :myvalue => ["defaultval", "my description"])
end
it "should not allow duplicate parameter specifications" do
@settings.setdefaults(:section, :myvalue => ["a", "b"])
lambda { @settings.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError)
end
it "should allow specification of default values associated with a section as a hash" do
@settings.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"})
end
it "should consider defined parameters to be valid" do
@settings.setdefaults(:section, :myvalue => ["defaultval", "my description"])
@settings.valid?(:myvalue).should be_true
end
it "should require a description when defaults are specified with an array" do
lambda { @settings.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError)
end
it "should require a description when defaults are specified with a hash" do
lambda { @settings.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError)
end
it "should support specifying owner, group, and mode when specifying files" do
@settings.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"})
end
it "should support specifying a short name" do
@settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"})
end
it "should fail when short names conflict" do
@settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"})
lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError)
end
end
describe Puppet::Util::Settings, " when setting values" do
before do
@settings = Puppet::Util::Settings.new
@settings.setdefaults :main, :myval => ["val", "desc"]
@settings.setdefaults :main, :bool => [true, "desc"]
end
it "should provide a method for setting values from other objects" do
@settings[:myval] = "something else"
@settings[:myval].should == "something else"
end
it "should support a getopt-specific mechanism for setting values" do
@settings.handlearg("--myval", "newval")
@settings[:myval].should == "newval"
end
it "should support a getopt-specific mechanism for turning booleans off" do
@settings.handlearg("--no-bool")
@settings[:bool].should == false
end
it "should support a getopt-specific mechanism for turning booleans on" do
# Turn it off first
@settings[:bool] = false
@settings.handlearg("--bool")
@settings[:bool].should == true
end
it "should clear the cache when setting getopt-specific values" do
@settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"]
@settings[:two].should == "whah yay"
@settings.handlearg("--one", "else")
@settings[:two].should == "else yay"
end
it "should not clear other values when setting getopt-specific values" do
@settings[:myval] = "yay"
@settings.handlearg("--no-bool")
@settings[:myval].should == "yay"
end
it "should call passed blocks when values are set" do
values = []
@settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }})
values.should == []
@settings[:hooker] = "something"
values.should == %w{something}
end
it "should provide an option to call passed blocks during definition" do
values = []
@settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :call_on_define => true, :hook => lambda { |v| values << v }})
values.should == %w{yay}
end
it "should pass the fully interpolated value to the hook when called on definition" do
values = []
@settings.setdefaults(:section, :one => ["test", "a"])
@settings.setdefaults(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_on_define => true, :hook => lambda { |v| values << v }})
values.should == %w{test/yay}
end
it "should munge values using the element-specific methods" do
@settings[:bool] = "false"
@settings[:bool].should == false
end
it "should prefer cli values to values set in Ruby code" do
@settings.handlearg("--myval", "cliarg")
@settings[:myval] = "memarg"
@settings[:myval].should == "cliarg"
end
end
describe Puppet::Util::Settings, " when returning values" do
before do
@settings = Puppet::Util::Settings.new
@settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"]
end
it "should provide a mechanism for returning set values" do
@settings[:one] = "other"
@settings[:one].should == "other"
end
it "should interpolate default values for other parameters into returned parameter values" do
@settings[:one].should == "ONE"
@settings[:two].should == "ONE TWO"
@settings[:three].should == "ONE ONE TWO THREE"
end
it "should interpolate default values that themselves need to be interpolated" do
@settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR"
end
it "should interpolate set values for other parameters into returned parameter values" do
@settings[:one] = "on3"
@settings[:two] = "$one tw0"
@settings[:three] = "$one $two thr33"
@settings[:four] = "$one $two $three f0ur"
@settings[:one].should == "on3"
@settings[:two].should == "on3 tw0"
@settings[:three].should == "on3 on3 tw0 thr33"
@settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur"
end
it "should not cache interpolated values such that stale information is returned" do
@settings[:two].should == "ONE TWO"
@settings[:one] = "one"
@settings[:two].should == "one TWO"
end
it "should not cache values such that information from one environment is returned for another environment" do
text = "[env1]\none = oneval\n[env2]\none = twoval\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@settings.stubs(:read_file).with(file).returns(text)
@settings.parse(file)
@settings.value(:one, "env1").should == "oneval"
@settings.value(:one, "env2").should == "twoval"
end
it "should have a name determined by the 'name' parameter" do
@settings.setdefaults(:whatever, :name => ["something", "yayness"])
@settings.name.should == :something
@settings[:name] = :other
@settings.name.should == :other
end
end
describe Puppet::Util::Settings, " when choosing which value to return" do
before do
@settings = Puppet::Util::Settings.new
@settings.setdefaults :section,
:one => ["ONE", "a"],
:name => ["myname", "w"]
end
it "should return default values if no values have been set" do
@settings[:one].should == "ONE"
end
it "should return values set on the cli before values set in the configuration file" do
text = "[main]\none = fileval\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@settings.stubs(:parse_file).returns(text)
@settings.handlearg("--one", "clival")
@settings.parse(file)
@settings[:one].should == "clival"
end
it "should return values set on the cli before values set in Ruby" do
@settings[:one] = "rubyval"
@settings.handlearg("--one", "clival")
@settings[:one].should == "clival"
end
it "should return values set in the executable-specific section before values set in the main section" do
text = "[main]\none = mainval\n[myname]\none = nameval\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@settings.stubs(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == "nameval"
end
it "should not return values outside of its search path" do
text = "[other]\none = oval\n"
file = "/some/file"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@settings.stubs(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == "ONE"
end
it "should return values in a specified environment" do
text = "[env]\none = envval\n"
file = "/some/file"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@settings.stubs(:read_file).with(file).returns(text)
@settings.parse(file)
@settings.value(:one, "env").should == "envval"
end
it "should return values in a specified environment before values in the main or name sections" do
text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n"
file = "/some/file"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@settings.stubs(:read_file).with(file).returns(text)
@settings.parse(file)
@settings.value(:one, "env").should == "envval"
end
end
describe Puppet::Util::Settings, " when parsing its configuration" do
before do
@settings = Puppet::Util::Settings.new
@settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"]
end
it "should return values set in the configuration file" do
text = "[main]
one = fileval
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == "fileval"
end
#484 - this should probably be in the regression area
it "should not throw an exception on unknown parameters" do
text = "[main]\nnosuchparam = mval\n"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
lambda { @settings.parse(file) }.should_not raise_error
end
it "should support an old parse method when per-executable configuration files still exist" do
# I'm not going to bother testing this method.
@settings.should respond_to(:old_parse)
end
it "should convert booleans in the configuration file into Ruby booleans" do
text = "[main]
one = true
two = false
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == true
@settings[:two].should == false
end
it "should convert integers in the configuration file into Ruby Integers" do
text = "[main]
one = 65
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == 65
end
it "should support specifying all metadata (owner, group, mode) in the configuration file" do
@settings.setdefaults :section, :myfile => ["/myfile", "a"]
text = "[main]
myfile = /other/file {owner = luke, group = luke, mode = 644}
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:myfile].should == "/other/file"
@settings.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"}
end
it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do
@settings.setdefaults :section, :myfile => ["/myfile", "a"]
text = "[main]
myfile = /other/file {owner = luke}
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:myfile].should == "/other/file"
@settings.metadata(:myfile).should == {:owner => "luke"}
end
it "should call hooks associated with values set in the configuration file" do
values = []
@settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
text = "[main]
mysetting = setval
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
values.should == ["setval"]
end
it "should not call the same hook for values set multiple times in the configuration file" do
values = []
@settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
text = "[main]
mysetting = setval
[puppet]
mysetting = other
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
values.should == ["setval"]
end
it "should pass the environment-specific value to the hook when one is available" do
values = []
@settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
@settings.setdefaults :section, :environment => ["yay", "a"]
@settings.setdefaults :section, :environments => ["yay,foo", "a"]
text = "[main]
mysetting = setval
[yay]
mysetting = other
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
values.should == ["other"]
end
it "should pass the interpolated value to the hook when one is available" do
values = []
@settings.setdefaults :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }}
@settings.setdefaults :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
text = "[main]
mysetting = $base/setval
"
file = "/some/file"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
values.should == ["yay/setval"]
end
it "should allow empty values" do
@settings.setdefaults :section, :myarg => ["myfile", "a"]
text = "[main]
myarg =
"
@settings.stubs(:read_file).returns(text)
@settings.parse("/some/file")
@settings[:myarg].should == ""
end
end
describe Puppet::Util::Settings, " when reparsing its configuration" do
before do
@settings = Puppet::Util::Settings.new
@settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"]
end
it "should replace in-memory values with on-file values" do
# Init the value
text = "[main]\none = disk-init\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/test/file")
@settings[:one] = "init"
@settings.file = file
# Now replace the value
text = "[main]\none = disk-replace\n"
# This is kinda ridiculous - the reason it parses twice is that
# it goes to parse again when we ask for the value, because the
# mock always says it should get reparsed.
@settings.expects(:read_file).with(file).returns(text).times(2)
@settings.reparse
@settings[:one].should == "disk-replace"
end
it "should retain parameters set by cli when configuration files are reparsed" do
@settings.handlearg("--one", "clival")
text = "[main]\none = on-disk\n"
file = mock 'file'
file.stubs(:file).returns("/test/file")
@settings.stubs(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == "clival"
end
it "should remove in-memory values that are no longer set in the file" do
# Init the value
text = "[main]\none = disk-init\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/test/file")
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
@settings[:one].should == "disk-init"
# Now replace the value
text = "[main]\ntwo = disk-replace\n"
@settings.expects(:read_file).with(file).returns(text)
@settings.parse(file)
#@settings.reparse
# The originally-overridden value should be replaced with the default
@settings[:one].should == "ONE"
# and we should now have the new value in memory
@settings[:two].should == "disk-replace"
end
end
describe Puppet::Util::Settings, " when being used to manage the host machine" do
before do
@settings = Puppet::Util::Settings.new
@settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"]
@settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "luke", :group => "johnny", :mode => 0755}
@settings.setdefaults :third, :thirddir => ["/thirddir", "b"]
@settings.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755}
end
def stub_transaction
@bucket = mock 'bucket'
@config = mock 'config'
@trans = mock 'transaction'
@settings.expects(:to_transportable).with(:whatever).returns(@bucket)
@bucket.expects(:to_catalog).returns(@config)
@config.expects(:apply).yields(@trans)
@config.stubs(:host_config=)
end
it "should provide a method that writes files with the correct modes" do
pending "Not converted from test/unit yet"
end
it "should provide a method that creates directories with the correct modes" do
Puppet::Util::SUIDManager.expects(:asuser).with("luke", "johnny").yields
Dir.expects(:mkdir).with("/otherdir", 0755)
@settings.mkdir(:otherdir)
end
it "should be able to create needed directories in a single section" do
Dir.expects(:mkdir).with("/maindir")
Dir.expects(:mkdir).with("/seconddir")
@settings.use(:main)
end
it "should be able to create needed directories in multiple sections" do
Dir.expects(:mkdir).with("/maindir")
Dir.expects(:mkdir).with("/seconddir")
Dir.expects(:mkdir).with("/thirddir")
@settings.use(:main, :third)
end
it "should provide a method to trigger enforcing of file modes on existing files and directories" do
pending "Not converted from test/unit yet"
end
it "should provide a method to convert the file mode enforcement into a Puppet manifest" do
pending "Not converted from test/unit yet"
end
it "should create files when configured to do so with the :create parameter"
it "should provide a method to convert the file mode enforcement into transportable resources" do
# Make it think we're root so it tries to manage user and group.
Puppet.features.stubs(:root?).returns(true)
File.stubs(:exist?).with("/myfile").returns(true)
trans = nil
trans = @settings.to_transportable
resources = []
trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject }
%w{/maindir /seconddir /otherdir /myfile}.each do |path|
obj = resources.find { |r| r.type == "file" and r.name == path }
if path.include?("dir")
obj[:ensure].should == :directory
else
# Do not create the file, just manage mode
obj[:ensure].should be_nil
end
obj.should be_instance_of(Puppet::TransObject)
case path
when "/otherdir":
obj[:owner].should == "luke"
obj[:group].should == "johnny"
obj[:mode].should == 0755
when "/myfile":
obj[:mode].should == 0755
end
end
end
it "should not try to manage user or group when not running as root" do
Puppet.features.stubs(:root?).returns(false)
trans = nil
trans = @settings.to_transportable(:other)
trans.delve do |obj|
next unless obj.is_a?(Puppet::TransObject)
obj[:owner].should be_nil
obj[:group].should be_nil
end
end
it "should add needed users and groups to the manifest when asked" do
# This is how we enable user/group management
@settings.setdefaults :main, :mkusers => [true, "w"]
Puppet.features.stubs(:root?).returns(false)
trans = nil
trans = @settings.to_transportable(:other)
resources = []
trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject and obj.type != "file" }
user = resources.find { |r| r.type == "user" }
user.should be_instance_of(Puppet::TransObject)
user.name.should == "luke"
user[:ensure].should == :present
# This should maybe be a separate test, but...
group = resources.find { |r| r.type == "group" }
group.should be_instance_of(Puppet::TransObject)
group.name.should == "johnny"
group[:ensure].should == :present
end
it "should ignore tags and schedules when creating files and directories"
it "should apply all resources in debug mode to reduce logging"
it "should not try to manage absent files" do
# Make it think we're root so it tries to manage user and group.
Puppet.features.stubs(:root?).returns(true)
trans = nil
trans = @settings.to_transportable
file = nil
trans.delve { |obj| file = obj if obj.name == "/myfile" }
file.should be_nil
end
it "should not try to manage files in memory" do
main = Puppet::Type.type(:file).create(:path => "/maindir")
trans = @settings.to_transportable
lambda { trans.to_catalog }.should_not raise_error
end
+ it "should ignore file settings whose values are not strings" do
+ @settings[:maindir] = false
+
+ lambda { trans = @settings.to_transportable }.should_not raise_error
+ end
+
it "should be able to turn the current configuration into a parseable manifest"
it "should convert octal numbers correctly when producing a manifest"
it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do
pending "Not converted from test/unit yet"
end
it "should not attempt to manage files within /dev" do
pending "Not converted from test/unit yet"
end
it "should not modify the stored state database when managing resources" do
Puppet::Util::Storage.expects(:store).never
Puppet::Util::Storage.expects(:load).never
Dir.expects(:mkdir).with("/maindir")
Dir.expects(:mkdir).with("/seconddir")
@settings.use(:main)
end
it "should convert all relative paths to fully-qualified paths (#795)" do
@settings[:myfile] = "unqualified"
dir = Dir.getwd
@settings[:myfile].should == File.join(dir, "unqualified")
end
it "should support a method for re-using all currently used sections" do
Dir.expects(:mkdir).with("/thirddir").times(2)
@settings.use(:third)
@settings.reuse
end
it "should fail if any resources fail" do
stub_transaction
@trans.expects(:any_failed?).returns(true)
proc { @settings.use(:whatever) }.should raise_error(RuntimeError)
end
after { Puppet::Type.allclear }
end
diff --git a/test/ral/types/exec.rb b/test/ral/types/exec.rb
index f718f944e..4133d8519 100755
--- a/test/ral/types/exec.rb
+++ b/test/ral/types/exec.rb
@@ -1,730 +1,770 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../lib/puppettest'
require 'puppettest'
class TestExec < Test::Unit::TestCase
include PuppetTest
def test_execution
command = nil
output = nil
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo"
)
}
assert_nothing_raised {
command.evaluate
}
assert_events([:executed_command], command)
end
def test_numvsstring
[0, "0"].each { |val|
Puppet.type(:exec).clear
Puppet.type(:component).clear
command = nil
output = nil
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo",
:returns => val
)
}
assert_events([:executed_command], command)
}
end
def test_path_or_qualified
command = nil
output = nil
assert_raise(Puppet::Error) {
command = Puppet.type(:exec).create(
:command => "echo"
)
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "echo",
:path => "/usr/bin:/bin:/usr/sbin:/sbin"
)
}
Puppet.type(:exec).clear
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo"
)
}
Puppet.type(:exec).clear
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo",
:path => "/usr/bin:/bin:/usr/sbin:/sbin"
)
}
end
def test_nonzero_returns
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "mkdir /this/directory/does/not/exist",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 1
)
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "touch /etc",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 1
)
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "thiscommanddoesnotexist",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 127
)
}
end
def test_cwdsettings
command = nil
dir = "/tmp"
wd = Dir.chdir(dir) {
Dir.getwd
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "pwd",
:cwd => dir,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 0
)
}
assert_events([:executed_command], command)
assert_equal(wd,command.output.chomp)
end
def test_refreshonly_functional
file = nil
cmd = nil
tmpfile = tempfile()
@@tmpfiles.push tmpfile
trans = nil
file = Puppet.type(:file).create(
:path => tmpfile,
:content => "yay"
)
# Get the file in sync
assert_apply(file)
# Now make an exec
maker = tempfile()
assert_nothing_raised {
cmd = Puppet.type(:exec).create(
:command => "touch %s" % maker,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:subscribe => file,
:refreshonly => true
)
}
assert(cmd, "did not make exec")
assert_nothing_raised do
assert(! cmd.check, "Check passed when refreshonly is set")
end
assert_events([], file, cmd)
assert(! FileTest.exists?(maker), "made file without refreshing")
# Now change our content, so we throw a refresh
file[:content] = "yayness"
assert_events([:file_changed, :triggered], file, cmd)
assert(FileTest.exists?(maker), "file was not made in refresh")
end
def test_refreshonly
cmd = true
assert_nothing_raised {
cmd = Puppet.type(:exec).create(
:command => "pwd",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:refreshonly => true
)
}
# Checks should always fail when refreshonly is enabled
assert(!cmd.check, "Check passed with refreshonly true")
# Now make sure it passes if we pass in "true"
assert(cmd.check(true), "Check failed with refreshonly true while refreshing")
# Now set it to false
cmd[:refreshonly] = false
assert(cmd.check, "Check failed with refreshonly false")
end
def test_creates
file = tempfile()
exec = nil
assert(! FileTest.exists?(file), "File already exists")
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:command => "touch %s" % file,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:creates => file
)
}
comp = mk_catalog("createstest", exec)
assert_events([:executed_command], comp, "creates")
assert_events([], comp, "creates")
end
# Verify that we can download the file that we're going to execute.
def test_retrievethenmkexe
exe = tempfile()
oexe = tempfile()
sh = %x{which sh}
File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" }
file = Puppet.type(:file).create(
:path => oexe,
:source => exe,
:mode => 0755
)
exec = Puppet.type(:exec).create(
:command => oexe,
:require => [:file, oexe]
)
comp = mk_catalog("Testing", file, exec)
assert_events([:file_created, :executed_command], comp)
end
# Verify that we auto-require any managed scripts.
def test_autorequire_files
exe = tempfile()
oexe = tempfile()
sh = %x{which sh}
File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" }
file = Puppet.type(:file).create(
:path => oexe,
:source => exe,
:mode => 755
)
basedir = File.dirname(oexe)
baseobj = Puppet.type(:file).create(
:path => basedir,
:source => exe,
:mode => 755
)
ofile = Puppet.type(:file).create(
:path => exe,
:mode => 755
)
exec = Puppet.type(:exec).create(
:command => oexe,
:path => ENV["PATH"],
:cwd => basedir
)
cat = Puppet.type(:exec).create(
:command => "cat %s %s" % [exe, oexe],
:path => ENV["PATH"]
)
rels = nil
assert_nothing_raised do
rels = exec.autorequire
end
# Verify we get the script itself
assert(rels.detect { |r| r.source == file }, "Exec did not autorequire its command")
# Verify we catch the cwd
assert(rels.detect { |r| r.source == baseobj }, "Exec did not autorequire its cwd")
# Verify we don't require ourselves
assert(! rels.detect { |r| r.source == ofile }, "Exec incorrectly required mentioned file")
assert(!exec.requires?(ofile), "Exec incorrectly required file")
# We not longer autorequire inline files
assert_nothing_raised do
rels = cat.autorequire
end
assert(! rels.detect { |r| r.source == ofile }, "Exec required second inline file")
assert(! rels.detect { |r| r.source == file }, "Exec required inline file")
end
def test_ifonly
afile = tempfile()
bfile = tempfile()
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:command => "touch %s" % bfile,
:onlyif => "test -f %s" % afile,
:path => ENV['PATH']
)
}
assert_events([], exec)
system("touch %s" % afile)
assert_events([:executed_command], exec)
assert_events([:executed_command], exec)
system("rm %s" % afile)
assert_events([], exec)
end
def test_unless
afile = tempfile()
bfile = tempfile()
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:command => "touch %s" % bfile,
:unless => "test -f %s" % afile,
:path => ENV['PATH']
)
}
comp = mk_catalog(exec)
assert_events([:executed_command], comp)
assert_events([:executed_command], comp)
system("touch %s" % afile)
assert_events([], comp)
assert_events([], comp)
system("rm %s" % afile)
assert_events([:executed_command], comp)
assert_events([:executed_command], comp)
end
if Puppet::Util::SUIDManager.uid == 0
# Verify that we can execute commands as a special user
def mknverify(file, user, group = nil, id = true)
File.umask(0022)
args = {
:command => "touch %s" % file,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
}
if user
#Puppet.warning "Using user %s" % user.name
if id
# convert to a string, because that's what the object expects
args[:user] = user.uid.to_s
else
args[:user] = user.name
end
end
if group
#Puppet.warning "Using group %s" % group.name
if id
args[:group] = group.gid.to_s
else
args[:group] = group.name
end
end
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(args)
}
comp = mk_catalog("usertest", exec)
assert_events([:executed_command], comp, "usertest")
assert(FileTest.exists?(file), "File does not exist")
if user
assert_equal(user.uid, File.stat(file).uid, "File UIDs do not match")
end
# We can't actually test group ownership, unfortunately, because
# behaviour changes wildlly based on platform.
Puppet::Type.allclear
end
def test_userngroup
file = tempfile()
[
[nonrootuser()], # just user, by name
[nonrootuser(), nil, true], # user, by uid
[nil, nonrootgroup()], # just group
[nil, nonrootgroup(), true], # just group, by id
[nonrootuser(), nonrootgroup()], # user and group, by name
[nonrootuser(), nonrootgroup(), true], # user and group, by id
].each { |ary|
mknverify(file, *ary) {
}
}
end
end
def test_logoutput
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:title => "logoutputesting",
:path => "/usr/bin:/bin",
:command => "echo logoutput is false",
:logoutput => false
)
}
assert_apply(exec)
assert_nothing_raised {
exec[:command] = "echo logoutput is true"
exec[:logoutput] = true
}
assert_apply(exec)
assert_nothing_raised {
exec[:command] = "echo logoutput is warning"
exec[:logoutput] = "warning"
}
assert_apply(exec)
end
def test_execthenfile
exec = nil
file = nil
basedir = tempfile()
path = File.join(basedir, "subfile")
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:title => "mkdir",
:path => "/usr/bin:/bin",
:creates => basedir,
:command => "mkdir %s; touch %s" % [basedir, path]
)
}
assert_nothing_raised {
file = Puppet.type(:file).create(
:path => basedir,
:recurse => true,
:mode => "755",
:require => ["exec", "mkdir"]
)
}
comp = mk_catalog(file, exec)
comp.finalize
assert_events([:executed_command, :file_changed], comp)
assert(FileTest.exists?(path), "Exec ran first")
assert(File.stat(path).mode & 007777 == 0755)
end
# Make sure all checks need to be fully qualified.
def test_falsevals
exec = nil
assert_nothing_raised do
exec = Puppet.type(:exec).create(
:command => "/bin/touch yayness"
)
end
Puppet.type(:exec).checks.each do |check|
klass = Puppet.type(:exec).paramclass(check)
next if klass.values.include? :false
assert_raise(Puppet::Error, "Check '%s' did not fail on false" % check) do
exec[check] = false
end
end
end
def test_createcwdandexe
exec1 = exec2 = nil
dir = tempfile()
file = tempfile()
assert_nothing_raised {
exec1 = Puppet.type(:exec).create(
:title => "one",
:path => ENV["PATH"],
:command => "mkdir #{dir}"
)
}
assert_nothing_raised("Could not create exec w/out existing cwd") {
exec2 = Puppet.type(:exec).create(
:title => "two",
:path => ENV["PATH"],
:command => "touch #{file}",
:cwd => dir
)
}
# Throw a check in there with our cwd and make sure it works
assert_nothing_raised("Could not check with a missing cwd") do
exec2[:unless] = "test -f /this/file/does/not/exist"
exec2.retrieve
end
assert_raise(Puppet::Error) do
exec2.property(:returns).sync
end
assert_nothing_raised do
exec2[:require] = exec1
end
assert_apply(exec1, exec2)
assert(FileTest.exists?(file))
end
def test_checkarrays
exec = nil
file = tempfile()
test = "test -f #{file}"
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "touch #{file}"
)
}
assert_nothing_raised {
exec[:unless] = test
}
assert_nothing_raised {
assert(exec.check, "Check did not pass")
}
assert_nothing_raised {
exec[:unless] = [test, test]
}
assert_nothing_raised {
exec.finish
}
assert_nothing_raised {
assert(exec.check, "Check did not pass")
}
assert_apply(exec)
assert_nothing_raised {
assert(! exec.check, "Check passed")
}
end
def test_missing_checks_cause_failures
# Solaris's sh exits with 1 here instead of 127
return if Facter.value(:operatingsystem) == "Solaris"
exec = Puppet::Type.type(:exec).create(
:command => "echo true",
:path => ENV["PATH"],
:onlyif => "/bin/nosuchthingexists"
)
assert_raise(ArgumentError, "Missing command did not raise error") {
exec.run("/bin/nosuchthingexists")
}
end
def test_envparam
exec = Puppet::Type.newexec(
:command => "echo $envtest",
:path => ENV["PATH"],
:env => "envtest=yayness"
)
assert(exec, "Could not make exec")
output = status = nil
assert_nothing_raised {
output, status = exec.run("echo $envtest")
}
assert_equal("yayness\n", output)
# Now check whether we can do multiline settings
assert_nothing_raised do
exec[:env] = "envtest=a list of things
and stuff"
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$envtest"')
}
assert_equal("a list of things\nand stuff\n", output)
# Now test arrays
assert_nothing_raised do
exec[:env] = ["funtest=A", "yaytest=B"]
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$funtest" "$yaytest"')
}
assert_equal("A B\n", output)
end
+ def test_environmentparam
+ exec = Puppet::Type.newexec(
+ :command => "echo $environmenttest",
+ :path => ENV["PATH"],
+ :environment => "environmenttest=yayness"
+ )
+
+ assert(exec, "Could not make exec")
+
+ output = status = nil
+ assert_nothing_raised {
+ output, status = exec.run("echo $environmenttest")
+ }
+
+ assert_equal("yayness\n", output)
+
+ # Now check whether we can do multiline settings
+ assert_nothing_raised do
+ exec[:environment] = "environmenttest=a list of things
+and stuff"
+ end
+
+ output = status = nil
+ assert_nothing_raised {
+ output, status = exec.run('echo "$environmenttest"')
+ }
+ assert_equal("a list of things\nand stuff\n", output)
+
+ # Now test arrays
+ assert_nothing_raised do
+ exec[:environment] = ["funtest=A", "yaytest=B"]
+ end
+
+ output = status = nil
+ assert_nothing_raised {
+ output, status = exec.run('echo "$funtest" "$yaytest"')
+ }
+ assert_equal("A B\n", output)
+ end
+
def test_timeout
exec = Puppet::Type.type(:exec).create(:command => "sleep 1", :path => ENV["PATH"], :timeout => "0.2")
time = Time.now
assert_raise(Timeout::Error) {
exec.run("sleep 1")
}
Puppet.info "%s seconds, vs a timeout of %s" % [Time.now.to_f - time.to_f, exec[:timeout]]
assert_apply(exec)
end
# Testing #470
def test_run_as_created_user
exec = nil
if Process.uid == 0
user = "nosuchuser"
assert_nothing_raised("Could not create exec with non-existent user") do
exec = Puppet::Type.type(:exec).create(
:command => "/bin/echo yay",
:user => user
)
end
end
# Now try the group
group = "nosuchgroup"
assert_nothing_raised("Could not create exec with non-existent user") do
exec = Puppet::Type.type(:exec).create(
:command => "/bin/echo yay",
:group => group
)
end
end
# make sure paths work both as arrays and strings
def test_paths_as_arrays
path = %w{/usr/bin /usr/sbin /sbin}
exec = nil
assert_nothing_raised("Could not use an array for the path") do
exec = Puppet::Type.type(:exec).create(:command => "echo yay",
:path => path)
end
assert_equal(path, exec[:path], "array-based path did not match")
assert_nothing_raised("Could not use a string for the path") do
exec = Puppet::Type.type(:exec).create(:command => "echo yay",
:path => path.join(":"))
end
assert_equal(path, exec[:path], "string-based path did not match")
assert_nothing_raised("Could not use a colon-separated strings in an array for the path") do
exec = Puppet::Type.type(:exec).create(:command => "echo yay",
:path => ["/usr/bin", "/usr/sbin:/sbin"])
end
assert_equal(path, exec[:path], "colon-separated array path did not match")
end
def test_checks_apply_to_refresh
file = tempfile()
maker = tempfile()
exec = Puppet::Type.type(:exec).create(
:title => "maker",
:command => "touch #{maker}",
:path => ENV["PATH"]
)
# Make sure it runs normally
assert_apply(exec)
assert(FileTest.exists?(maker), "exec did not run")
File.unlink(maker)
# Now make sure it refreshes
assert_nothing_raised("Failed to refresh exec") do
exec.refresh
end
assert(FileTest.exists?(maker), "exec did not run refresh")
File.unlink(maker)
# Now add the checks
exec[:creates] = file
# Make sure it runs when the file doesn't exist
assert_nothing_raised("Failed to refresh exec") do
exec.refresh
end
assert(FileTest.exists?(maker), "exec did not refresh when checks passed")
File.unlink(maker)
# Now create the file and make sure it doesn't refresh
File.open(file, "w") { |f| f.puts "" }
assert_nothing_raised("Failed to refresh exec") do
exec.refresh
end
assert(! FileTest.exists?(maker), "exec refreshed with failing checks")
end
def test_explicit_refresh
refresher = tempfile()
maker = tempfile()
exec = Puppet::Type.type(:exec).create(
:title => "maker",
:command => "touch #{maker}",
:path => ENV["PATH"]
)
# Call refresh normally
assert_nothing_raised do
exec.refresh
end
# Make sure it created the normal file
assert(FileTest.exists?(maker), "normal refresh did not work")
File.unlink(maker)
# Now reset refresh, and make sure it wins
assert_nothing_raised("Could not set refresh parameter") do
exec[:refresh] = "touch #{refresher}"
end
assert_nothing_raised do
exec.refresh
end
# Make sure it created the normal file
assert(FileTest.exists?(refresher), "refresh param was ignored")
assert(! FileTest.exists?(maker), "refresh param also ran command")
end
if Puppet.features.root?
def test_autorequire_user
user = Puppet::Type.type(:user).create(:name => "yay")
exec = Puppet::Type.type(:exec).create(:command => "/bin/echo fun", :user => "yay")
rels = nil
assert_nothing_raised("Could not evaluate autorequire") do
rels = exec.autorequire
end
assert(rels.find { |r| r.source == user and r.target == exec }, "Exec did not autorequire user")
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:29 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10075902
Default Alt Text
(235 KB)

Event Timeline