diff --git a/CHANGELOG b/CHANGELOG index a82fd567a..825fad225 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,990 +1,1002 @@ - Removed the loglevels from the valid values for 'logoutput' + Fixing #1062 by moving the yamldir setting to its own yaml + section. This should keep the yamldir from being created + on clients. + + Fixed #1047 -- Puppet's parser no longer changes the order + in which statements are evaluated, which means that case + statements can now set variables that are used by other + variables. + + Fixed #1063 -- the master correctly logs syntax errors when + reparsing during a single run. + + Removed the loglevels from the valid values for `logoutput` in the Exec resource type -- the log levels are specified - using the 'loglevel' parameter, not 'logoutput'. This never - worked, or at least hasn't for ages, and now the docs are + using the `loglevel` parameter, not `logoutput`. This never + worked, or at least hasn`t for ages, and now the docs are just correct. Somewhat refactored fileserving so that it no longer caches any objects, nor does it use Puppet's RAL resources. In the process, I fixed #894 (you can now copy links) and refactored other classes as necessary. Mostly it was fixing tests. Hopefully partially fixed #1010 -- clients should now fail to install files whose checksums do not match the checksum from the server. Fixed #1018 -- resources now have their namevars added as aliases in the resource catalog, just like they were added in the resource classes. Fixed #1037 -- remote unreadable files no longer have the permission denied exceptions caught, thus forbidding them from being replaced with 'nil'. Fixed #1043 -- autoloading now searches the plugins directory in each module, in addition to the lib directory. The 'lib' directory is also deprecated, but supported for now to give people a chance to convert. Fixed #1003 -- Applying DavidS's patch to fix searching for tags in sql. Fixed #992 -- Puppet is now compatible with gems 1.0.1. Fixed #968 again, this time with tests -- parseonly works, including not compiling the configurations, and also storeconfigs is no longer required during parse-testing. Fixed #1021 -- the problem was that my method of determining the in-degree sometimes resulted in a lower number than the number of in-edges. Fixed #997 -- virtual defined types are no longer evaluated. NOTE: This introduces a behaviour change, in that you previously could realize a resource within a virtual defined resource, and now you must realize the entire defined resource, rather than just the contained resource. Fixed #1030 - class and definition evaluation has been significantly refactored, fixing this problem and making the whole interplay between the classes, definitions, and nodes, and the Compile class much cleaner. Exec resources must now have unique names, although the commands can still be duplicated. This is easily accomplished by just specifying a unique name with whatever (unique or otherwise) command you need. 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/puppetrun b/bin/puppetrun index c92b59fc6..d0823b9c5 100755 --- a/bin/puppetrun +++ b/bin/puppetrun @@ -1,404 +1,399 @@ #!/usr/bin/env ruby # # = Synopsis # # Trigger a puppetd run on a set of hosts. # # = Usage # # puppetrun [-a|--all] [-c|--class ] [-d|--debug] [-f|--foreground] # [-h|--help] [--host ] [--no-fqdn] [--ignoreschedules] # [-t|--tag ] [--test] # # = Description # # This script can be used to connect to a set of machines running +puppetd+ # and trigger them to run their configurations. The most common usage would # be to specify a class of hosts and a set of tags, and +puppetrun+ would # look up in LDAP all of the hosts matching that class, then connect to # each host and trigger a run of all of the objects with the specified tags. # # If you are not storing your host configurations in LDAP, you can specify # hosts manually. # # You will most likely have to run +puppetrun+ as root to get access to # the SSL certificates. # # +puppetrun+ reads +puppetmaster+'s configuration file, so that it can copy # things like LDAP settings. # # = Usage Notes # # +puppetrun+ is useless unless +puppetd+ is listening. See its documentation # for more information, but the gist is that you must enable +listen+ on the # +puppetd+ daemon, either using +--listen+ on the command line or adding # 'listen: true' in its config file. In addition, you need to set the daemons # up to specifically allow connections by creating the +namespaceauth+ file, # normally at '/etc/puppet/namespaceauth.conf'. This file specifies who has # access to each namespace; if you create the file you must add every namespace # you want any Puppet daemon to allow -- it is currently global to all Puppet # daemons. # # An example file looks like this:: # # [fileserver] # allow *.madstop.com # # [puppetmaster] # allow *.madstop.com # # [puppetrunner] # allow culain.madstop.com # # This is what you would install on your Puppet master; non-master hosts could # leave off the 'fileserver' and 'puppetmaster' namespaces. # # Expect more documentation on this eventually. # # = Options # # Note that any configuration parameter that's valid in the configuration file # is also a valid long argument. For example, 'ssldir' is a valid configuration # parameter, so you can specify '--ssldir ' 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 puppetmasterdd with # '--genconfig'. # # # all:: # Connect to all available hosts. Requires LDAP support at this point. # # class:: # Specify a class of machines to which to connect. This only works if you # have LDAP configured, at the moment. # # debug:: # Enable full debugging. # # foreground:: # Run each configuration in the foreground; that is, when connecting to a host, # do not return until the host has finished its run. The default is false. # # help:: # Print this help message # # host:: # A specific host to which to connect. This flag can be specified more # than once. # # ignoreschedules:: # Whether the client should ignore schedules when running its configuration. # This can be used to force the client to perform work it would not normally # perform so soon. The default is false. # # parallel:: # How parallel to make the connections. Parallelization is provided by forking # for each client to which to connect. The default is 1, meaning serial execution. # # tag:: # Specify a tag for selecting the objects to apply. Does not work with the # --test option. # # # test:: # Print the hosts you would connect to but do not actually connect. This # option requires LDAP support at this point. # # = Example # # sudo puppetrun -p 10 --host host1 --host host2 -t remotefile -t webserver # # = Author # # Luke Kanies # # = Copyright # # Copyright (c) 2005 Reductive Labs, LLC # Licensed under the GNU Public License [:INT, :TERM].each do |signal| trap(signal) do $stderr.puts "Cancelling" exit(1) end end begin require 'rubygems' rescue LoadError # Nothing; we were just doing this just in case end begin require 'ldap' rescue LoadError $stderr.puts "Failed to load ruby LDAP library. LDAP functionality will not be available" end require 'puppet' require 'puppet/network/client' require 'getoptlong' # Look up all nodes matching a given class in LDAP. def ldapnodes(klass, fqdn = true) unless defined? @ldap setupldap() end hosts = [] filter = nil if klass == :all filter = "objectclass=puppetclient" else filter = "puppetclass=#{klass}" end @ldap.search(Puppet[:ldapbase], 2, filter, "cn") do |entry| # Skip the default host entry if entry.dn =~ /cn=default,/ $stderr.puts "Skipping default host entry" next end if fqdn hosts << entry.dn.sub("cn=",'').sub(/ou=hosts,/i, '').gsub(",dc=",".") else hosts << entry.get_values("cn")[0] end end return hosts end def setupldap begin @ldap = Puppet::Parser::Interpreter.ldap() rescue => detail $stderr.puts "Could not connect to LDAP: %s" % detail exit(34) end end flags = [ [ "--all", "-a", GetoptLong::NO_ARGUMENT ], [ "--tag", "-t", GetoptLong::REQUIRED_ARGUMENT ], [ "--class", "-c", GetoptLong::REQUIRED_ARGUMENT ], [ "--foreground", "-f", GetoptLong::NO_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--host", GetoptLong::REQUIRED_ARGUMENT ], [ "--parallel", "-p", GetoptLong::REQUIRED_ARGUMENT ], [ "--no-fqdn", "-n", GetoptLong::NO_ARGUMENT ], [ "--test", GetoptLong::NO_ARGUMENT ], [ "--version", "-V", GetoptLong::NO_ARGUMENT ] ] # Add all of the config parameters as valid options. Puppet.settings.addargs(flags) result = GetoptLong.new(*flags) options = { :ignoreschedules => false, :foreground => false, :parallel => 1, :debug => false, :test => false, :all => false, :verbose => true, :fqdn => true } hosts = [] classes = [] tags = [] Puppet::Util::Log.newdestination(:console) begin result.each { |opt,arg| case opt when "--version" puts "%s" % Puppet.version exit when "--ignoreschedules" options[:ignoreschedules] = true when "--no-fqdn" options[:fqdn] = false when "--all" options[:all] = true when "--test" options[:test] = true when "--tag" tags << arg when "--class" classes << arg when "--host" hosts << arg when "--help" if Puppet.features.usage? RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end when "--parallel" begin options[:parallel] = Integer(arg) rescue $stderr.puts "Could not convert %s to an integer" % arg.inspect exit(23) end when "--foreground" options[:foreground] = true when "--debug" options[:debug] = true else Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail $stderr.puts "Try '#{$0} --help'" exit(1) end if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end # Now parse the config -config = File.join(Puppet[:confdir], "puppet.conf") -Puppet.parse_config(config) - -if File.exists? config - Puppet.settings.parse(config) -end +Puppet.parse_config if Puppet[:node_terminus] = "ldap" if options[:all] hosts = ldapnodes(:all, options[:fqdn]) puts "all: %s" % hosts.join(", ") else classes.each do |klass| list = ldapnodes(klass, options[:fqdn]) puts "%s: %s" % [klass, list.join(", ")] hosts += list end end elsif ! classes.empty? $stderr.puts "You must be using LDAP to specify host classes" exit(24) end if tags.empty? tags = "" else tags = tags.join(",") end children = {} # If we get a signal, then kill all of our children and get out. [:INT, :TERM].each do |signal| trap(signal) do Puppet.notice "Caught #{signal}; shutting down" children.each do |pid, host| Process.kill("INT", pid) end waitall exit(1) end end if options[:test] puts "Skipping execution in test mode" exit(0) end todo = hosts.dup failures = [] # Now do the actual work go = true while go # If we don't have enough children in process and we still have hosts left to # do, then do the next host. if children.length < options[:parallel] and ! todo.empty? host = todo.shift pid = fork do # First make sure the client is up out = %x{ping -c 1 #{host}} unless $? == 0 $stderr.print "Could not contact %s\n" % host next end client = Puppet::Network::Client.runner.new( :Server => host, :Port => Puppet[:puppetport] ) print "Triggering %s\n" % host begin result = client.run(tags, options[:ignoreschedules], options[:foreground]) rescue => detail $stderr.puts "Host %s failed: %s\n" % [host, detail] exit(2) end case result when "success": exit(0) when "running": $stderr.puts "Host %s is already running" % host exit(3) else $stderr.puts "Host %s returned unknown answer '%s'" % [host, result] exit(12) end end children[pid] = host else # Else, see if we can reap a process. begin pid = Process.wait if host = children[pid] # Remove our host from the list of children, so the parallelization # continues working. children.delete(pid) if $?.exitstatus != 0 failures << host end print "%s finished with exit code %s\n" % [host, $?.exitstatus] else $stderr.puts "Could not find host for PID %s with status %s" % [pid, $?.exitstatus] end rescue Errno::ECHILD # There are no children left, so just exit unless there are still # children left to do. next unless todo.empty? if failures.empty? puts "Finished" exit(0) else puts "Failed: %s" % failures.join(", ") exit(3) end end end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 520a18d1a..77792172f 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,676 +1,679 @@ # 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 '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/.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."] ) + + Puppet.setdefaults(:yaml, + :yamldir => {:default => "$vardir/yaml", :owner => "$user", :group => "$user", :mode => "750", + :desc => "The directory in which YAML data is stored, usually in a subdirectory."} + ) end diff --git a/lib/puppet/file_serving/file_base.rb b/lib/puppet/file_serving/file_base.rb index 06b3ad9ef..e87d683aa 100644 --- a/lib/puppet/file_serving/file_base.rb +++ b/lib/puppet/file_serving/file_base.rb @@ -1,75 +1,76 @@ # # Created by Luke Kanies on 2007-10-22. # Copyright (c) 2007. All rights reserved. require 'puppet/file_serving' # The base class for Content and Metadata; provides common # functionality like the behaviour around links. class Puppet::FileServing::FileBase attr_accessor :key # Does our file exist? def exist? begin stat return true rescue => detail return false end end # Return the full path to our file. Fails if there's no path set. def full_path raise(ArgumentError, "You must set a path to get a file's path") unless self.path if relative_path.nil? or relative_path == "" path else File.join(path, relative_path) end end def initialize(key, options = {}) @key = key @links = :manage options.each do |param, value| begin send param.to_s + "=", value rescue NoMethodError raise ArgumentError, "Invalid option %s for %s" % [param, self.class] end end end # Determine how we deal with links. attr_reader :links def links=(value) + value = :manage if value == :ignore raise(ArgumentError, ":links can only be set to :manage or :follow") unless [:manage, :follow].include?(value) @links = value end # Set our base path. attr_reader :path def path=(path) raise ArgumentError.new("Paths must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/ @path = path end # Set a relative path; this is used for recursion, and sets # the file's path relative to the initial recursion point. attr_reader :relative_path def relative_path=(path) raise ArgumentError.new("Relative paths must not be fully qualified") if path =~ /^#{::File::SEPARATOR}/ @relative_path = path end # Stat our file, using the appropriate link-sensitive method. def stat unless defined?(@stat_method) @stat_method = self.links == :manage ? :lstat : :stat end File.send(@stat_method, full_path()) end end diff --git a/lib/puppet/network/client.rb b/lib/puppet/network/client.rb index 0a0a72345..cf1782f79 100644 --- a/lib/puppet/network/client.rb +++ b/lib/puppet/network/client.rb @@ -1,189 +1,191 @@ # the available clients require 'puppet' require 'puppet/daemon' require 'puppet/network/xmlrpc/client' require 'puppet/util/subclass_loader' require 'puppet/util/methodhelper' require 'puppet/sslcertificates/support' +require 'puppet/network/handler' + require 'net/http' # Some versions of ruby don't have this method defined, which basically causes # us to never use ssl. Yay. class Net::HTTP def use_ssl? if defined? @use_ssl @use_ssl else false end end # JJM: This is a "backport" of sorts to older ruby versions which # do not have this accessor. See #896 for more information. unless Net::HTTP.instance_methods.include? "enable_post_connection_check" attr_accessor :enable_post_connection_check end end # The base class for all of the clients. Many clients just directly # call methods, but some of them need to do some extra work or # provide a different interface. class Puppet::Network::Client Client = self include Puppet::Daemon include Puppet::Util extend Puppet::Util::SubclassLoader include Puppet::Util::MethodHelper # This handles reading in the key and such-like. include Puppet::SSLCertificates::Support attr_accessor :schedule, :lastrun, :local, :stopping attr_reader :driver # Set up subclass loading handle_subclasses :client, "puppet/network/client" # Determine what clients look for when being passed an object for local # client/server stuff. E.g., you could call Client::CA.new(:CA => ca). def self.drivername unless defined? @drivername @drivername = self.name end @drivername end # Figure out the handler for our client. def self.handler unless defined? @handler @handler = Puppet::Network::Handler.handler(self.name) end @handler end # The class that handles xmlrpc interaction for us. def self.xmlrpc_client unless defined? @xmlrpc_client @xmlrpc_client = Puppet::Network::XMLRPCClient.handler_class(self.handler) end @xmlrpc_client end # Create our client. def initialize(hash) # to whom do we connect? @server = nil if hash.include?(:Cache) @cache = hash[:Cache] else @cache = true end driverparam = self.class.drivername if hash.include?(:Server) args = {:Server => hash[:Server]} @server = hash[:Server] args[:Port] = hash[:Port] || Puppet[:masterport] @driver = self.class.xmlrpc_client.new(args) self.read_cert # We have to start the HTTP connection manually before we start # sending it requests or keep-alive won't work. @driver.start if @driver.respond_to? :start @local = false elsif hash.include?(driverparam) @driver = hash[driverparam] if @driver == true @driver = self.class.handler.new end @local = true else raise Puppet::Network::ClientError, "%s must be passed a Server or %s" % [self.class, driverparam] end end # Are we a local client? def local? if defined? @local and @local true else false end end # Make sure we set the driver up when we read the cert in. def recycle_connection @driver.recycle_connection if @driver.respond_to?(:recycle_connection) end # A wrapper method to run and then store the last run time def runnow if self.stopping Puppet.notice "In shutdown progress; skipping run" return end begin self.run self.lastrun = Time.now.to_i rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not run %s: %s" % [self.class, detail] end end def run raise Puppet::DevError, "Client type %s did not override run" % self.class end def scheduled? if sched = self.schedule return sched.match?(self.lastrun) else return true end end def shutdown if self.stopping Puppet.notice "Already in shutdown" else self.stopping = true if self.respond_to? :running? and self.running? Puppet::Util::Storage.store end rmpidfile() end end # Start listening for events. We're pretty much just listening for # timer events here. def start # Create our timer. Puppet will handle observing it and such. timer = Puppet.newtimer( :interval => Puppet[:runinterval], :tolerance => 1, :start? => true ) do begin self.runnow if self.scheduled? rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not run client; got otherwise uncaught exception: %s" % detail end end # Run once before we start following the timer self.runnow end require 'puppet/network/client/proxy' end diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb index b66fd6bba..8f09aa922 100644 --- a/lib/puppet/parser/ast/astarray.rb +++ b/lib/puppet/parser/ast/astarray.rb @@ -1,75 +1,57 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST # The basic container class. This object behaves almost identically # to a normal array except at initialization time. Note that its name # is 'AST::ASTArray', rather than plain 'AST::Array'; I had too many # bugs when it was just 'AST::Array', because things like # 'object.is_a?(Array)' never behaved as I expected. class ASTArray < Branch include Enumerable # Return a child by index. Probably never used. def [](index) @children[index] end # Evaluate our children. def evaluate(scope) - rets = nil - # We basically always operate declaratively, and when we - # do we need to evaluate the settor-like statements first. This - # is basically variable and type-default declarations. - # This is such a stupid hack. I've no real idea how to make a - # "real" declarative language, so I hack it so it looks like - # one, yay. - settors = [] - others = [] - # Make a new array, so we don't have to deal with the details of # flattening and such items = [] # First clean out any AST::ASTArrays @children.each { |child| if child.instance_of?(AST::ASTArray) child.each do |ac| - if ac.class.settor? - settors << ac - else - others << ac - end + items << ac end else - if child.class.settor? - settors << child - else - others << child - end + items << child end } - rets = [settors, others].flatten.collect { |child| + rets = items.flatten.collect { |child| child.safeevaluate(scope) } return rets.reject { |o| o.nil? } end def push(*ary) ary.each { |child| #Puppet.debug "adding %s(%s) of type %s to %s" % # [child, child.object_id, child.class.to_s.sub(/.+::/,''), # self.object_id] @children.push(child) } return self end end # A simple container class, containing the parameters for an object. # Used for abstracting the grammar declarations. Basically unnecessary # except that I kept finding bugs because I had too many arrays that # meant completely different things. class ResourceInstance < ASTArray; end end diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 132ec15db..70cd6e11a 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -1,472 +1,471 @@ # Created by Luke A. Kanies on 2007-08-13. # Copyright (c) 2007. All rights reserved. require 'puppet/node' require 'puppet/node/catalog' require 'puppet/util/errors' # Maintain a graph of scopes, along with a bunch of data # about the individual catalog we're compiling. class Puppet::Parser::Compiler include Puppet::Util include Puppet::Util::Errors attr_reader :parser, :node, :facts, :collections, :catalog, :node_scope # Add a collection to the global list. def add_collection(coll) @collections << coll end # Store a resource override. def add_override(override) # If possible, merge the override in immediately. if resource = @catalog.resource(override.ref) resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end # Store a resource in our resource table. def add_resource(scope, resource) # Note that this will fail if the resource is not unique. @catalog.add_resource(resource) # And in the resource graph. At some point, this might supercede # the global resource table, but the table is a lot faster # so it makes sense to maintain for now. @catalog.add_edge(scope.resource, resource) end # Do we use nodes found in the code, vs. the external node sources? def ast_nodes? parser.nodes.length > 0 end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) if existing = @class_scopes[name] if existing.nodescope? != scope.nodescope? raise Puppet::ParseError, "Cannot have classes, nodes, or definitions with the same name" else raise Puppet::DevError, "Somehow evaluated %s %s twice" % [ existing.nodescope? ? "node" : "class", name] end end @class_scopes[name] = scope @catalog.add_class(name) unless name == "" end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name if klass.respond_to?(:classname) @class_scopes[klass.classname] else @class_scopes[klass] end end # Return a list of all of the defined classes. def classlist return @catalog.classes end # Compiler our catalog. This mostly revolves around finding and evaluating classes. # This is the main entry into our catalog. def compile # Set the client's parameters into the top scope. set_node_parameters() evaluate_main() evaluate_ast_node() evaluate_node_classes() evaluate_generators() finish() fail_on_unevaluated() if Puppet[:storeconfigs] store() end return @catalog end # LAK:FIXME There are no tests for this. def delete_collection(coll) @collections.delete(coll) if @collections.include?(coll) end # Return the node's environment. def environment unless defined? @environment if node.environment and node.environment != "" @environment = node.environment else @environment = nil end end @environment end # Evaluate all of the classes specified by the node. def evaluate_node_classes evaluate_classes(@node.classes, topscope) end # Evaluate each specified class in turn. If there are any classes we can't # find, just tag the catalog and move on. This method really just # creates resource objects that point back to the classes, and then the # resources are themselves evaluated later in the process. def evaluate_classes(classes, scope, lazy_evaluate = true) unless scope.source raise Puppet::DevError, "No source for scope passed to evaluate_classes" end found = [] classes.each do |name| # If we can find the class, then make a resource that will evaluate it. if klass = scope.findclass(name) found << name and next if class_scope(klass) resource = klass.evaluate(scope) # If they've disabled lazy evaluation (which the :include function does), # then evaluate our resource immediately. resource.evaluate unless lazy_evaluate found << name else Puppet.info "Could not find class %s for %s" % [name, node.name] @catalog.tag(name) end end found end # Return a resource by either its ref or its type and title. def findresource(*args) @catalog.resource(*args) end # Set up our compile. We require a parser # and a node object; the parser is so we can look up classes # and AST nodes, and the node has all of the client's info, # like facts and environment. def initialize(node, parser, options = {}) @node = node @parser = parser options.each do |param, value| begin send(param.to_s + "=", value) rescue NoMethodError raise ArgumentError, "Compiler objects do not accept %s" % param end end initvars() init_main() end # Create a new scope, with either a specified parent scope or # using the top scope. Adds an edge between the scope and # its parent to the graph. def newscope(parent, options = {}) parent ||= topscope options[:compiler] = self options[:parser] ||= self.parser scope = Puppet::Parser::Scope.new(options) @scope_graph.add_edge(parent, scope) scope end # Find the parent of a given scope. Assumes scopes only ever have # one in edge, which will always be true. def parent(scope) if ary = @scope_graph.adjacent(scope, :direction => :in) and ary.length > 0 ary[0] else nil end end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end # Return a list of all resources. def resources @catalog.vertices end # The top scope is usually the top-level scope, but if we're using AST nodes, # then it is instead the node's scope. def topscope node_scope || @topscope end private # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node return unless ast_nodes? # Now see if we can find the node. astnode = nil @node.names.each do |name| break if astnode = @parser.nodes[name.to_s.downcase] end unless (astnode ||= @parser.nodes["default"]) raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") end # Create a resource to model this node, and then add it to the list # of resources. resource = astnode.evaluate(topscope) resource.evaluate # Now set the node scope appropriately, so that :topscope can # behave differently. @node_scope = class_scope(astnode) end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? found_something = false exceptwrap do # We have to iterate over a dup of the array because # collections can delete themselves from the list, which # changes its length and causes some collections to get missed. @collections.dup.each do |collection| found_something = true if collection.evaluate end end return found_something end # Make sure all of our resources have been evaluated into native resources. # We return true if any resources have, so that we know to continue the # evaluate_generators loop. def evaluate_definitions exceptwrap do if ary = unevaluated_resources evaluated = false ary.each do |resource| if not resource.virtual? resource.evaluate evaluated = true end end # If we evaluated, let the loop know. return evaluated else return false end end end # Iterate over collections and resources until we're sure that the whole # compile is evaluated. This is necessary because both collections # and defined resources can generate new resources, which themselves could # be defined resources. def evaluate_generators count = 0 loop do done = true # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions break if done count += 1 if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host catalog" end end end # Find and evaluate our main object, if possible. def evaluate_main @main = @parser.findclass("", "") || @parser.newclass("") @topscope.source = @main @main_resource = Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => @topscope, :source => @main) @topscope.resource = @main_resource @catalog.add_resource(@main_resource) @main_resource.evaluate end # Make sure the entire catalog is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = [] @resource_overrides.each do |name, overrides| remaining += overrides end unless remaining.empty? fail Puppet::ParseError, "Could not find object(s) %s" % remaining.collect { |o| o.ref }.join(", ") end end # Make sure we don't have any remaining collections that specifically # look for resources, because we want to consider those to be # parse errors. def fail_on_unevaluated_resource_collections remaining = [] @collections.each do |coll| # We're only interested in the 'resource' collections, # which result from direct calls of 'realize'. Anything # else is allowed not to return resources. # Collect all of them, so we have a useful error. if r = coll.resources if r.is_a?(Array) remaining += r else remaining << r end end end unless remaining.empty? raise Puppet::ParseError, "Failed to realize virtual resources %s" % remaining.join(', ') end end # Make sure all of our resources and such have done any last work # necessary. def finish - @catalog.resources.each do |name| - resource = @catalog.resource(name) - + #@catalog.resources.each do |name| + @catalog.vertices.each do |resource| # Add in any resource overrides. if overrides = resource_overrides(resource) overrides.each do |over| resource.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end resource.finish if resource.respond_to?(:finish) end end # Initialize the top-level scope, class, and resource. def init_main # Create our initial scope and a resource that will evaluate main. @topscope = Puppet::Parser::Scope.new(:compiler => self, :parser => self.parser) @scope_graph.add_vertex(@topscope) end # Set up all of our internal variables. def initvars # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} # The list of objects that will available for export. @exported_resources = {} # The list of overrides. This is used to cache overrides on objects # that don't exist yet. We store an array of each override. @resource_overrides = Hash.new do |overs, ref| overs[ref] = [] end # The list of collections that have been created. This is a global list, # but they each refer back to the scope that created them. @collections = [] # A graph for maintaining scope relationships. @scope_graph = Puppet::SimpleGraph.new # For maintaining the relationship between scopes and their resources. @catalog = Puppet::Node::Catalog.new(@node.name) @catalog.version = @parser.version end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope.setvar(param, value) end end # Store the catalog into the database. def store unless Puppet.features.rails? raise Puppet::Error, "storeconfigs is enabled but rails is unavailable" end unless ActiveRecord::Base.connected? Puppet::Rails.connect end # We used to have hooks here for forking and saving, but I don't # think it's worth retaining at this point. store_to_active_record(@node, @catalog.vertices) end # Do the actual storage. def store_to_active_record(node, resources) begin # We store all of the objects, even the collectable ones benchmark(:info, "Stored catalog for #{node.name}") do Puppet::Rails::Host.transaction do Puppet::Rails::Host.store(node, resources) end end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not store configs: %s" % detail.to_s end end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources ary = @catalog.vertices.reject { |resource| resource.builtin? or resource.evaluated? } if ary.empty? return nil else return ary end end end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index d4655c403..f27c1c5c8 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,95 +1,99 @@ require 'puppet' require 'timeout' require 'puppet/rails' require 'puppet/util/methodhelper' require 'puppet/parser/parser' require 'puppet/parser/compiler' require 'puppet/parser/scope' # The interpreter is a very simple entry-point class that # manages the existence of the parser (e.g., replacing it # when files are reparsed). You can feed it a node and # get the node's catalog back. class Puppet::Parser::Interpreter include Puppet::Util attr_accessor :usenodes include Puppet::Util::Errors # Determine the configuration version for a given node's environment. def configuration_version(node) parser(node.environment).version end # evaluate our whole tree def compile(node) raise Puppet::ParseError, "Could not parse configuration; cannot compile on node %s" % node.name unless env_parser = parser(node.environment) begin return Puppet::Parser::Compiler.new(node, env_parser).compile rescue => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, detail.to_s + " on node %s" % node.name end end # create our interpreter def initialize # The class won't always be defined during testing. if Puppet[:storeconfigs] if Puppet.features.rails? Puppet::Rails.init else raise Puppet::Error, "Rails is missing; cannot store configurations" end end @parsers = {} end # Return the parser for a specific environment. def parser(environment) if ! @parsers[environment] or @parsers[environment].reparse? # This will throw an exception if it does not succeed. We only # want to get rid of the old parser if we successfully create a new # one. begin tmp = create_parser(environment) @parsers[environment].clear if @parsers[environment] @parsers[environment] = tmp rescue => detail # If a parser already exists, than assume that we logged the # exception elsewhere and reuse the parser. If one doesn't # exist, then reraise. - raise detail unless @parsers[environment] + if @parsers[environment] + Puppet.err detail + else + raise detail + end end end @parsers[environment] end private # Create a new parser object and pre-parse the configuration. def create_parser(environment) begin parser = Puppet::Parser::Parser.new(:environment => environment) if code = Puppet.settings.value(:code, environment) and code != "" parser.string = code else file = Puppet.settings.value(:manifest, environment) parser.file = file end parser.parse return parser rescue => detail msg = "Could not parse" if environment and environment != "" msg += " for environment %s" % environment end msg += ": %s" % detail.to_s error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end end end diff --git a/lib/puppet/provider/package/yumhelper.py b/lib/puppet/provider/package/yumhelper.py index 1142401b9..962b96ce4 100644 --- a/lib/puppet/provider/package/yumhelper.py +++ b/lib/puppet/provider/package/yumhelper.py @@ -1,37 +1,39 @@ # Python helper script to query for the packages that have # pending updates. Called by the yum package provider # # (C) 2007 Red Hat Inc. # David Lutterkort import yum import sys OVERRIDE_OPTS = { 'debuglevel': 0, 'errorlevel': 0, 'logfile': '/dev/null' } -def pkg_lists(): - my = yum.YumBase() +def pkg_lists(my): my.doConfigSetup() for k in OVERRIDE_OPTS.keys(): if hasattr(my.conf, k): setattr(my.conf, k, OVERRIDE_OPTS[k]) else: my.conf.setConfigOption(k, OVERRIDE_OPTS[k]) my.doTsSetup() my.doRpmDBSetup() return my.doPackageLists('updates') try: - ypl = pkg_lists() + try: + my = yum.YumBase() + ypl = pkg_lists(my) + for pkg in ypl.updates: + print "_pkg %s %s %s %s %s" % (pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch) + finally: + my.closeRpmDB() except IOError, e: print "_err IOError %d %s" % (e.errno, e) sys.exit(1) - -for pkg in ypl.updates: - print "_pkg %s %s %s %s %s" % (pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch) diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index c41a7883b..1b625cc41 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -1,183 +1,192 @@ # This is our main way of managing processes right now. # # a service is distinct from a process in that services # can only be managed through the interface of an init script # which is why they have a search path for initscripts and such module Puppet newtype(:service) do @doc = "Manage running services. Service support unfortunately varies widely by platform -- some platforms have very little if any concept of a running service, and some have a very codified and powerful concept. Puppet's service support will generally be able to make up for any inherent shortcomings (e.g., if there is no 'status' command, then Puppet will look in the process table for a command matching the service name), but the more information you can provide the better behaviour you will get. Or, you can just use a platform that has very good service support. Note that if a ``service`` receives an event from another resource, the service will get restarted. The actual command to restart the service depends on the platform. You can provide a special command for restarting with the ``restart`` attribute." feature :refreshable, "The provider can restart the service.", :methods => [:restart] feature :enableable, "The provider can enable and disable the service", :methods => [:disable, :enable, :enabled?] + feature :controllable, "The provider uses a control variable." + newproperty(:enable, :required_features => :enableable) do desc "Whether a service should be enabled to start at boot. This property behaves quite differently depending on the platform; wherever possible, it relies on local tools to enable or disable a given service." newvalue(:true, :event => :service_enabled) do provider.enable end newvalue(:false, :event => :service_disabled) do provider.disable end def retrieve return provider.enabled? end end # Handle whether the service should actually be running right now. newproperty(:ensure) do desc "Whether a service should be running." newvalue(:stopped, :event => :service_stopped) do provider.stop end newvalue(:running, :event => :service_started) do provider.start end aliasvalue(:false, :stopped) aliasvalue(:true, :running) def retrieve return provider.status end def sync event = super() if property = @resource.property(:enable) val = property.retrieve property.sync unless property.insync?(val) end return event end end newparam(:binary) do desc "The path to the daemon. This is only used for systems that do not support init scripts. This binary will be used to start the service if no ``start`` parameter is provided." end newparam(:hasstatus) do desc "Declare the the service's init script has a functional status command. Based on testing, it was found that a large number of init scripts on different platforms do not support any kind of status command; thus, you must specify manually whether the service you are running has such a command (or you can specify a specific command using the ``status`` parameter). If you do not specify anything, then the service name will be looked for in the process table." newvalues(:true, :false) end newparam(:name) do desc "The name of the service to run. This name is used to find the service in whatever service subsystem it is in." isnamevar end newparam(:path) do desc "The search path for finding init scripts. Multiple values should be separated by colons or provided as an array." munge do |value| value = [value] unless value.is_a?(Array) paths = value.flatten.collect { |p| p.split(":") }.flatten.find_all do |path| if FileTest.directory?(path) true else if FileTest.exist?(path) and ! FileTest.directory?(path) @resource.debug "Search path %s is not a directory" % [path] else @resource.debug("Search path %s does not exist" % [path]) end false end end paths end defaultto { provider.class.defpath if provider.class.respond_to?(:defpath) } end newparam(:pattern) do desc "The pattern to search for in the process table. This is used for stopping services on platforms that do not support init scripts, and is also used for determining service status on those service whose init scripts do not include a status command. If this is left unspecified and is needed to check the status of a service, then the service name will be used instead. The pattern can be a simple string or any legal Ruby pattern." defaultto { @resource[:binary] || @resource[:name] } end newparam(:restart) do desc "Specify a *restart* command manually. If left unspecified, the service will be stopped and then started." end newparam(:start) do desc "Specify a *start* command manually. Most service subsystems support a ``start`` command, so this will not need to be specified." end newparam(:status) do desc "Specify a *status* command manually. If left unspecified, the status method will be determined automatically, usually by looking for the service in the process table." end newparam(:stop) do desc "Specify a *stop* command manually." end + newparam(:control) do + desc "The control variable used to manage services (originally for HP-UX). + Defaults to the upcased service name plus ``START`` replacing dots with + underscores, for those providers that support the ``controllable`` feature." + defaultto { resource.name.gsub(".","_").upcase + "_START" if resource.provider.controllable? } + end + newparam :hasrestart do desc "Specify that an init script has a ``restart`` option. Otherwise, the init script's ``stop`` and ``start`` methods are used." newvalues(:true, :false) end # Basically just a synonym for restarting. Used to respond # to events. def refresh # Only restart if we're actually running if (@parameters[:ensure] || newattr(:ensure)).retrieve == :running provider.restart else debug "Skipping restart; service is not running" end end end end diff --git a/spec/integration/node.rb b/spec/integration/node.rb index e4a311998..87ff448e4 100755 --- a/spec/integration/node.rb +++ b/spec/integration/node.rb @@ -1,46 +1,51 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-9-23. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/node' describe Puppet::Node, " when using the memory terminus" do before do @name = "me" + @old_terminus = Puppet::Node.indirection.terminus_class Puppet::Node.terminus_class = :memory @node = Puppet::Node.new(@name) end + after do + Puppet::Node.terminus_class = @old_terminus + end + it "should find no nodes by default" do Puppet::Node.find(@name).should be_nil end it "should be able to find nodes that were previously saved" do @node.save Puppet::Node.find(@name).should equal(@node) end it "should replace existing saved nodes when a new node with the same name is saved" do @node.save two = Puppet::Node.new(@name) two.save Puppet::Node.find(@name).should equal(two) end it "should be able to remove previously saved nodes" do @node.save Puppet::Node.destroy(@node) Puppet::Node.find(@name).should be_nil end it "should fail when asked to destroy a node that does not exist" do proc { Puppet::Node.destroy(@node) }.should raise_error(ArgumentError) end after do Puppet.settings.clear end end diff --git a/spec/unit/file_serving/file_base.rb b/spec/unit/file_serving/file_base.rb index e1a61cd65..ded6ae4a8 100755 --- a/spec/unit/file_serving/file_base.rb +++ b/spec/unit/file_serving/file_base.rb @@ -1,120 +1,124 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/file_serving/file_base' describe Puppet::FileServing::FileBase do it "should accept a key in the form of a URI" do Puppet::FileServing::FileBase.new("puppet://host/module/dir/file").key.should == "puppet://host/module/dir/file" end it "should allow specification of whether links should be managed" do Puppet::FileServing::FileBase.new("puppet://host/module/dir/file", :links => :manage).links.should == :manage end - it "should fail if :links is set to anything other than :manage or :follow" do + it "should consider :ignore links equivalent to :manage links" do + Puppet::FileServing::FileBase.new("puppet://host/module/dir/file", :links => :ignore).links.should == :manage + end + + it "should fail if :links is set to anything other than :manage, :follow, or :ignore" do proc { Puppet::FileServing::FileBase.new("puppet://host/module/dir/file", :links => :else) }.should raise_error(ArgumentError) end it "should default to :manage for :links" do Puppet::FileServing::FileBase.new("puppet://host/module/dir/file").links.should == :manage end it "should allow specification of a path" do FileTest.stubs(:exists?).returns(true) Puppet::FileServing::FileBase.new("puppet://host/module/dir/file", :path => "/my/file").path.should == "/my/file" end it "should allow specification of a relative path" do FileTest.stubs(:exists?).returns(true) Puppet::FileServing::FileBase.new("puppet://host/module/dir/file", :relative_path => "my/file").relative_path.should == "my/file" end it "should have a means of determining if the file exists" do Puppet::FileServing::FileBase.new("blah").should respond_to(:exist?) end it "should correctly indicate if the file is present" do File.expects(:lstat).with("/my/file").returns(mock("stat")) Puppet::FileServing::FileBase.new("blah", :path => "/my/file").exist?.should be_true end it "should correctly indicate if the file is asbsent" do File.expects(:lstat).with("/my/file").raises RuntimeError Puppet::FileServing::FileBase.new("blah", :path => "/my/file").exist?.should be_false end describe "when setting the base path" do before do @file = Puppet::FileServing::FileBase.new("puppet://host/module/dir/file") end it "should require that the base path be fully qualified" do FileTest.stubs(:exists?).returns(true) proc { @file.path = "unqualified/file" }.should raise_error(ArgumentError) end end describe "when setting the relative path" do it "should require that the relative path be unqualified" do @file = Puppet::FileServing::FileBase.new("puppet://host/module/dir/file") FileTest.stubs(:exists?).returns(true) proc { @file.relative_path = "/qualified/file" }.should raise_error(ArgumentError) end end describe "when determining the full file path" do before do @file = Puppet::FileServing::FileBase.new("mykey", :path => "/this/file") end it "should return the path if there is no relative path" do @file.full_path.should == "/this/file" end it "should return the path if the relative_path is set to ''" do @file.relative_path = "" @file.full_path.should == "/this/file" end it "should return the path joined with the relative path if there is a relative path and it is not set to '/' or ''" do @file.relative_path = "not/qualified" @file.full_path.should == "/this/file/not/qualified" end it "should should fail if there is no path set" do @file = Puppet::FileServing::FileBase.new("not/qualified") proc { @file.full_path }.should raise_error(ArgumentError) end end describe "when stat'ing files" do before do @file = Puppet::FileServing::FileBase.new("mykey", :path => "/this/file") end it "should stat the file's full path" do @file.stubs(:full_path).returns("/this/file") File.expects(:lstat).with("/this/file").returns stub("stat", :ftype => "file") @file.stat end it "should fail if the file does not exist" do @file.stubs(:full_path).returns("/this/file") File.expects(:lstat).with("/this/file").raises(Errno::ENOENT) proc { @file.stat }.should raise_error(Errno::ENOENT) end it "should use :lstat if :links is set to :manage" do File.expects(:lstat).with("/this/file").returns stub("stat", :ftype => "file") @file.stat end it "should use :stat if :links is set to :follow" do File.expects(:stat).with("/this/file").returns stub("stat", :ftype => "file") @file.links = :follow @file.stat end end end diff --git a/spec/unit/parser/compiler.rb b/spec/unit/parser/compiler.rb index 9980f2c6a..ab430da62 100755 --- a/spec/unit/parser/compiler.rb +++ b/spec/unit/parser/compiler.rb @@ -1,532 +1,532 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Parser::Compiler do before :each do @node = Puppet::Node.new "testnode" @parser = Puppet::Parser::Parser.new :environment => "development" - @scope_resource = stub 'scope_resource', :builtin? => true + @scope_resource = stub 'scope_resource', :builtin? => true, :finish => nil, :ref => 'Class[main]' @scope = stub 'scope', :resource => @scope_resource, :source => mock("source") @compiler = Puppet::Parser::Compiler.new(@node, @parser) end describe Puppet::Parser::Compiler do it "should be able to store references to class scopes" do lambda { @compiler.class_set "myname", "myscope" }.should_not raise_error end it "should be able to retrieve class scopes by name" do @compiler.class_set "myname", "myscope" @compiler.class_scope("myname").should == "myscope" end it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' klass.expects(:classname).returns("myname") @compiler.class_set "myname", "myscope" @compiler.class_scope(klass).should == "myscope" end it "should be able to return a class list containing all set classes" do @compiler.class_set "", "empty" @compiler.class_set "one", "yep" @compiler.class_set "two", "nope" @compiler.classlist.sort.should == %w{one two}.sort end end describe Puppet::Parser::Compiler, " when initializing" do it "should set its node attribute" do @compiler.node.should equal(@node) end it "should set its parser attribute" do @compiler.parser.should equal(@parser) end it "should detect when ast nodes are absent" do @compiler.ast_nodes?.should be_false end it "should detect when ast nodes are present" do @parser.nodes["testing"] = "yay" @compiler.ast_nodes?.should be_true end end describe Puppet::Parser::Compiler, "when managing scopes" do it "should create a top scope" do @compiler.topscope.should be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do @compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope) end it "should correctly set the level of newly created scopes" do @compiler.newscope(@compiler.topscope, :level => 5).level.should == 5 end it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' newscope = @compiler.newscope(scope) @compiler.parent(newscope).should equal(scope) end end describe Puppet::Parser::Compiler, " when compiling" do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compiler.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.topscope.lookupvar("a").should == "b" @compiler.topscope.lookupvar("c").should == "d" end it "should evaluate any existing classes named in the node" do classes = %w{one two three four} main = stub 'main' one = stub 'one', :classname => "one" three = stub 'three', :classname => "three" @node.stubs(:name).returns("whatever") @node.stubs(:classes).returns(classes) @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) @compiler.class.publicize_methods(:evaluate_node_classes) { @compiler.evaluate_node_classes } end it "should enable ast_nodes if the parser has any nodes" do @parser.expects(:nodes).returns(:one => :yay) @compiler.ast_nodes?.should be_true end it "should disable ast_nodes if the parser has no nodes" do @parser.expects(:nodes).returns({}) @compiler.ast_nodes?.should be_false end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = mock 'main_class' main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } @compiler.topscope.expects(:source=).with(main_class) @parser.stubs(:findclass).with("", "").returns(main_class) @compiler.compile end it "should evaluate any node classes" do @node.stubs(:classes).returns(%w{one two three four}) @compiler.expects(:evaluate_classes).with(%w{one two three four}, @compiler.topscope) @compiler.send(:evaluate_node_classes) end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } @compiler.add_collection(colls[0]) @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) @compiler.compile end it "should ignore builtin resources" do resource = stub 'builtin', :ref => "File[testing]", :builtin? => true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => false @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns true } @compiler.compile end it "should not evaluate already-evaluated resources" do resource = stub 'already_evaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => true, :virtual? => false @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => false @compiler.add_resource(@scope, resource) resource2 = stub 'created', :ref => "File[other]", :builtin? => false, :evaluated? => false, :virtual? => false # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns(true); @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.stubs(:evaluated?).returns(true) } @compiler.compile end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new :scope => @scope, :type => "file", :title => "finish" resource.expects(:finish) @compiler.add_resource(@scope, resource) # And one that does not dnf = stub "dnf", :ref => "File[dnf]" @compiler.add_resource(@scope, dnf) @compiler.send(:finish) end it "should add resources that do not conflict with existing resources" do resource = stub "noconflict", :ref => "File[yay]" @compiler.add_resource(@scope, resource) @compiler.catalog.should be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do type = stub 'faketype', :isomorphic? => true, :name => "mytype" Puppet::Type.stubs(:type).with("mytype").returns(type) resource1 = stub "iso1conflict", :ref => "Mytype[yay]", :type => "mytype", :file => "eh", :line => 0 resource2 = stub "iso2conflict", :ref => "Mytype[yay]", :type => "mytype", :file => "eh", :line => 0 @compiler.add_resource(@scope, resource1) lambda { @compiler.add_resource(@scope, resource2) }.should raise_error(ArgumentError) end it "should have a method for looking up resources" do resource = stub 'resource', :ref => "Yay[foo]" @compiler.add_resource(@scope, resource) @compiler.findresource("Yay[foo]").should equal(resource) end it "should be able to look resources up by type and title" do resource = stub 'resource', :ref => "Yay[foo]" @compiler.add_resource(@scope, resource) @compiler.findresource("Yay", "foo").should equal(resource) end it "should not evaluate virtual defined resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end end describe Puppet::Parser::Compiler, " when evaluating collections" do it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do @compiler.delete_collection(coll) end } @compiler.class.publicize_methods(:evaluate_collections) { @compiler.evaluate_collections } end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(nil) @compiler.add_collection(coll) lambda { @compiler.compile }.should_not raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(:something) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error(Puppet::ParseError) end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns([:one, :two]) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error(Puppet::ParseError) end end describe Puppet::Parser::Compiler, "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end it "should tag the catalog with the name of each not-found class" do @compiler.catalog.expects(:tag).with("notfound") @scope.expects(:findclass).with("notfound").returns(nil) @compiler.evaluate_classes(%w{notfound}, @scope) end end describe Puppet::Parser::Compiler, " when evaluating found classes" do before do @class = stub 'class', :classname => "my::class" @scope.stubs(:findclass).with("myclass").returns(@class) @resource = stub 'resource', :ref => "Class[myclass]" end it "should evaluate each class" do @compiler.catalog.stubs(:tag) @class.expects(:evaluate).with(@scope) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should not evaluate the resources created for found classes unless asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:evaluate).returns(@resource) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:evaluate).returns(@resource) @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) @compiler.expects(:class_scope).with(@class).returns("something") @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should return the list of found classes" do @compiler.catalog.stubs(:tag) @compiler.stubs(:add_resource) @scope.stubs(:findclass).with("notfound").returns(nil) Puppet::Parser::Resource.stubs(:new).returns(@resource) @class.stubs :evaluate @compiler.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass} end end describe Puppet::Parser::Compiler, " when evaluating AST nodes with no AST nodes present" do it "should do nothing" do @compiler.expects(:ast_nodes?).returns(false) @compiler.parser.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never @compiler.send(:evaluate_ast_node) end end describe Puppet::Parser::Compiler, " when evaluating AST nodes with AST nodes present" do before do @nodes = mock 'node_hash' @compiler.stubs(:ast_nodes?).returns(true) @compiler.parser.stubs(:nodes).returns(@nodes) # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @nodes.stubs(:[]).with("a").returns(nil) @nodes.stubs(:[]).with("b").returns(nil) @nodes.stubs(:[]).with("c").returns(nil) # It should check this last, of course. @nodes.stubs(:[]).with("default").returns(nil) end it "should fail if the named node cannot be found" do proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :classname => "c", :evaluate_code => nil @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil node_class.expects(:evaluate).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :classname => "default", :evaluate_code => nil @nodes.stubs(:[]).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil node_class.expects(:evaluate).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :classname => "c" @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]" node_class.expects(:evaluate).returns(node_resource) node_resource.expects(:evaluate) @compiler.send(:evaluate_ast_node) end it "should set the node's scope as the top scope" do node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil node_class = stub 'node', :classname => "c", :evaluate => node_resource @nodes.stubs(:[]).with("c").returns(node_class) # The #evaluate method normally does this. scope = stub 'scope', :source => "mysource" @compiler.class_set(node_class.classname, scope) node_resource.stubs(:evaluate) @compiler.compile @compiler.topscope.should equal(scope) end end describe Puppet::Parser::Compiler, "when storing compiled resources" do it "should store the resources" do Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) @compiler.catalog.expects(:vertices).returns(:resources) @compiler.expects(:store_to_active_record).with(@node, :resources) @compiler.send(:store) end it "should store to active_record" do @node.expects(:name).returns("myname") Puppet::Rails::Host.stubs(:transaction).yields Puppet::Rails::Host.expects(:store).with(@node, :resources) @compiler.send(:store_to_active_record, @node, :resources) end end describe Puppet::Parser::Compiler, "when managing resource overrides" do before do @override = stub 'override', :ref => "My[ref]" @resource = stub 'resource', :ref => "My[ref]", :builtin? => true end it "should be able to store overrides" do lambda { @compiler.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) @compiler.add_override(@override) @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override @compiler.add_override(@override) # Then the resource @compiler.add_resource(@scope, @resource) # And compile, so they get resolved @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do @compiler.add_override(@override) lambda { @compiler.compile }.should raise_error(Puppet::ParseError) end end # #620 - Nodes and classes should conflict, else classes don't get evaluated describe Puppet::Parser::Compiler, "when evaluating nodes and classes with the same name (#620)" do before do @node = stub :nodescope? => true @class = stub :nodescope? => false end it "should fail if a node already exists with the same name as the class being evaluated" do @compiler.class_set("one", @node) lambda { @compiler.class_set("one", @class) }.should raise_error(Puppet::ParseError) end it "should fail if a class already exists with the same name as the node being evaluated" do @compiler.class_set("one", @class) lambda { @compiler.class_set("one", @node) }.should raise_error(Puppet::ParseError) end end -end \ No newline at end of file +end diff --git a/spec/unit/parser/interpreter.rb b/spec/unit/parser/interpreter.rb index eb5dd9aaf..f2526c73d 100755 --- a/spec/unit/parser/interpreter.rb +++ b/spec/unit/parser/interpreter.rb @@ -1,149 +1,159 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' -describe Puppet::Parser::Interpreter, " when creating parser instances" do +describe Puppet::Parser::Interpreter do before do @interp = Puppet::Parser::Interpreter.new - @parser = mock('parser') - end - - it "should create a parser with code if there is code defined in the :code setting" do - Puppet.settings.stubs(:value).with(:code, :myenv).returns("mycode") - @parser.expects(:string=).with("mycode") - @parser.expects(:parse) - Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).returns(@parser) - @interp.send(:create_parser, :myenv).object_id.should equal(@parser.object_id) - end - - it "should create a parser with the main manifest when the code setting is an empty string" do - Puppet.settings.stubs(:value).with(:code, :myenv).returns("") - Puppet.settings.stubs(:value).with(:manifest, :myenv).returns("/my/file") - @parser.expects(:parse) - @parser.expects(:file=).with("/my/file") - Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).returns(@parser) - @interp.send(:create_parser, :myenv).should equal(@parser) - end - - it "should return nothing when new parsers fail" do - Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).raises(ArgumentError) - proc { @interp.send(:create_parser, :myenv) }.should raise_error(Puppet::Error) - end - - it "should create parsers with environment-appropriate manifests" do - # Set our per-environment values. We can't just stub :value, because - # it's called by too much of the rest of the code. - text = "[env1]\nmanifest = /t/env1.pp\n[env2]\nmanifest = /t/env2.pp" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - Puppet.settings.stubs(:read_file).with(file).returns(text) - Puppet.settings.parse(file) - - parser1 = mock 'parser1' - Puppet::Parser::Parser.expects(:new).with(:environment => :env1).returns(parser1) - parser1.expects(:file=).with("/t/env1.pp") - parser1.expects(:parse) - @interp.send(:create_parser, :env1) - - parser2 = mock 'parser2' - Puppet::Parser::Parser.expects(:new).with(:environment => :env2).returns(parser2) - parser2.expects(:file=).with("/t/env2.pp") - parser2.expects(:parse) - @interp.send(:create_parser, :env2) - end -end - -describe Puppet::Parser::Interpreter, " when managing parser instances" do - before do - @interp = Puppet::Parser::Interpreter.new - @parser = mock('parser') - end - - it "should use the same parser when the parser does not need reparsing" do - @interp.expects(:create_parser).with(:myenv).returns(@parser) - @interp.send(:parser, :myenv).should equal(@parser) - - @parser.expects(:reparse?).returns(false) - @interp.send(:parser, :myenv).should equal(@parser) - end - - it "should create a new parser when reparse is true" do - oldparser = mock('oldparser') - newparser = mock('newparser') - oldparser.expects(:reparse?).returns(true) - oldparser.expects(:clear) - - @interp.expects(:create_parser).with(:myenv).returns(oldparser) - @interp.send(:parser, :myenv).should equal(oldparser) - @interp.expects(:create_parser).with(:myenv).returns(newparser) - @interp.send(:parser, :myenv).should equal(newparser) - end - - it "should fail intelligently if a parser cannot be created and one does not already exist" do - @interp.expects(:create_parser).with(:myenv).raises(ArgumentError) - proc { @interp.send(:parser, :myenv) }.should raise_error(ArgumentError) - end - - it "should keep the old parser if a new parser cannot be created" do - # Get the first parser in the hash. - @interp.expects(:create_parser).with(:myenv).returns(@parser) - @interp.send(:parser, :myenv).should equal(@parser) - - # Have it indicate something has changed - @parser.expects(:reparse?).returns(true) - - # But fail to create a new parser - @interp.expects(:create_parser).with(:myenv).raises(ArgumentError) - - # And make sure we still get the old valid parser - @interp.send(:parser, :myenv).should equal(@parser) - end - - it "should use different parsers for different environments" do - # get one for the first env - @interp.expects(:create_parser).with(:first_env).returns(@parser) - @interp.send(:parser, :first_env).should equal(@parser) - - other_parser = mock('otherparser') - @interp.expects(:create_parser).with(:second_env).returns(other_parser) - @interp.send(:parser, :second_env).should equal(other_parser) - end -end - -describe Puppet::Parser::Interpreter, " when compiling catalog" do - before do - @interp = Puppet::Parser::Interpreter.new - @node = stub 'node', :environment => :myenv - @compiler = mock 'compile' @parser = mock 'parser' end - it "should create a compile with the node and parser" do - @compiler.expects(:compile).returns(:config) - @interp.expects(:parser).with(:myenv).returns(@parser) - Puppet::Parser::Compiler.expects(:new).with(@node, @parser).returns(@compiler) - @interp.compile(@node) - end - - it "should fail intelligently when no parser can be found" do - @node.stubs(:name).returns("whatever") - @interp.expects(:parser).with(:myenv).returns(nil) - proc { @interp.compile(@node) }.should raise_error(Puppet::ParseError) - end -end - -describe Puppet::Parser::Interpreter, " when returning catalog version" do - before do - @interp = Puppet::Parser::Interpreter.new - end - - it "should ask the appropriate parser for the catalog version" do - node = mock 'node' - node.expects(:environment).returns(:myenv) - parser = mock 'parser' - parser.expects(:version).returns(:myvers) - @interp.expects(:parser).with(:myenv).returns(parser) - @interp.configuration_version(node).should equal(:myvers) + describe "when creating parser instances" do + it "should create a parser with code if there is code defined in the :code setting" do + Puppet.settings.stubs(:value).with(:code, :myenv).returns("mycode") + @parser.expects(:string=).with("mycode") + @parser.expects(:parse) + Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).returns(@parser) + @interp.send(:create_parser, :myenv).object_id.should equal(@parser.object_id) + end + + it "should create a parser with the main manifest when the code setting is an empty string" do + Puppet.settings.stubs(:value).with(:code, :myenv).returns("") + Puppet.settings.stubs(:value).with(:manifest, :myenv).returns("/my/file") + @parser.expects(:parse) + @parser.expects(:file=).with("/my/file") + Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).returns(@parser) + @interp.send(:create_parser, :myenv).should equal(@parser) + end + + it "should return nothing when new parsers fail" do + Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).raises(ArgumentError) + proc { @interp.send(:create_parser, :myenv) }.should raise_error(Puppet::Error) + end + + it "should create parsers with environment-appropriate manifests" do + # Set our per-environment values. We can't just stub :value, because + # it's called by too much of the rest of the code. + text = "[env1]\nmanifest = /t/env1.pp\n[env2]\nmanifest = /t/env2.pp" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + Puppet.settings.stubs(:read_file).with(file).returns(text) + Puppet.settings.parse(file) + + parser1 = mock 'parser1' + Puppet::Parser::Parser.expects(:new).with(:environment => :env1).returns(parser1) + parser1.expects(:file=).with("/t/env1.pp") + parser1.expects(:parse) + @interp.send(:create_parser, :env1) + + parser2 = mock 'parser2' + Puppet::Parser::Parser.expects(:new).with(:environment => :env2).returns(parser2) + parser2.expects(:file=).with("/t/env2.pp") + parser2.expects(:parse) + @interp.send(:create_parser, :env2) + end + end + + describe "when managing parser instances" do + it "should use the same parser when the parser does not need reparsing" do + @interp.expects(:create_parser).with(:myenv).returns(@parser) + @interp.send(:parser, :myenv).should equal(@parser) + + @parser.expects(:reparse?).returns(false) + @interp.send(:parser, :myenv).should equal(@parser) + end + + it "should fail intelligently if a parser cannot be created and one does not already exist" do + @interp.expects(:create_parser).with(:myenv).raises(ArgumentError) + proc { @interp.send(:parser, :myenv) }.should raise_error(ArgumentError) + end + + it "should use different parsers for different environments" do + # get one for the first env + @interp.expects(:create_parser).with(:first_env).returns(@parser) + @interp.send(:parser, :first_env).should equal(@parser) + + other_parser = mock('otherparser') + @interp.expects(:create_parser).with(:second_env).returns(other_parser) + @interp.send(:parser, :second_env).should equal(other_parser) + end + + describe "when files need reparsing" do + it "should create a new parser" do + oldparser = mock('oldparser') + newparser = mock('newparser') + oldparser.expects(:reparse?).returns(true) + oldparser.expects(:clear) + + @interp.expects(:create_parser).with(:myenv).returns(oldparser) + @interp.send(:parser, :myenv).should equal(oldparser) + @interp.expects(:create_parser).with(:myenv).returns(newparser) + @interp.send(:parser, :myenv).should equal(newparser) + end + + it "should keep the old parser if a new parser cannot be created" do + # Get the first parser in the hash. + @interp.expects(:create_parser).with(:myenv).returns(@parser) + @interp.send(:parser, :myenv).should equal(@parser) + + # Have it indicate something has changed + @parser.expects(:reparse?).returns(true) + + # But fail to create a new parser + @interp.expects(:create_parser).with(:myenv).raises(ArgumentError) + + # And make sure we still get the old valid parser + @interp.send(:parser, :myenv).should equal(@parser) + end + + it "should log syntax errors when using the old parser" do + # Get the first parser in the hash. + @interp.stubs(:create_parser).with(:myenv).returns(@parser) + @interp.send(:parser, :myenv) + + # Have it indicate something has changed + @parser.stubs(:reparse?).returns(true) + + # But fail to create a new parser + @interp.stubs(:create_parser).with(:myenv).raises(ArgumentError) + + Puppet.expects(:err) + + # And make sure we still get the old valid parser + @interp.send(:parser, :myenv) + end + end + end + + describe "when compiling a catalog" do + before do + @node = stub 'node', :environment => :myenv + @compiler = mock 'compile' + end + + it "should create a compile with the node and parser" do + @compiler.expects(:compile).returns(:config) + @interp.expects(:parser).with(:myenv).returns(@parser) + Puppet::Parser::Compiler.expects(:new).with(@node, @parser).returns(@compiler) + @interp.compile(@node) + end + + it "should fail intelligently when no parser can be found" do + @node.stubs(:name).returns("whatever") + @interp.expects(:parser).with(:myenv).returns(nil) + proc { @interp.compile(@node) }.should raise_error(Puppet::ParseError) + end + end + + describe "when returning catalog version" do + it "should ask the appropriate parser for the catalog version" do + node = mock 'node' + node.expects(:environment).returns(:myenv) + parser = mock 'parser' + parser.expects(:version).returns(:myvers) + @interp.expects(:parser).with(:myenv).returns(parser) + @interp.configuration_version(node).should equal(:myvers) + end end end diff --git a/spec/unit/ral/types/exec.rb b/spec/unit/ral/type/exec.rb similarity index 100% rename from spec/unit/ral/types/exec.rb rename to spec/unit/ral/type/exec.rb diff --git a/spec/unit/ral/types/file.rb b/spec/unit/ral/type/file.rb similarity index 100% rename from spec/unit/ral/types/file.rb rename to spec/unit/ral/type/file.rb diff --git a/spec/unit/ral/types/interface.rb b/spec/unit/ral/type/interface.rb similarity index 100% rename from spec/unit/ral/types/interface.rb rename to spec/unit/ral/type/interface.rb diff --git a/spec/unit/ral/types/mount.rb b/spec/unit/ral/type/mount.rb similarity index 100% rename from spec/unit/ral/types/mount.rb rename to spec/unit/ral/type/mount.rb diff --git a/spec/unit/ral/types/nagios.rb b/spec/unit/ral/type/nagios.rb similarity index 100% rename from spec/unit/ral/types/nagios.rb rename to spec/unit/ral/type/nagios.rb diff --git a/spec/unit/ral/types/package.rb b/spec/unit/ral/type/package.rb similarity index 100% rename from spec/unit/ral/types/package.rb rename to spec/unit/ral/type/package.rb diff --git a/spec/unit/ral/types/schedule.rb b/spec/unit/ral/type/schedule.rb similarity index 100% rename from spec/unit/ral/types/schedule.rb rename to spec/unit/ral/type/schedule.rb diff --git a/spec/unit/ral/types/service.rb b/spec/unit/ral/type/service.rb similarity index 92% rename from spec/unit/ral/types/service.rb rename to spec/unit/ral/type/service.rb index 981d38a15..0f00992fa 100755 --- a/spec/unit/ral/types/service.rb +++ b/spec/unit/ral/type/service.rb @@ -1,249 +1,256 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/type/service' describe Puppet::Type::Service do it "should have an :enableable feature that requires the :enable, :disable, and :enabled? methods" do Puppet::Type::Service.provider_feature(:enableable).methods.should == [:disable, :enable, :enabled?] end it "should have a :refreshable feature that requires the :restart method" do Puppet::Type::Service.provider_feature(:refreshable).methods.should == [:restart] end end describe Puppet::Type::Service, "when validating attributes" do - [:name, :binary, :hasstatus, :path, :pattern, :start, :restart, :stop, :status, :hasrestart].each do |param| + [:name, :binary, :hasstatus, :path, :pattern, :start, :restart, :stop, :status, :hasrestart, :control].each do |param| it "should have a #{param} parameter" do Puppet::Type::Service.attrtype(param).should == :param end end [:ensure, :enable].each do |param| it "should have an #{param} property" do Puppet::Type::Service.attrtype(param).should == :property end end end describe Puppet::Type::Service, "when validating attribute values" do before do - @provider = stub 'provider', :class => Puppet::Type::Service.defaultprovider, :clear => nil + @provider = stub 'provider', :class => Puppet::Type::Service.defaultprovider, :clear => nil, :controllable? => false Puppet::Type::Service.defaultprovider.stubs(:new).returns(@provider) end it "should support :running as a value to :ensure" do Puppet::Type::Service.create(:name => "yay", :ensure => :running) end it "should support :stopped as a value to :ensure" do Puppet::Type::Service.create(:name => "yay", :ensure => :stopped) end it "should alias the value :true to :running in :ensure" do svc = Puppet::Type::Service.create(:name => "yay", :ensure => true) svc.should(:ensure).should == :running end it "should alias the value :false to :stopped in :ensure" do svc = Puppet::Type::Service.create(:name => "yay", :ensure => false) svc.should(:ensure).should == :stopped end it "should support :true as a value to :enable" do Puppet::Type::Service.create(:name => "yay", :enable => :true) end it "should support :false as a value to :enable" do Puppet::Type::Service.create(:name => "yay", :enable => :false) end it "should support :true as a value to :hasstatus" do Puppet::Type::Service.create(:name => "yay", :hasstatus => :true) end it "should support :false as a value to :hasstatus" do Puppet::Type::Service.create(:name => "yay", :hasstatus => :false) end it "should support :true as a value to :hasrestart" do Puppet::Type::Service.create(:name => "yay", :hasrestart => :true) end it "should support :false as a value to :hasrestart" do Puppet::Type::Service.create(:name => "yay", :hasrestart => :false) end it "should allow setting the :enable parameter if the provider has the :enableable feature" do Puppet::Type::Service.defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type::Service.defaultprovider.expects(:supports_parameter?).with(Puppet::Type::Service.attrclass(:enable)).returns(true) svc = Puppet::Type::Service.create(:name => "yay", :enable => true) svc.should(:enable).should == :true end it "should not allow setting the :enable parameter if the provider is missing the :enableable feature" do Puppet::Type::Service.defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type::Service.defaultprovider.expects(:supports_parameter?).with(Puppet::Type::Service.attrclass(:enable)).returns(false) svc = Puppet::Type::Service.create(:name => "yay", :enable => true) svc.should(:enable).should be_nil end it "should discard paths that do not exist" do FileTest.stubs(:exist?).returns(false) FileTest.stubs(:directory?).returns(false) svc = Puppet::Type::Service.create(:name => "yay", :path => "/one/two") svc[:path].should be_empty end it "should discard paths that are not directories" do FileTest.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(false) svc = Puppet::Type::Service.create(:name => "yay", :path => "/one/two") svc[:path].should be_empty end it "should split paths on ':'" do FileTest.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type::Service.create(:name => "yay", :path => "/one/two:/three/four") svc[:path].should == %w{/one/two /three/four} end it "should accept arrays of paths joined by ':'" do FileTest.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type::Service.create(:name => "yay", :path => ["/one:/two", "/three:/four"]) svc[:path].should == %w{/one /two /three /four} end after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when setting default attribute values" do it "should default to the provider's default path if one is available" do FileTest.stubs(:directory?).returns(true) FileTest.stubs(:exist?).returns(true) Puppet::Type::Service.defaultprovider.stubs(:respond_to?).returns(true) Puppet::Type::Service.defaultprovider.stubs(:defpath).returns("testing") svc = Puppet::Type::Service.create(:name => "other") svc[:path].should == ["testing"] end - it "should default to the binary for the pattern if one is provided" do + it "should default 'pattern' to the binary if one is provided" do svc = Puppet::Type::Service.create(:name => "other", :binary => "/some/binary") svc[:pattern].should == "/some/binary" end - it "should default to the name for the pattern if no pattern is provided" do + it "should default 'pattern' to the name if no pattern is provided" do svc = Puppet::Type::Service.create(:name => "other") svc[:pattern].should == "other" end + it "should default 'control' to the upcased service name with periods replaced by underscores if the provider supports the 'controllable' feature" do + provider = stub 'provider', :controllable? => true, :class => Puppet::Type::Service.defaultprovider, :clear => nil + Puppet::Type::Service.defaultprovider.stubs(:new).returns(provider) + svc = Puppet::Type::Service.create(:name => "nfs.client") + svc[:control].should == "NFS_CLIENT_START" + end + after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when retrieving the host's current state" do before do @service = Puppet::Type::Service.create(:name => "yay") end it "should use the provider's status to determine whether the service is running" do @service.provider.expects(:status).returns(:yepper) @service[:ensure] = :running @service.property(:ensure).retrieve.should == :yepper end it "should ask the provider whether it is enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service.provider.expects(:enabled?).returns(:yepper) @service[:enable] = true @service.property(:enable).retrieve.should == :yepper end after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when changing the host" do before do @service = Puppet::Type::Service.create(:name => "yay") end it "should start the service if it is supposed to be running" do @service[:ensure] = :running @service.provider.expects(:start) @service.property(:ensure).sync end it "should stop the service if it is supposed to be stopped" do @service[:ensure] = :stopped @service.provider.expects(:stop) @service.property(:ensure).sync end it "should enable the service if it is supposed to be enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = true @service.provider.expects(:enable) @service.property(:enable).sync end it "should disable the service if it is supposed to be disabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service.provider.expects(:disable) @service.property(:enable).sync end it "should sync the service's enable state when changing the state of :ensure if :enable is being managed" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service[:ensure] = :stopped @service.property(:enable).expects(:retrieve).returns("whatever") @service.property(:enable).expects(:insync?).returns(false) @service.property(:enable).expects(:sync) @service.provider.stubs(:stop) @service.property(:ensure).sync end after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when refreshing the service" do before do @service = Puppet::Type::Service.create(:name => "yay") end it "should restart the service if it is running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should restart the service if it is running, even if it is supposed to stopped" do @service[:ensure] = :stopped @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should not restart the service if it is not running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:stopped) @service.refresh end it "should add :ensure as a property if it is not being managed" do @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end after { Puppet::Type::Service.clear } end diff --git a/spec/unit/ral/types/user.rb b/spec/unit/ral/type/user.rb similarity index 100% rename from spec/unit/ral/types/user.rb rename to spec/unit/ral/type/user.rb diff --git a/test/ral/types/basic.rb b/test/ral/type/basic.rb similarity index 100% rename from test/ral/types/basic.rb rename to test/ral/type/basic.rb diff --git a/test/ral/types/cron.rb b/test/ral/type/cron.rb similarity index 100% rename from test/ral/types/cron.rb rename to test/ral/type/cron.rb diff --git a/test/ral/types/exec.rb b/test/ral/type/exec.rb similarity index 100% rename from test/ral/types/exec.rb rename to test/ral/type/exec.rb diff --git a/test/ral/types/file.rb b/test/ral/type/file.rb similarity index 100% rename from test/ral/types/file.rb rename to test/ral/type/file.rb diff --git a/test/ral/types/file/target.rb b/test/ral/type/file/target.rb similarity index 100% rename from test/ral/types/file/target.rb rename to test/ral/type/file/target.rb diff --git a/test/ral/types/filebucket.rb b/test/ral/type/filebucket.rb similarity index 100% rename from test/ral/types/filebucket.rb rename to test/ral/type/filebucket.rb diff --git a/test/ral/types/fileignoresource.rb b/test/ral/type/fileignoresource.rb similarity index 100% rename from test/ral/types/fileignoresource.rb rename to test/ral/type/fileignoresource.rb diff --git a/test/ral/types/filesources.rb b/test/ral/type/filesources.rb similarity index 100% rename from test/ral/types/filesources.rb rename to test/ral/type/filesources.rb diff --git a/test/ral/types/group.rb b/test/ral/type/group.rb similarity index 100% rename from test/ral/types/group.rb rename to test/ral/type/group.rb diff --git a/test/ral/types/host.rb b/test/ral/type/host.rb similarity index 100% rename from test/ral/types/host.rb rename to test/ral/type/host.rb diff --git a/test/ral/types/mailalias.rb b/test/ral/type/mailalias.rb similarity index 100% rename from test/ral/types/mailalias.rb rename to test/ral/type/mailalias.rb diff --git a/test/ral/types/parameter.rb b/test/ral/type/parameter.rb similarity index 100% rename from test/ral/types/parameter.rb rename to test/ral/type/parameter.rb diff --git a/test/ral/types/port.rb b/test/ral/type/port.rb similarity index 100% rename from test/ral/types/port.rb rename to test/ral/type/port.rb diff --git a/test/ral/types/property.rb b/test/ral/type/property.rb similarity index 100% rename from test/ral/types/property.rb rename to test/ral/type/property.rb diff --git a/test/ral/types/resources.rb b/test/ral/type/resources.rb similarity index 100% rename from test/ral/types/resources.rb rename to test/ral/type/resources.rb diff --git a/test/ral/types/service.rb b/test/ral/type/service.rb similarity index 100% rename from test/ral/types/service.rb rename to test/ral/type/service.rb diff --git a/test/ral/types/sshkey.rb b/test/ral/type/sshkey.rb similarity index 100% rename from test/ral/types/sshkey.rb rename to test/ral/type/sshkey.rb diff --git a/test/ral/types/tidy.rb b/test/ral/type/tidy.rb similarity index 100% rename from test/ral/types/tidy.rb rename to test/ral/type/tidy.rb diff --git a/test/ral/types/user.rb b/test/ral/type/user.rb similarity index 100% rename from test/ral/types/user.rb rename to test/ral/type/user.rb diff --git a/test/ral/types/yumrepo.rb b/test/ral/type/yumrepo.rb similarity index 100% rename from test/ral/types/yumrepo.rb rename to test/ral/type/yumrepo.rb diff --git a/test/ral/types/zone.rb b/test/ral/type/zone.rb similarity index 100% rename from test/ral/types/zone.rb rename to test/ral/type/zone.rb