diff --git a/documentation/Rakefile b/documentation/Rakefile deleted file mode 100644 index 6bfe4d239..000000000 --- a/documentation/Rakefile +++ /dev/null @@ -1,58 +0,0 @@ -# vim: syntax=ruby - -require 'bluecloth' - -htmlfiles = [] - -CLEAN = [] - -FileList['**/*.page'].each do |src| - name = src.sub(".page", ".html") - htmlfiles << name - CLEAN << name - file name => [src, "Rakefile"] do - File.open(name, "w") do |f| - text = File.read(src).sub(/\A^---[^-]+^---$/, '') - f.puts BlueCloth.new( text ).to_html - end - end -end - -task :clean do - CLEAN.each do |file| - if FileTest.directory?(file) - sh %{rm -rf #{file}} - elsif FileTest.exists?(file) - File.unlink(file) - end - end -end - -task :html => htmlfiles - -task :default => :html - -docs = %w{configref typedocs reports functions} - -docs.each do |doc| - task doc do - docs = %x{puppetdoc --mode #{doc}} - - header = "reference/%s.header" % doc - if FileTest.exists?(header) - headertext = File.read(header) - else - headertext = "" - end - - file = "reference/%s.page" % doc - - puts "Creating %s" % file - File.open(file, "w") do |f| - f.puts headertext - f.puts docs - end - end -end - -task :docs => docs diff --git a/documentation/faq.page b/documentation/faq.page deleted file mode 100644 index 83a83dca6..000000000 --- a/documentation/faq.page +++ /dev/null @@ -1,258 +0,0 @@ ---- -inMenu: true -title: FAQ -orderInfo: 2 ---- -What is Puppet? ----------------- -Puppet is an open-source next-generation server automation tool. It is -composed of a *declarative* language for expressing system configuration, a -*client* and *server* for distributing it, and a *library* for realizing the -configuration. - -The primary design goal of Puppet is that it have an expressive enough -language backed by a powerful enough library that you can write your own -server automation applications in just a few lines of code. With Puppet, you -can express the configuration of your entire network in one program capable of -realizing the configuration. The fact that Puppet has open source combined -with how easily it can be extended means that you can add whatever -functionality you think is missing and then contribute it back to the main -project if you desire. - -You can learn more about Puppet by reading its [Documentation][]. - -What license is Puppet released under? --------------------------------------- -Puppet is open source and is released under the [GNU Public License][]. - -Why does Puppet exist? ----------------------- -Luke Kanies, who founded Reductive Labs, has been doing server automation for -years, and Puppet is the result of his frustration with existing tools. After -significant effort spent trying to enhance cfengine, plus a stint at a -commercial server automation vendor, Luke concluded that the only way to get a -great automation tool was to develop one. - -Puppet is actually the result of years of design and prototyping (called Blink -during its prototype phases), but only in 2005 was a commercial company -(Reductive Labs) built to be fully dedicated to its creation. If Puppet is -not the most powerful and most flexible server automation platform available, -then its goals are not being met. - -Why does Puppet have its own language? --------------------------------------- -This actually is a frequently asked question, and people most often ask why I -did not choose to use something like XML or YAML as the configuration -format; otherwise people ask why I didn't just choose to just use Ruby as the -input language. - -The input format for Puppet is not XML or YAML because these are data formats -developed to be easy for computers to handle. They do not do conditionals -(although, yes, they support data structures that could be considered -conditionals), but mostly, they're just horrible human interfaces. While some -people are comfortable reading and writing them, there's a reason why we use -web browsers instead of just reading the HTML directly. Also, using XML or -YAML would limit the ability to make sure the interface is declarative -- one -process might treat an XML configuration differently from another. - -As to just using Ruby as the input format, that unnecessarily ties Puppet to -Ruby, which is undesirable, and Ruby provides a bit too much functionality. -For more detail, see the [thread][] on the puppet-dev list. - - -How does Puppet compare to cfengine? ------------------------------------- -Puppet could be said to be the next-generation cfengine. The overall design -is heavily influenced by cfengine, but the language is more powerful than -cfengine's and the library is more flexible. In addition, Puppet's client and -server use standard protocols like XMLRPC and are easy to enhance with new -functionality, so they are well-positioned to become the platform for the -network applications of the future, while cfengine's client and server rely -entirely on cfengine-specific protocols and are quite difficult to enhance. - -See [How Puppet Compares to Cfengine][] for more information. - -How does Puppet compare to available commercial products? ---------------------------------------------------------- -The primary commercial vendors are BladeLogic and OpsWare. While they both -have useful product lines, Puppet surpasses them by reframing the entire -server automation problem -- while the commercial vendors are writing GUI -applications for you, Reductive Labs is providing a development platform with -all the features of a great language, like library development, code sharing, -and the ability to version control your configurations. - -Trying to express a complex network configuration entirely through a GUI is an -exercise in frustration that no one should suffer, but expressing the -abstraction necessary to share those GUI configurations goes beyond -frustrating. - -Of course, another great difference between Puppet and the commercial products -is that Puppet is open sourced under the [GNU Public License][]. You can -[download][] the product, try it out, peruse the source, and make whatever -modifications you want. You have to have more than 100 servers just to get a -demo from the commercial vendors, but Puppet is available for testing by any -company that needs to reduce its server administration costs. - -Who would find Puppet useful? ------------------------------ -Any organization that would like to reduce the cost of maintaining its -computers could benefit from using Puppet. However, because the return on -investment is linked to multiple factors, like current administrative -overhead, diversity among existing computers, and cost of downtime, it can be -difficult for organizations to determine whether they should invest in any -configuration management tools, much less Puppet. Reductive Labs can always -be contacted directly at info at reductivelabs.com to help answer this question. - -Generally, however, an organization should be using server automation if any -of the following are true: - -* It has high server administration costs -* It pays a high price for downtime, either because of contracts or - opportunity cost -* It has many servers that are essentially either identical or nearly - identical -* Flexibility and agility in server configuration are essential - -Can Puppet manage workstations? -------------------------------- -Yes, Puppet can manage any type of machine. We have found that most -organizations are more concerned with server management than workstation -management, and frankly, the term 'server' is slightly more aesthetically -appealing than 'computer', but Puppet would be ideal for organizations with a -large number of workstations. - -Does Puppet run on Windows? ---------------------------- -The short answer is 'not yet'. It will eventually, but Reductive Labs does -not yet have the development bandwidth to make this work. - -What size organizations should use Puppet? ------------------------------------------- -There is no minimum or maximum organization size that can benefit from Puppet, -but there are sizes that are more likely to benefit. Organizations with fewer -than 10-20 servers are unlikely to consider maintaining those servers to be a -real problem, and thus they can avoid investment in tools even though those -tools could likely provide savings. - -There is no real upper limit to who could benefit from using Puppet. -Obviously as the server count increases the investment must increase somewhat, -but with Puppet that increase is not linear. - -My servers are all unique; can Puppet still help? -------------------------------------------------- -All servers are at least somewhat unique -- with different host names and -different IP addresses -- but very few servers are entirely unique, since -nearly every one runs a relatively standard operating system. Servers are -also often very similar to other servers within a single organization -- all -Solaris servers might have similar security settings, or all web servers might -have roughly equivalent configurations -- even if they're very different from -servers in other organizations. Finally, servers are often needlessly unique, -in that they have been built and managed manually with no attempt at retaining -appropriate consistency. - -Puppet can help both on the side of consistency and uniqueness. Puppet -can be used to express the consistency that should exist, even if -that consistency spans arbritrary sets of servers based on any type of data -like operating system, data center, or physical location. Puppet can also be -used to handle uniqueness, either by allowing special provision of what makes -a given host unique or through specifying exceptions to otherwise standard -classes. - -Who is Reductive Labs? ----------------------- -Reductive Labs is a small, private company focused on reframing the server -automation problem. Our primary focus is Puppet, but Reductive Labs also -provides automation consulting, training, and custom development. For more -information email info at reductivelabs.com. - -The [Projects][] page lists our active projects. - -How Do I Install Puppet? ------------------------- - -The [Installation Guide][] documents the fastest way to start using Puppet. - -What is a Manifest? -------------------- -Because the word *script* implies a procedural one-step-after-another program, -the word does not apply well to Puppet programs. Thus, we use the word -*manifest* to describe declarative Puppet programs. Speaking of applying, -Puppet *applies* a manifest to a server or a network, rather than *executing* -it. - -How Do I Write Manifests? -------------------------- -The best way is to download Puppet and just start writing. There are multiple -sets of examples, including the [examples][] used in [unit testing][], -and the [reference][]will obviously be useful. - -How Do I Run Manifests? ------------------------ -Once you have Puppet installed according the the [Installation Guide][], -just run the ``puppet`` executable against your example: - - puppet -v example.pp - -How do I contribute? --------------------- -First join the [Mailing List][] -- there is currently only a development list, -but as the community grows a user list will be created. You can also join the -IRC channel ``#puppet`` on irc.freenode.net, where Puppet's developers will be -hanging out most days (and nights). - -The most valuable contribution you can make, though, is to use Puppet and -submit your feedback, either directly on IRC or through the mailing list, or -via the [bug database][]. We're always looking for great ideas to incorporate -into Puppet. - -When is the Next Release? -------------------------- -There are regular feature and release updates on the [Mailing List][], -and you can always find the latest release in the [download][] directory. - -I keep getting "certificates were not trusted". What's wrong? --------------------------------------------------------------- -Historically this has usually been a problem with the client machine having -such a different date setting that the certificate is not yet valid. - -You can figure the problem out by manually verifying the certificate with -openssl: - - sudo openssl verify -CAfile /etc/puppet/ssl/certs/ca.pem /etc/puppet/ssl/certs/myhostname.domain.com.pem - -[Mailing List]: http://mail.madstop.com/mailman/listinfo/puppet-dev -[Projects]: /projects/ -[Documentation]: documentation/index.html -[Installation Guide]: documentation/installation.html -[How Puppet Compares to Cfengine]: documentation/notcfengine.html -[GNU Public License]: http://www.gnu.org/copyleft/gpl.html -[examples]: /trac/puppet/browser/trunk/examples/code/ -[unit testing]: http://www.pragmaticprogrammer.com/starter_kit/ut/ -[bug database]: /trac/puppet/report -[reference]: documentation/typedocs.html -[download]: /downloads -[thread]: http://mail.madstop.com/pipermail/puppet-dev/2006-April/000393.html - -I'm getting IPv6 errors; what's wrong? --------------------------------------- -This can apparently happen if Ruby is not compiled with IPv6 support; see the -[mail thread](http://mail.madstop.com/pipermail/puppet-dev/2006-August/001410.html) -for more details. The only known solution is to make sure you're running a -version of Ruby compiled with IPv6 support. - -I'm getting ``tlsv1 alert unknown ca`` errors; what's wrong? ------------------------------------------------------------- -This problem is caused by ``puppetmasterd`` not being able to read its -ca certificate. This problem might occur up to 0.18.4 but has been -fixed in 0.19.0. You can probably fix it for versions before 0.19.0 by -chgrping /etc/puppet/ssl to the puppet group, but ``puppetd`` might -chgrp it back. Having ``puppetmasterd`` start as the root group should -fix the problem permanently until you can upgrade. - -How do all of these variables, like ``operatingsystem``, get set? ------------------------------------------------------------------ -The variables are all set by [Facter](/projects/facter). You can get -a full listing of the available variables and their values by running -``facter`` by itself in a shell. - -*$Id$* diff --git a/documentation/index.page b/documentation/index.page deleted file mode 100644 index 940951258..000000000 --- a/documentation/index.page +++ /dev/null @@ -1,71 +0,0 @@ ---- -inMenu: false -directoryName: Puppet ---- - -Puppet lets you centrally manage every important aspect of your system using a -cross-platform specification language that manages all the separate elements -normally aggregated in different files, like users, cron jobs, and hosts, -along with obviously discrete elements like packages, services, and files. - -Puppet's simple declarative specification language provides powerful classing -abilities for drawing out the similarities between hosts while allowing them -to be as specific as necessary, and it handles dependency and prerequisite -relationships between objects clearly and explicitly. Puppet is written -entirely in [Ruby](http://www.ruby-lang.org/). - -Many general questions about Puppet and Reductive are answered in the -[FAQ](faq.html), such as "How to get started quickly", "How to contribute", -and "What is Puppet's License? (GPL)") - -You can also often get good support on ``#puppet`` on irc.freenode.net; -Puppet's primary author, Luke Kanies, is usually online there. - -## Relevant Links - -* [Documentation](/trac/puppet/wiki/DocumentationStart) - - Available documentation on puppet. Including an Introduction, and Language - & Type Library References. - -* [Puppet Recipe Manager](http://prmweb.hezmatt.org/) - - A CPAN-like site for sharing and downloading Puppet recipes. - -* [Cookbook](/trac/puppet/tags/puppet%2Crecipe) - - All of the cookbook recipes on the Puppet wiki. - -* [Downloads](/downloads/) - - Puppet source code, Packages (RPMs, debs, etc.), and Ruby GEM packages. - -* [Source Code](/svn/puppet/) - - Puppet Subversion Repository - -* [Bug Tracker](https://reductivelabs.com/trac/puppet) - - Bug tickets, feature enhancements, and source browsing - -* [Configuration Management Blog](http://madstop.com) - - A blog Luke Kanies is maintaining about the development process of Puppet. - -## Mailing Lists - -* [Puppet User](http://mail.madstop.com/mailman/listinfo/puppet-users) - - The Puppet users mailing list, for any and all Puppet discussion. - -* [Puppet Developer](http://mail.madstop.com/mailman/listinfo/puppet-dev) - - The Puppet-dev mailing list, for all public discussions related to the - development of puppet. All emails generated by the bug tracker are also - sent to this list. - -* [Puppet Commits](http://mail.madstop.com/mailman/listinfo/puppet-commit) - - A read-only list that gets a copy of all subversion commits. - -*$Id$* diff --git a/documentation/reference/configref.header b/documentation/reference/configref.header deleted file mode 100644 index 854eb1d99..000000000 --- a/documentation/reference/configref.header +++ /dev/null @@ -1,94 +0,0 @@ ---- -inMenu: true -title: Configuration Reference -orderInfo: 40 ---- -# Puppet Configuration Reference - -## Specifying Configuration Parameters - -Every Puppet executable (with the exception of ``puppetdoc``) accepts all of -the arguments below, but not all of the arguments make sense for every executable. -Each argument has a section listed with it in parentheses; often, that section -will map to an executable (e.g., ``puppetd``), in which case it probably only -makes sense for that one executable. If ``puppet`` is listed as the section, -it is most likely an option that is valid for everyone. - -This will not always be the case. I have tried to be as thorough as possible -in the descriptions of the arguments, so it should be obvious whether an -argument is appropriate or not. - -These arguments can be supplied to the executables either as command-line -arugments or in the configuration file for the appropriate executable. For -instance, the command-line invocation below would set the configuration directory -to /private/puppet - - $ puppetd --confdir=/private/puppet - -Note that boolean options are turned on and off with a slightly different syntax -on the command line: - - $ puppetd --storeconfigs - - $ puppetd --no-storeconfigs - -The invocations above will enable and disable, respectively, the storage of -the client configuration. - -As mentioned above, the configuration parameters can also be stored in a -configuration file located in the configuration directory (`/etc/puppet` -by default). The file is named for the executable it is intended for, for -example `/etc/puppetd.conf` is the configuration file for `puppetd`. - -The file, which follows INI-style formatting, should contain a bracketed -heading named for the executable, followed by pairs of parameters with their -values. Here is an example of a very simple `puppetd.conf` file: - - [puppetd] - confdir = /private/puppet - storeconfigs = true - -Note that boolean parameters must be explicitly specified as `true` or -`false` as seen above. - -If you're starting out with a fresh configuration, you may wish to let -the executable generate a template configuration file for you by invoking -the executable in question with the `--genconfig` command. The executable -will print a template configuration to standard output, which can be -redirected to a file like so: - - $ puppetd --genconfig > /etc/puppet/puppetd.conf - -Note that this invocation will "clobber" (throw away) the contents of any -pre-existing `puppetd.conf` file, so make a backup of your present config -if it contains valuable information. - -Like the `--genconfig` argument, the executables also accept a `--genmanifest` -argument, which will generate a manifest that can be used to manage all of -Puppet's directories and files and prints it to standard output. This can -likewise be redirected to a file: - - $ puppetd --genmanifest > /etc/puppet/manifests/site.pp - -Puppet can also create user and group accounts for itself (one `puppet` group -and one `puppet` user) if it is invoked as `root` with the `--mkusers` argument: - - $ puppetd --mkusers - -## Signals - -The `puppetd` and `puppetmasterd` executables catch some signals for special -handling. Both daemons catch (`SIGHUP`), which forces the server to restart -tself. Predictably, interrupt and terminate (`SIGINT` and `SIGHUP`) will shut -down the server, whether it be an instance of `puppetd` or `puppetmasterd`. - -Sending the `SIGUSR1` signal to an instance of `puppetd` will cause it to -immediately begin a new configuration transaction with the server. This -signal has no effect on `puppetmasterd`. - - -## Configuration Parameter Reference - -Below is a list of all documented parameters. Any default values are in ``block type`` at the end of the description. - - diff --git a/documentation/reference/configref.page b/documentation/reference/configref.page deleted file mode 100644 index d3081fe0c..000000000 --- a/documentation/reference/configref.page +++ /dev/null @@ -1,576 +0,0 @@ ---- -inMenu: true -title: Configuration Reference -orderInfo: 40 ---- -# Puppet Configuration Reference - -## Specifying Configuration Parameters - -Every Puppet executable (with the exception of ``puppetdoc``) accepts all of -the arguments below, but not all of the arguments make sense for every executable. -Each argument has a section listed with it in parentheses; often, that section -will map to an executable (e.g., ``puppetd``), in which case it probably only -makes sense for that one executable. If ``puppet`` is listed as the section, -it is most likely an option that is valid for everyone. - -This will not always be the case. I have tried to be as thorough as possible -in the descriptions of the arguments, so it should be obvious whether an -argument is appropriate or not. - -These arguments can be supplied to the executables either as command-line -arugments or in the configuration file for the appropriate executable. For -instance, the command-line invocation below would set the configuration directory -to /private/puppet - - $ puppetd --confdir=/private/puppet - -Note that boolean options are turned on and off with a slightly different syntax -on the command line: - - $ puppetd --storeconfigs - - $ puppetd --no-storeconfigs - -The invocations above will enable and disable, respectively, the storage of -the client configuration. - -As mentioned above, the configuration parameters can also be stored in a -configuration file located in the configuration directory (`/etc/puppet` -by default). The file is named for the executable it is intended for, for -example `/etc/puppetd.conf` is the configuration file for `puppetd`. - -The file, which follows INI-style formatting, should contain a bracketed -heading named for the executable, followed by pairs of parameters with their -values. Here is an example of a very simple `puppetd.conf` file: - - [puppetd] - confdir = /private/puppet - storeconfigs = true - -Note that boolean parameters must be explicitly specified as `true` or -`false` as seen above. - -If you're starting out with a fresh configuration, you may wish to let -the executable generate a template configuration file for you by invoking -the executable in question with the `--genconfig` command. The executable -will print a template configuration to standard output, which can be -redirected to a file like so: - - $ puppetd --genconfig > /etc/puppet/puppetd.conf - -Note that this invocation will "clobber" (throw away) the contents of any -pre-existing `puppetd.conf` file, so make a backup of your present config -if it contains valuable information. - -Like the `--genconfig` argument, the executables also accept a `--genmanifest` -argument, which will generate a manifest that can be used to manage all of -Puppet's directories and files and prints it to standard output. This can -likewise be redirected to a file: - - $ puppetd --genmanifest > /etc/puppet/manifests/site.pp - -Puppet can also create user and group accounts for itself (one `puppet` group -and one `puppet` user) if it is invoked as `root` with the `--mkusers` argument: - - $ puppetd --mkusers - -## Signals - -The `puppetd` and `puppetmasterd` executables catch some signals for special -handling. Both daemons catch (`SIGHUP`), which forces the server to restart -tself. Predictably, interrupt and terminate (`SIGINT` and `SIGHUP`) will shut -down the server, whether it be an instance of `puppetd` or `puppetmasterd`. - -Sending the `SIGUSR1` signal to an instance of `puppetd` will cause it to -immediately begin a new configuration transaction with the server. This -signal has no effect on `puppetmasterd`. - - -## Configuration Parameter Reference - -Below is a list of all documented parameters. Any default values are in ``block type`` at the end of the description. - - -#### authconfig (puppet) - -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``. ``/etc/puppet/namespaceauth.conf`` - -#### autoflush (puppet) - -Whether log files should always flush to disk. - -#### autosign (ca) - -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. ``/etc/puppet/autosign.conf`` - -#### bucketdir (puppetmasterd) - -Where FileBucket files are stored. ``/var/puppet/bucket`` - -#### ca_days (ca) - -How long a certificate should be valid. This parameter is deprecated, use ca_ttl instead - -#### ca_md (ca) - -The type of hash used in certificates. ``md5`` - -#### ca_ttl (ca) - -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) ``5y`` - -#### cacert (ca) - -The CA certificate. ``/etc/puppet/ssl/ca/ca_crt.pem`` - -#### cacrl (ca) - -The certificate revocation list (CRL) for the CA. Set this to 'none' if you do not want to use a CRL. ``/etc/puppet/ssl/ca/ca_crl.pem`` - -#### cadir (ca) - -The root directory for the certificate authority. ``/etc/puppet/ssl/ca`` - -#### cakey (ca) - -The CA private key. ``/etc/puppet/ssl/ca/ca_key.pem`` - -#### capass (ca) - -Where the CA stores the password for the private key ``/etc/puppet/ssl/ca/private/ca.pass`` - -#### caprivatedir (ca) - -Where the CA stores private certificate information. ``/etc/puppet/ssl/ca/private`` - -#### capub (ca) - -The CA public key. ``/etc/puppet/ssl/ca/ca_pub.pem`` - -#### casesensitive (puppet) - -Whether matching in case statements and selectors should be case-sensitive. Case insensitivity is handled by downcasing all values before comparison. - -#### cert_inventory (ca) - -A Complete listing of all certificates ``/etc/puppet/ssl/ca/inventory.txt`` - -#### certdir (certificates) - -The certificate directory. ``/etc/puppet/ssl/certs`` - -#### classfile (puppetd) - -The file in which puppetd stores a list of the classes associated with the retrieved configuratiion. Can be loaded in the separate ``puppet`` executable using the ``--loadclasses`` option. ``/etc/puppet/classes.txt`` - -#### clientbucketdir (filebucket) - -Where FileBucket files are stored locally. ``/var/puppet/clientbucket`` - -#### color (puppet) - -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. ``ansi`` - -#### confdir (puppet) - -The main Puppet configuration directory. ``/etc/puppet`` - -#### config (puppetdoc) - -The configuration file for puppetdoc. ``/etc/puppet/puppetdoc.conf`` - -#### configprint (puppet) - -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. - -#### configtimeout (puppetd) - -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. ``30`` - -#### csrdir (ca) - -Where the CA stores certificate requests ``/etc/puppet/ssl/ca/requests`` - -#### dbadapter (puppetmaster) - -The type of database to use. ``sqlite3`` - -#### dblocation (puppetmaster) - -The database cache for client configurations. Used for querying within the language. ``/var/puppet/state/clientconfigs.sqlite3`` - -#### dbmigrate (puppetmaster) - -Whether to automatically migrate the database. - -#### dbname (puppetmaster) - -The name of the database to use. ``puppet`` - -#### dbpassword (puppetmaster) - -The database password for Client caching. Only used when networked databases are used. ``puppet`` - -#### dbserver (puppetmaster) - -The database server for Client caching. Only used when networked databases are used. ``localhost`` - -#### dbuser (puppetmaster) - -The database user for Client caching. Only used when networked databases are used. ``puppet`` - -#### downcasefacts (puppetd) - -Whether facts should be made all lowercase when sent to the server. - -#### evaltrace (transaction) - -Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done. - -#### external_nodes (puppet) - -An external command that can produce node information. The first line of output must be either the parent node or blank, and if there is a second line of output it should be a list of whitespace-separated classes to include on that node. This command makes it straightforward to store your node mapping information in other data sources like databases. For unknown nodes, the commands should exit with an exit code of 1. ``none`` - -#### factdest (puppet) - -Where Puppet should store facts that it pulls down from the central server. ``/var/puppet/facts`` - -#### factpath (puppet) - -Where Puppet should look for facts. Multiple directories should be colon-separated, like normal PATH variables. ``/var/puppet/facts`` - -#### factsignore (puppet) - -What files to ignore when pulling down facts. ``.svn CVS`` - -#### factsource (puppet) - -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. ``puppet://puppet/facts`` - -#### factsync (puppet) - -Whether facts should be synced with the central server. - -#### fileserverconfig (fileserver) - -Where the fileserver configuration is stored. ``/etc/puppet/fileserver.conf`` - -#### filetimeout (puppet) - -The minimum time to wait between checking for updates in configuration files. ``15`` - -#### genconfig (puppet) - -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 (puppet) - -Whether to just print a manifest to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI. - -#### graph (puppet) - -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 (puppet) - -Where to store dot-outputted graphs. ``/var/puppet/state/graphs`` - -#### group (puppetmasterd) - -The group puppetmasterd should run as. ``puppet`` - -#### hostcert (certificates) - -Where individual hosts store and look for their certificates. ``/etc/puppet/ssl/certs/culain.madstop.com.pem`` - -#### hostprivkey (certificates) - -Where individual hosts store and look for their private key. ``/etc/puppet/ssl/private_keys/culain.madstop.com.pem`` - -#### hostpubkey (certificates) - -Where individual hosts store and look for their public key. ``/etc/puppet/ssl/public_keys/culain.madstop.com.pem`` - -#### httplog (puppetd) - -Where the puppetd web server logs. ``/var/puppet/log/http.log`` - -#### ignoreschedules (puppetd) - -Boolean; whether puppetd should ignore schedules. This is useful for initial puppetd runs. - -#### keylength (ca) - -The bit length of keys. ``1024`` - -#### ldapattrs (ldap) - -The LDAP attributes to use to define Puppet classes. Values should be comma-separated. ``puppetclass`` - -#### ldapbase (ldap) - -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. - -#### ldapnodes (ldap) - -Whether to search for node configurations in LDAP. - -#### ldapparentattr (ldap) - -The attribute to use to define the parent node. ``parentnode`` - -#### ldappassword (ldap) - -The password to use to connect to LDAP. - -#### ldapport (ldap) - -The LDAP port. Only used if ``ldapnodes`` is enabled. ``389`` - -#### ldapserver (ldap) - -The LDAP server. Only used if ``ldapnodes`` is enabled. ``ldap`` - -#### ldapssl (ldap) - -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. - -#### ldapstring (ldap) - -The search string used to find an LDAP node. ``(&(objectclass=puppetClient)(cn=%s))`` - -#### ldaptls (ldap) - -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. - -#### ldapuser (ldap) - -The user to use to connect to LDAP. Must be specified as a full DN. - -#### lexical (puppet) - -Whether to use lexical scoping (vs. dynamic). - -#### listen (puppetd) - -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. - -#### localcacert (certificates) - -Where each client stores the CA certificate. ``/etc/puppet/ssl/certs/ca.pem`` - -#### localconfig (puppetd) - -Where puppetd caches the local configuration. An extension indicating the cache format is added automatically. ``/etc/puppet/localconfig`` - -#### logdir (puppet) - -The Puppet log directory. ``/var/puppet/log`` - -#### manifest (puppetmasterd) - -The entry-point manifest for puppetmasterd. ``/etc/puppet/manifests/site.pp`` - -#### manifestdir (puppetmasterd) - -Where puppetmasterd looks for its manifests. ``/etc/puppet/manifests`` - -#### masterhttplog (puppetmasterd) - -Where the puppetmasterd web server logs. ``/var/puppet/log/masterhttp.log`` - -#### masterlog (puppetmasterd) - -Where puppetmasterd logs. This is generally not used, since syslog is the default log destination. ``/var/puppet/log/puppetmaster.log`` - -#### masterport (puppetmasterd) - -Which port puppetmasterd listens on. ``8140`` - -#### mkusers (puppet) - -Whether to create the necessary user and group that puppetd will run as. - -#### node_name (puppetmasterd) - -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) ``cert`` - -#### noop (puppetd) - -Whether puppetd should be run in noop mode. - -#### paramcheck (ast) - -Whether to validate parameters during parsing. ``true`` - -#### parseonly (puppetmasterd) - -Just check the syntax of the manifests. - -#### passfile (certificates) - -Where puppetd stores the password for its private key. Generally unused. ``/etc/puppet/ssl/private/password`` - -#### path (puppet) - -The shell search path. Defaults to whatever is inherited from the parent process. ``none`` - -#### plugindest (puppet) - -Where Puppet should store plugins that it pulls down from the central server. ``/var/puppet/plugins`` - -#### pluginpath (puppet) - -Where Puppet should look for plugins. Multiple directories should be colon-separated, like normal PATH variables. ``/var/puppet/plugins`` - -#### pluginsignore (puppet) - -What files to ignore when pulling down plugins. ``.svn CVS`` - -#### pluginsource (puppet) - -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. ``puppet://puppet/plugins`` - -#### pluginsync (puppet) - -Whether plugins should be synced with the central server. - -#### privatedir (certificates) - -Where the client stores private certificate information. ``/etc/puppet/ssl/private`` - -#### privatekeydir (certificates) - -The private key directory. ``/etc/puppet/ssl/private_keys`` - -#### publickeydir (certificates) - -The public key directory. ``/etc/puppet/ssl/public_keys`` - -#### puppetdlockfile (puppetd) - -A lock file to temporarily stop puppetd from doing anything. ``/var/puppet/state/puppetdlock`` - -#### puppetdlog (puppetd) - -The log file for puppetd. This is generally not used. ``/var/puppet/log/puppetd.log`` - -#### puppetport (puppetd) - -Which port puppetd listens on. ``8139`` - -#### railslog (puppetmaster) - -Where Rails-specific logs are sent ``/var/puppet/log/rails.log`` - -#### report (puppetd) - -Whether to send reports after every transaction. - -#### reportdir (reporting) - -The directory in which to store reports received from the client. Each client gets a separate subdirectory. ``/var/puppet/reports`` - -#### reports (reporting) - -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). ``store`` - -#### reportserver (puppetd) - -The server to which to send transaction reports. ``puppet`` - -#### req_bits (ca) - -The bit length of the certificates. ``2048`` - -#### rrddir (metrics) - -The directory where RRD database files are stored. Directories for each reporting host will be created under this directory. ``/var/puppet/rrd`` - -#### rrdgraph (metrics) - -Whether RRD information should be graphed. - -#### rrdinterval (metrics) - -How often RRD should expect data. This should match how often the hosts report back to the server. ``1800`` - -#### rundir (puppet) - -Where Puppet PID files are kept. ``/var/run/puppet`` - -#### runinterval (puppetd) - -How often puppetd applies the client configuration; in seconds ``1800`` - -#### serial (ca) - -Where the serial number for certificates is stored. ``/etc/puppet/ssl/ca/serial`` - -#### server (puppetd) - -The server to which server puppetd should connect ``puppet`` - -#### setpidfile (puppet) - -Whether to store a PID file for the daemon. ``true`` - -#### signeddir (ca) - -Where the CA stores signed certificates. ``/etc/puppet/ssl/ca/signed`` - -#### ssldir (puppet) - -Where SSL certificates are kept. ``/etc/puppet/ssl`` - -#### statedir (puppet) - -The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts). ``/var/puppet/state`` - -#### statefile (puppet) - -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. ``/var/puppet/state/state.yaml`` - -#### storeconfigs (puppetmaster) - -Whether to store each client's configuration. This requires ActiveRecord from Ruby on Rails. - -#### syslogfacility (puppet) - -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. ``daemon`` - -#### tags (transaction) - -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. - -#### templatedir (puppet) - -Where Puppet looks for template files. ``/var/puppet/templates`` - -#### trace (puppet) - -Whether to print stack traces on some errors - -#### typecheck (ast) - -Whether to validate types during parsing. ``true`` - -#### usecacheonfailure (puppetd) - -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. ``true`` - -#### user (puppetmasterd) - -The user puppetmasterd should run as. ``puppet`` - -#### vardir (puppet) - -Where Puppet stores dynamic and growing data. ``/var/puppet`` - - - ----------------- - - -*This page autogenerated on Fri Jan 26 16:40:43 CST 2007* diff --git a/documentation/reference/functions.header b/documentation/reference/functions.header deleted file mode 100644 index 9c58cd36a..000000000 --- a/documentation/reference/functions.header +++ /dev/null @@ -1,15 +0,0 @@ ---- -inMenu: true -title: Function Reference -orderInfo: 40 ---- - -There are two types of functions in Puppet: Statements and rvalues. -Statements stand on their own and do not return arguments; they are used for -performing stand-alone work like importing. Rvalues return values and can -only be used in a statement requiring a value, such as an assignment or a case -statement. - -Here are the functions available in Puppet: - - diff --git a/documentation/reference/functions.page b/documentation/reference/functions.page deleted file mode 100644 index ad2c03f5a..000000000 --- a/documentation/reference/functions.page +++ /dev/null @@ -1,51 +0,0 @@ ---- -inMenu: true -title: Function Reference -orderInfo: 40 ---- - -There are two types of functions in Puppet: Statements and rvalues. -Statements stand on their own and do not return arguments; they are used for -performing stand-alone work like importing. Rvalues return values and can -only be used in a statement requiring a value, such as an assignment or a case -statement. - -Here are the functions available in Puppet: - - -* **alert** (*statement*): Log a message on the server at level alert. - -* **crit** (*statement*): Log a message on the server at level crit. - -* **debug** (*statement*): Log a message on the server at level debug. - -* **defined** (*rvalue*): Determine whether a given type is defined, either as a native type or a defined type, or whether a resource has been specified. If you are checking with a resource is defined, use the normal resource reference syntax, e.g., ``File['/etc/passwd']``. - -* **emerg** (*statement*): Log a message on the server at level emerg. - -* **err** (*statement*): Log a message on the server at level err. - -* **fail** (*statement*): Fail with a parse error. - -* **include** (*statement*): Evaluate one or more classes. - -* **info** (*statement*): Log a message on the server at level info. - -* **notice** (*statement*): Log a message on the server at level notice. - -* **realize** (*statement*): Make a virtual object real. This is useful when you want to know the name of the virtual object and don't want to bother with a full collection. It is slightly faster than a collection, and, of course, is a bit shorter. You must pass the object using a reference; e.g.: ``realize User[luke]``. - -* **tag** (*statement*): Add the specified tags to the containing class or definition. All contained objects will then acquire that tag, also. - -* **tagged** (*rvalue*): A boolean function that tells you whether the current container is tagged with the specified tags. The tags are ANDed, so thta all of the specified tags must be included for the function to return true. - -* **template** (*rvalue*): Evaluate a template and return its value. See [the templating docs](/trac/puppet/wiki/PuppetTemplating) for more information. Note that if multiple templates are specified, their output is all concatenated and returned as the output of the function. - -* **warning** (*statement*): Log a message on the server at level warning. - - - ----------------- - - -*This page autogenerated on Fri Jan 26 16:40:49 CST 2007* diff --git a/documentation/reference/index.page b/documentation/reference/index.page deleted file mode 100644 index 809b854d3..000000000 --- a/documentation/reference/index.page +++ /dev/null @@ -1,18 +0,0 @@ ---- -inMenu: false -directoryName: Reference -title: Reference -orderInfo: 4 -subtreeLevel: 6 ---- - -Reference -========= -* [Configuration Parameter Reference](configref.html) -* [Function Reference](functions.html) -* [Reports Reference](reports.html) -* [Type Reference](typedocs.html) - - - -*$Id: index.page 1843 2006-11-09 20:47:30Z luke $* diff --git a/documentation/reference/reports.header b/documentation/reference/reports.header deleted file mode 100644 index f919a1aca..000000000 --- a/documentation/reference/reports.header +++ /dev/null @@ -1,19 +0,0 @@ ---- -inMenu: true -title: Reports Reference -orderInfo: 40 ---- - -Puppet clients can report back to the server after each -transaction. This transaction report is sent as a YAML dump and includes every -log message that was generated during the transaction along with as many metrics -as Puppet knows how to collect. - -Currently, clients default to not sending in reports; you can enable reporting -by setting the ``report`` parameter to true. - -To use a report, set the ``reports`` parameter on the server; multiple -reports must be comma-separated. - -Puppet provides multiple report handlers that will process client reports: - diff --git a/documentation/reference/reports.page b/documentation/reference/reports.page deleted file mode 100644 index 0319589ae..000000000 --- a/documentation/reference/reports.page +++ /dev/null @@ -1,82 +0,0 @@ ---- -inMenu: true -title: Reports Reference -orderInfo: 40 ---- - -Puppet clients can report back to the server after each -transaction. This transaction report is sent as a YAML dump and includes every -log message that was generated during the transaction along with as many metrics -as Puppet knows how to collect. - -Currently, clients default to not sending in reports; you can enable reporting -by setting the ``report`` parameter to true. - -To use a report, set the ``reports`` parameter on the server; multiple -reports must be comma-separated. - -Puppet provides multiple report handlers that will process client reports: - -## log - -Send all received logs to the local log destinations. - -## rrdgraph - -Graph all available data about hosts using the RRD library. You -must have the RRD binary library installed to use this report, which -you can get from [Tobias Oetiker's site](http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/pub/contrib/). - -This report will create, manage, and graph RRD database files for each -of the metrics generated during transactions, and it will create a -few simple html files to display the reporting host's graphs. At this -point, it will not create a common index file to display links to -all hosts. - -All RRD files and graphs get created in the ``rrddir`` directory. If -you want to serve these publicly, you should be able to just alias that -directory in a web server. - -## store - -Store the yaml report on disk. Each host sends its report as a YAML dump -and this just stores the file on disk, in the ``reportdir`` directory. - -These files collect quickly -- one every half hour -- so it is a good idea -to perform some maintenance on them if you use this report (it's the only -default report). - -## tagmail - -This report sends specific log messages to specific email addresses -based on the tags in the log messages. See the -[tag documentation](/trac/puppet/wiki/UsingTags) for more information -on tags. - -To use this report, you must create a ``tagmail.conf`` (in the location -specified by ``tagmap``). This is a simple file that maps tags to -email addresses: Any log messages in the report that match the specified -tags will be sent to the specified email addresses. - -Tags must be comma-separated, and they can be negated so that messages -only match when they do not have that tag. The tags are separated from -the email addresses by a colon, and the email addresses should also -be comma-separated. - -Lastly, there is an ``all`` tag that will always match all log messages. - -Here is an example tagmail.conf: - - all: me@domain.com - webserver, !mailserver: httpadmins@domain.com - -This will send all messages to ``me@domain.com``, and all messages from -webservers that are not also from mailservers to ``httpadmins@domain.com``. - - - - ----------------- - - -*This page autogenerated on Fri Jan 26 16:40:48 CST 2007* diff --git a/documentation/reference/typedocs.header b/documentation/reference/typedocs.header deleted file mode 100644 index 69fabd07c..000000000 --- a/documentation/reference/typedocs.header +++ /dev/null @@ -1,7 +0,0 @@ ---- -inMenu: true -title: Type Reference -orderInfo: 40 ---- -# Type Reference - diff --git a/documentation/reference/typedocs.page b/documentation/reference/typedocs.page deleted file mode 100644 index 3fff7ff70..000000000 --- a/documentation/reference/typedocs.page +++ /dev/null @@ -1,1775 +0,0 @@ ---- -inMenu: true -title: Type Reference -orderInfo: 40 ---- -# Type Reference - -## Table of Contents -1. Meta-Parameters -1. Cron -1. Exec -1. File -1. Filebucket -1. Group -1. Host -1. Mount -1. Notify -1. Package -1. Resources -1. Schedule -1. Service -1. Sshkey -1. Tidy -1. User -1. Yumrepo -1. Zone - -

Meta-Parameters

- -Metaparameters are parameters that work with any element; they are part of the -Puppet framework itself rather than being part of the implementation of any -given instance. Thus, any defined metaparameter can be used with any instance -in your manifest, including defined components. - - -#### alias -Creates an alias for the object. Puppet uses this internally when you -provide a symbolic name: - - file { sshdconfig: - path => $operatingsystem ? { - solaris => "/usr/local/etc/ssh/sshd_config", - default => "/etc/ssh/sshd_config" - }, - source => "..." - } - - service { sshd: - subscribe => file[sshdconfig] - } - -When you use this feature, the parser sets ``sshdconfig`` as the name, -and the library sets that as an alias for the file so the dependency -lookup for ``sshd`` works. You can use this parameter yourself, -but note that only the library can use these aliases; for instance, -the following code will not work: - - file { "/etc/ssh/sshd_config": - owner => root, - group => root, - alias => sshdconfig - } - - file { sshdconfig: - mode => 644 - } - -There's no way here for the Puppet parser to know that these two stanzas -should be affecting the same file. - -See the [language tutorial][] for more information. - -[language tutorial]: languagetutorial.html - - -#### before -This parameter is the opposite of **require** -- it guarantees -that the specified object is applied later than the specifying -object: - - file { "/var/nagios/configuration": - source => "...", - recurse => true, - before => exec["nagios-rebuid"] - } - - exec { "nagios-rebuild": - command => "/usr/bin/make", - cwd => "/var/nagios/configuration" - } - -This will make sure all of the files are up to date before the -make command is run. - -#### check -States which should have their values retrieved -but which should not actually be modified. This is currently used -internally, but will eventually be used for querying, so that you -could specify that you wanted to check the install state of all -packages, and then query the Puppet client daemon to get reports -on all packages. - -#### loglevel -Sets the level that information will be logged. -The log levels have the biggest impact when logs are sent to -syslog (which is currently the default). Valid values are ``debug``, ``info``, ``notice``, ``warning``, ``err``, ``alert``, ``emerg``, ``crit``, ``verbose``. - -#### noop -Boolean flag indicating whether work should actually -be done. Valid values are ``true``, ``false``. - -#### notify -This parameter is the opposite of **subscribe** -- it sends events -to the specified object: - - file { "/etc/sshd_config": - source => "....", - notify => service[sshd] - } - - service { sshd: - ensure => running - } - -This will restart the sshd service if the sshd config file changes. - -#### require -One or more objects that this object depends on. -This is used purely for guaranteeing that changes to required objects -happen before the dependent object. For instance: - - # Create the destination directory before you copy things down - file { "/usr/local/scripts": - ensure => directory - } - - file { "/usr/local/scripts/myscript": - source => "puppet://server/module/myscript", - mode => 755, - require => file["/usr/local/scripts"] - } - -Note that Puppet will autorequire everything that it can, and -there are hooks in place so that it's easy for elements to add new -ways to autorequire objects, so if you think Puppet could be -smarter here, let us know. - -In fact, the above code was redundant -- Puppet will autorequire -any parent directories that are being managed; it will -automatically realize that the parent directory should be created -before the script is pulled down. - -Currently, exec elements will autorequire their CWD (if it is -specified) plus any fully qualified paths that appear in the -command. For instance, if you had an ``exec`` command that ran -the ``myscript`` mentioned above, the above code that pulls the -file down would be automatically listed as a requirement to the -``exec`` code, so that you would always be running againts the -most recent version. - -#### schedule -On what schedule the object should be managed. You must create a -schedule object, and then reference the name of that object to use -that for your schedule: - - schedule { daily: - period => daily, - range => "2-4" - } - - exec { "/usr/bin/apt-get update": - schedule => daily - } - -The creation of the schedule object does not need to appear in the -configuration before objects that use it. - -#### subscribe -One or more objects that this object depends on. Changes in the -subscribed to objects result in the dependent objects being -refreshed (e.g., a service will get restarted). For instance: - - class nagios { - file { "/etc/nagios/nagios.conf": - source => "puppet://server/module/nagios.conf", - alias => nagconf # just to make things easier for me - } - service { nagios: - running => true, - subscribe => file[nagconf] - } - } - -#### tag -Add the specified tags to the associated element. While all elements -are automatically tagged with as much information as possible -(e.g., each class and component containing the element), it can -be useful to add your own tags to a given element. - -Tags are currently useful for things like applying a subset of a -host's configuration: - - puppetd --test --tag mytag - -This way, when you're testing a configuration you can run just the -portion you're testing. - - -## Types - -- *namevar* is the parameter used to uniquely identify a type instance. - This is the parameter that gets assigned when a string is provided before - the colon in a type declaration. In general, only developers will need to - worry about which parameter is the ``namevar``. - - In the following code: - - file { "/etc/passwd": - owner => root, - group => root, - mode => 644 - } - - "/etc/passwd" is considered the name of the file object (used for things like - dependency handling), and because ``path`` is the namevar for ``file``, that - string is assigned to the ``path`` parameter. - -- *parameters* determine the specific configuration of the instance. They either - directly modify the system (internally, these are called states) or they affect - how the instance behaves (e.g., adding a search path for ``exec`` instances - or determining recursion on ``file`` instances). - -When required binaries are specified for providers, fully qualifed paths -indicate that the binary must exist at that specific path and unqualified -binaries indicate that Puppet will search for the binary using the shell -path. - - - - ----------------- - - -

cron

-Installs and manages cron jobs. All fields except the command -and the user are optional, although specifying no periodic -fields would result in the command being executed every -minute. While the name of the cron job is not part of the actual -job, it is used by Puppet to store and retrieve it. - -If you specify a cron job that matches an existing job in every way -except name, then the jobs will be considered equivalent and the -new name will be permanently associated with that job. Once this -association is made and synced to disk, you can then manage the job -normally (e.g., change the schedule of the job). - -Example: - - cron { logrotate: - command => "/usr/sbin/logrotate", - user => root, - hour => 2, - minute => 0 - } - - - -### Cron Parameters -#### command -The command to execute in the cron job. The environment -provided to the command varies by local system rules, and it is -best to always provide a fully qualified command. The user's -profile is not sourced when the command is run, so if the -user's environment is desired it should be sourced manually. - -All cron parameters support ``absent`` as a value; this will -remove any existing values for that field. - -#### ensure -The basic state that the object should be in. Valid values are ``absent``, ``present``. - -#### environment -Any environment settings associated with this cron job. They -will be stored between the header and the job in the crontab. There -can be no guarantees that other, earlier settings will not also -affect a given cron job. - -Also, Puppet cannot automatically determine whether an existing, -unmanaged environment setting is associated with a given cron -job. If you already have cron jobs with environment settings, -then Puppet will keep those settings in the same place in the file, -but will not associate them with a specific job. - -Settings should be specified exactly as they should appear in -the crontab, e.g., 'PATH=/bin:/usr/bin:/usr/sbin'. Multiple -settings should be specified as an array. - -#### hour -The hour at which to run the cron job. Optional; -if specified, must be between 0 and 23, inclusive. - -#### minute -The minute at which to run the cron job. -Optional; if specified, must be between 0 and 59, inclusive. - -#### month -The month of the year. Optional; if specified -must be between 1 and 12 or the month name (e.g., December). - -#### monthday -The day of the month on which to run the -command. Optional; if specified, must be between 1 and 31. - -#### name (*namevar*) -The symbolic name of the cron job. This name -is used for human reference only and is generated automatically -for cron jobs found on the system. This generally won't -matter, as Puppet will do its best to match existing cron jobs -against specified jobs (and Puppet adds a comment to cron jobs it -adds), but it is at least possible that converting from -unmanaged jobs to managed jobs might require manual -intervention. - -The names can only have alphanumeric characters plus the '-' -character. - -#### special -Special schedules only supported on FreeBSD. - -#### user -The user to run the command as. This user must -be allowed to run cron jobs, which is not currently checked by -Puppet. - -The user defaults to whomever Puppet is running as. - -#### weekday -The weekday on which to run the command. -Optional; if specified, must be between 0 and 6, inclusive, with -0 being Sunday, or must be the name of the day (e.g., Tuesday). - - - - ----------------- - - -

exec

-Executes external commands. It is critical that all commands -executed using this mechanism can be run multiple times without -harm, i.e., they are *idempotent*. One useful way to create idempotent -commands is to use the *creates* parameter. - -It is worth noting that ``exec`` is special, in that it is not -currently considered an error to have multiple ``exec`` instances -with the same name. This was done purely because it had to be this -way in order to get certain functionality, but it complicates things. -In particular, you will not be able to use ``exec`` instances that -share their commands with other instances as a dependency, since -Puppet has no way of knowing which instance you mean. - -For example: - - # defined in the production class - exec { "make": - cwd => "/prod/build/dir", - path => "/usr/bin:/usr/sbin:/bin" - } - - . etc. . - - # defined in the test class - exec { "make": - cwd => "/test/build/dir", - path => "/usr/bin:/usr/sbin:/bin" - } - -Any other type would throw an error, complaining that you had -the same instance being managed in multiple places, but these are -obviously different images, so ``exec`` had to be treated specially. - -It is recommended to avoid duplicate names whenever possible. - -There is a strong tendency to use ``exec`` to do whatever work Puppet -can't already do; while this is obviously acceptable (and unavoidable) -in the short term, it is highly recommended to migrate work from ``exec`` -to real Puppet element types as quickly as possible. If you find that -you are doing a lot of work with ``exec``, please at least notify -us at Reductive Labs what you are doing, and hopefully we can work with -you to get a native element type for the work you are doing. In general, -it is a Puppet bug if you need ``exec`` to do your work. - - -### Exec Parameters -#### command (*namevar*) -The actual command to execute. Must either be fully qualified -or a search path for the command must be provided. If the command -succeeds, any output produced will be logged at the instance's -normal log level (usually ``notice``), but if the command fails -(meaning its return code does not match the specified code) then -any output is logged at the ``err`` log level. - -#### creates -A file that this command creates. If this -parameter is provided, then the command will only be run -if the specified file does not exist. - - exec { "tar xf /my/tar/file.tar": - cwd => "/var/tmp", - creates => "/var/tmp/myfile", - path => ["/usr/bin", "/usr/sbin"] - } - - -#### cwd -The directory from which to run the command. If -this directory does not exist, the command will fail. - -#### env -Any additional environment variables you want to set for a -command. Note that if you use this to set PATH, it will override -the ``path`` attribute. Multiple environment variables should be -specified as an array. - -#### group -The group to run the command as. This seems to work quite -haphazardly on different platforms -- it is a platform issue -not a Ruby or Puppet one, since the same variety exists when -running commnands as different users in the shell. - -#### logoutput -Whether to log output. Defaults to logging output at the -loglevel for the ``exec`` element. Values are **true**, *false*, -and any legal log level. Valid values are ``true``, ``false``, ``debug``, ``info``, ``notice``, ``warning``, ``err``, ``alert``, ``emerg``, ``crit``. - -#### onlyif -If this parameter is set, then this ``exec`` will only run if -the command returns 0. For example: - - exec { "logrotate": - path => "/usr/bin:/usr/sbin:/bin", - onlyif => "test `du /var/log/messages | cut -f1` -gt 100000" - } - -This would run ``logrotate`` only if that test returned true. - -Note that this command follows the same rules as the main command, -which is to say that it must be fully qualified if the path is not set. - -#### path -The search path used for command execution. -Commands must be fully qualified if no path is specified. Paths -can be specified as an array or as a colon-separated list. - -#### refreshonly -The command should only be run as a -refresh mechanism for when a dependent object is changed. It only -makes sense to use this option when this command depends on some -other object; it is useful for triggering an action: - - # Pull down the main aliases file - file { "/etc/aliases": - source => "puppet://server/module/aliases" - } - - # Rebuild the database, but only when the file changes - exec { newaliases: - path => ["/usr/bin", "/usr/sbin"], - subscribe => file["/etc/aliases"], - refreshonly => true - } - -Note that only ``subscribe`` can trigger actions, not ``require``, -so it only makes sense to use ``refreshonly`` with ``subscribe``. Valid values are ``true``, ``false``. - -#### returns -The expected return code. An error will be returned if the -executed command returns something else. Defaults to 0. - -#### timeout -The maximum time the command should take. If the command takes -longer than the timeout, the command is considered to have failed -and will be stopped. Use any negative number to disable the timeout. - -#### unless -If this parameter is set, then this ``exec`` will run unless -the command returns 0. For example: - - exec { "/bin/echo root >> /usr/lib/cron/cron.allow": - path => "/usr/bin:/usr/sbin:/bin", - unless => "grep root /usr/lib/cron/cron.allow 2>/dev/null" - } - -This would add ``root`` to the cron.allow file (on Solaris) unless -``grep`` determines it's already there. - -Note that this command follows the same rules as the main command, -which is to say that it must be fully qualified if the path is not set. - -#### user -The user to run the command as. Note that if you -use this then any error output is not currently captured. This -is because of a bug within Ruby. - - - - ----------------- - - -

file

-Manages local files, including setting ownership and -permissions, creation of both files and directories, and -retrieving entire files from remote servers. As Puppet matures, it -expected that the ``file`` element will be used less and less to -manage content, and instead native elements will be used to do so. - -If you find that you are often copying files in from a central -location, rather than using native elements, please contact -Reductive Labs and we can hopefully work with you to develop a -native element to support what you are doing. - - -### File Parameters -#### backup -Whether files should be backed up before -being replaced. The preferred method of backing files up is via -a ``filebucket``, which stores files by their MD5 sums and allows -easy retrieval without littering directories with backups. You -can specify a local filebucket or a network-accessible -server-based filebucket. Alternatively, if you specify any -value that begins with a ``.`` (e.g., ``.puppet-bak``), then -Puppet will use copy the file in the same directory with that -value as the extension of the backup. - -Puppet automatically creates a local filebucket named ``puppet`` and -defaults to backing up there. To use a server-based filebucket, -you must specify one in your configuration: - - filebucket { main: - server => puppet - } - -The ``puppetmasterd`` daemon creates a filebucket by default, -so you can usually back up to your main server with this -configuration. Once you've described the bucket in your -configuration, you can use it in any file: - - file { "/my/file": - source => "/path/in/nfs/or/something", - backup => main - } - -This will back the file up to the central server. - -At this point, the benefits of using a filebucket are that you do not -have backup files lying around on each of your machines, a given -version of a file is only backed up once, and you can restore -any given file manually, no matter how old. Eventually, -transactional support will be able to automatically restore -filebucketed files. - -#### checksum -How to check whether a file has changed. This state is used internally -for file copying, but it can also be used to monitor files somewhat -like Tripwire without managing the file contents in any way. You can -specify that a file's checksum should be monitored and then subscribe to -the file from another object and receive events to signify -checksum changes, for instance. Valid values are ``time``, ``md5lite``, ``nosum``, ``timestamp``, ``md5``, ``mtime``. Values can also match ``(?-mix:^\{md5|md5lite|timestamp|mtime|time\})``. - -#### content -Specify the contents of a file as a string. Newlines, tabs, and -spaces can be specified using the escaped syntax (e.g., \n for a -newline). The primary purpose of this parameter is to provide a -kind of limited templating: - - define resolve(nameserver1, nameserver2, domain, search) { - $str = "search $search - domain $domain - nameserver $nameserver1 - nameserver $nameserver2 - " - - file { "/etc/resolv.conf": - content => $str - } - } - - This attribute is especially useful when used with - [templating](/trac/puppet/wiki/PuppetTemplating). - -#### ensure -Whether to create files that don't currently exist. -Possible values are *absent*, *present* (equivalent to ``exists`` in -most file tests -- will match any form of file existence, and if the -file is missing will create an empty file), *file*, and -*directory*. Specifying ``absent`` will delete the file, although -currently this will not recursively delete directories. - -Anything other than those values will be considered to be a symlink. -For instance, the following text creates a link: - - # Useful on solaris - file { "/etc/inetd.conf": - ensure => "/etc/inet/inetd.conf" - } - -You can make relative links: - - # Useful on solaris - file { "/etc/inetd.conf": - ensure => "inet/inetd.conf" - } - -If you need to make a relative link to a file named the same -as one of the valid values, you must prefix it with ``./`` or -something similar. - -You can also make recursive symlinks, which will create a -directory structure that maps to the target directory, -with directories corresponding to each directory -and links corresponding to each file. Valid values are ``link``, ``present``, ``absent`` (also called ``false``), ``directory``, ``file``. Values can also match ``(?-mix:.)``. - -#### force -Force the file operation. Currently only used when replacing -directories with links. Valid values are ``true``, ``false``. - -#### group -Which group should own the file. Argument can be either group -name or group ID. - -#### ignore -A parameter which omits action on files matching -specified patterns during recursion. Uses Ruby's builtin globbing -engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``. -Matches that would descend into the directory structure are ignored, -e.g., ``*/*``. - -#### linkmaker -An internal parameter used by the *symlink* -type to do recursive link creation. - -#### links -How to handle links during file actions. During file copying, -``follow`` will copy the target file instead of the link, ``manage`` -will copy the link itself, and ``ignore`` will just pass it by. -When not copying, ``manage`` and ``ignore`` behave equivalently -(because you cannot really ignore links entirely during local -recursion), and ``follow`` will manage the file to which the -link points. Valid values are ``follow``, ``manage``, ``ignore``. - -#### mode -Mode the file should be. Currently relatively limited: -you must specify the exact mode the file should be. - -#### owner -To whom the file should belong. Argument can be user name or -user ID. - -#### path (*namevar*) -The path to the file to manage. Must be fully qualified. - -#### purge -Whether unmanaged files should be purged. If you have a filebucket -configured the purged files will be uploaded, but if you do not, -this will destroy data. Only use this option for generated -files unless you really know what you are doing. This option only -makes sense when recursively managing directories. Valid values are ``true``, ``false``. - -#### recurse -Whether and how deeply to do recursive -management. Valid values are ``true``, ``false``, ``inf``. Values can also match ``(?-mix:^[0-9]+$)``. - -#### replace -Whether or not to replace a file that is -sourced but exists. This is useful for using file sources -purely for initialization. Valid values are ``true`` (also called ``yes``), ``false`` (also called ``no``). - -#### source -Copy a file over the current file. Uses ``checksum`` to -determine when a file should be copied. Valid values are either -fully qualified paths to files, or URIs. Currently supported URI -types are *puppet* and *file*. - -This is one of the primary mechanisms for getting content into -applications that Puppet does not directly support and is very -useful for those configuration files that don't change much across -sytems. For instance: - - class sendmail { - file { "/etc/mail/sendmail.cf": - source => "puppet://server/module/sendmail.cf" - } - } - -See the [fileserver docs][] for information on how to configure -and use file services within Puppet. - -If you specify multiple file sources for a file, then the first -source that exists will be used. This allows you to specify -what amount to search paths for files: - - file { "/path/to/my/file": - source => [ - "/nfs/files/file.$host", - "/nfs/files/file.$operatingsystem", - "/nfs/files/file" - ] - } - -This will use the first found file as the source. - -You cannot currently copy links using this mechanism; set ``links`` -to ``follow`` if any remote sources are links. - -[fileserver docs]: ../installing/fsconfigref.html - - -#### sourceselect -Whether to copy all valid sources, or just the first one. Valid values are ``first``, ``all``. - -#### target -The target for creating a link. Currently, symlinks are the -only type supported. Valid values are ``notlink``. Values can also match ``(?-mix:.)``. - -#### type -A read-only state to check the file type. - - - - ----------------- - - -

filebucket

-A repository for backing up files. If no filebucket is -defined, then files will be backed up in their current directory, -but the filebucket can be either a host- or site-global repository -for backing up. It stores files and returns the MD5 sum, which -can later be used to retrieve the file if restoration becomes -necessary. A filebucket does not do any work itself; instead, -it can be specified as the value of *backup* in a **file** object. - -Currently, filebuckets are only useful for manual retrieval of -accidentally removed files (e.g., you look in the log for the md5 -sum and retrieve the file with that sum from the filebucket), but -when transactions are fully supported filebuckets will be used to -undo transactions. - -You will normally want to define a single filebucket for your -whole network and then use that as the default backup location: - - # Define the bucket - filebucket { main: server => puppet } - - # Specify it as the default target - File { backup => main } - -Puppetmaster servers create a filebucket by default, so this will -work in a default configuration. - - - -### Filebucket Parameters -#### name (*namevar*) -The name of the filebucket. - -#### path -The path to the local filebucket. If this is -not specified, then the bucket is remote and *server* must be -specified. - -#### port -The port on which the remote server is listening. -Defaults to the normal Puppet port, 8140. - -#### server -The server providing the filebucket. If this is -not specified, then the bucket is local and *path* must be -specified. - - - - ----------------- - - -

group

-Manage groups. This type can only create groups. Group -membership must be managed on individual users. This element type -uses the prescribed native tools for creating groups and generally -uses POSIX APIs for retrieving information about them. It does -not directly modify /etc/group or anything. - -For most platforms, the tools used are ``groupadd`` and its ilk; -for Mac OS X, NetInfo is used. This is currently unconfigurable, -but if you desperately need it to be so, please contact us. - - -### Group Parameters -#### allowdupe -Whether to allow duplicate GIDs. This option does not work on -FreeBSD (contract to the ``pw`` man page). Valid values are ``true``, ``false``. - -#### ensure -The basic state that the object should be in. Valid values are ``absent``, ``present``. - -#### gid -The group ID. Must be specified numerically. If not -specified, a number will be picked, which can result in ID -differences across systems and thus is not recommended. The -GID is picked according to local system standards. - -#### name (*namevar*) -The group name. While naming limitations vary by -system, it is advisable to keep the name to the degenerate -limitations, which is a maximum of 8 characters beginning with -a letter. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **groupadd**: Group management via ``groupadd`` and its ilk. The default - for most platforms Required binaries: ``groupadd``, ``groupmod``, ``groupdel``. -* **netinfo**: Group management using NetInfo. Default for ``operatingsystem`` == ``darwin``. Required binaries: ``nireport``, ``niutil``. -* **pw**: Group management via ``pw``. Only works on FreeBSD. Default for ``operatingsystem`` == ``freebsd``. Required binaries: ``/usr/sbin/pw``. - - - - ----------------- - - -

host

-Installs and manages host entries. For most systems, these -entries will just be in /etc/hosts, but some systems (notably OS X) -will have different solutions. - - -### Host Parameters -#### alias -Any alias the host might have. Multiple values must be -specified as an array. Note that this state has the same name -as one of the metaparams; using this state to set aliases will -make those aliases available in your Puppet scripts and also on -disk. - -#### ensure -The basic state that the object should be in. Valid values are ``absent``, ``present``. - -#### ip -The host's IP address, IPv4 or IPv6. - -#### name (*namevar*) -The host name. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **netinfo**: Host management in NetInfo. This provider is highly experimental and is known - not to work currently. Default for ``operatingsystem`` == ``darwin``. Required binaries: ``nireport``, ``mount``, ``niutil``, ``df``, ``umount``. -* **parsed**: - -#### target -The file in which to store service information. Only used by -those providers that write to disk (i.e., not NetInfo). - - - - ----------------- - - -

mount

-Manages mounted mounts, including putting mount -information into the mount table. The actual behavior depends -on the value of the 'ensure' parameter. - - -### Mount Parameters -#### atboot -Whether to mount the mount at boot. Not all platforms -support this. - -#### blockdevice -The the device to fsck. This is state is only valid -on Solaris, and in most cases will default to the correct -value. - -#### device -The device providing the mount. This can be whatever -device is supporting by the mount, including network -devices or devices specified by UUID rather than device -path, depending on the operating system. - -#### dump -Whether to dump the mount. Not all platforms -support this. - -#### ensure -Control what to do with this mount. If the value is -``present``, the mount is entered into the mount table, -but not mounted, if it is ``absent``, the entry is removed -from the mount table and the filesystem is unmounted if -currently mounted, if it is ``mounted``, the filesystem -is entered into the mount table and mounted. Valid values are ``absent``, ``present`` (also called ``unmounted``), ``mounted``. - -#### fstype -The mount type. Valid values depend on the -operating system. - -#### name (*namevar*) -The mount path for the mount. - -#### options -Mount options for the mounts, as they would -appear in the fstab. - -#### pass -The pass in which the mount is checked. - -#### path -The deprecated name for the mount point. Please use ``name`` now. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **netinfo**: Mount management in NetInfo. This provider is highly experimental and is known - not to work currently. Default for ``operatingsystem`` == ``darwin``. Required binaries: ``nireport``, ``mount``, ``niutil``, ``df``, ``umount``. -* **parsed**: Required binaries: ``mount``, ``df``, ``umount``. - -#### target -The file in which to store the mount table. Only used by -those providers that write to disk (i.e., not NetInfo). - - - - ----------------- - - -

notify

-Sends an arbitrary message to the puppetd run-time log. - - -### Notify Parameters -#### message -The message to be sent to the log. - -#### name (*namevar*) -An arbitrary tag for your own reference; the name of the message. - -#### withpath -Whether to not to show the full object path. Sends the -message at the current loglevel. Valid values are ``true``, ``false``. - - - - ----------------- - - -

package

-Manage packages. There is a basic dichotomy in package -support right now: Some package types (e.g., yum and apt) can -retrieve their own package files, while others (e.g., rpm and -sun) cannot. For those package formats that cannot retrieve -their own files, you can use the ``source`` parameter to point to -the correct file. - -Puppet will automatically guess the packaging format that you are -using based on the platform you are on, but you can override it -using the ``type`` parameter; obviously, if you specify that you -want to use ``rpm`` then the ``rpm`` tools must be available. - - -### Package Parameters -#### adminfile -A file containing package defaults for installing packages. -This is currently only used on Solaris. The value will be -validated according to system rules, which in the case of -Solaris means that it should either be a fully qualified path -or it should be in /var/sadm/install/admin. - -#### allowcdrom -Tells apt to allow cdrom sources in the sources.list file. -Normally apt will bail if you try this. Valid values are ``true``, ``false``. - -#### category -A read-only parameter set by the package. - -#### configfiles -Whether configfiles should be kept or replaced. Most packages -types do not support this parameter. Valid values are ``keep``, ``replace``. - -#### description -A read-only parameter set by the package. - -#### ensure -What state the package should be in. -*latest* only makes sense for those packaging formats that can -retrieve new packages on their own and will throw an error on -those that cannot. For those packaging systems that allow you -to specify package versions, specify them here. Valid values are ``absent``, ``present`` (also called ``installed``), ``latest``. Values can also match ``(?-mix:.)``. - -#### instance -A read-only parameter set by the package. - -#### name (*namevar*) -The package name. This is the name that the packaging -system uses internally, which is sometimes (especially on Solaris) -a name that is basically useless to humans. If you want to -abstract package installation, then you can use aliases to provide -a common name to packages: - - # In the 'openssl' class - $ssl = $operationgsystem ? { - solaris => SMCossl, - default => openssl - } - - # It is not an error to set an alias to the same value as the - # object name. - package { $ssl: - ensure => installed, - alias => openssl - } - - . etc. . - - $ssh = $operationgsystem ? { - solaris => SMCossh, - default => openssh - } - - # Use the alias to specify a dependency, rather than - # having another selector to figure it out again. - package { $ssh: - ensure => installed, - alias => openssh, - require => package[openssl] - } - - -#### platform -A read-only parameter set by the package. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **apple**: Package management based on OS X's builtin packaging system. This is - essentially the simplest and least functional package system in existence -- - it only supports installation; no deletion or upgrades. Default for ``operatingsystem`` == ``darwin``. Required binaries: ``/usr/sbin/installer``. -* **apt**: Package management via ``apt-get``. Default for ``operatingsystem`` == ``debian``. Required binaries: ``/usr/bin/apt-get``, ``/usr/bin/apt-cache``, ``/usr/bin/debconf-set-selections``. -* **aptitude**: Package management via ``aptitude``. Required binaries: ``/usr/bin/apt-cache``, ``/usr/bin/aptitude``. -* **blastwave**: Package management using Blastwave.org's ``pkg-get`` command on Solaris. Required binaries: ``pkg-get``. -* **darwinport**: Package management using DarwinPorts on OS X. Required binaries: ``/opt/local/bin/port``. -* **dpkg**: Package management via ``dpkg``. Because this only uses ``dpkg`` - and not ``apt``, you must specify the source of any packages you want - to manage. Required binaries: ``/usr/bin/dpkg``, ``/usr/bin/dpkg-query``. -* **freebsd**: The specific form of package management on FreeBSD. This is an - extremely quirky packaging system, in that it freely mixes between - ports and packages. Apparently all of the tools are written in Ruby, - so there are plans to rewrite this support to directly use those - libraries. Required binaries: ``/usr/sbin/pkg_info``, ``/usr/sbin/pkg_add``, ``/usr/sbin/pkg_delete``. -* **gem**: Ruby Gem support. By default uses remote gems, but you can specify - the path to a local gem via ``source``. Required binaries: ``gem``. -* **openbsd**: OpenBSD's form of ``pkg_add`` support. Default for ``operatingsystem`` == ``openbsd``. Required binaries: ``pkg_info``, ``pkg_add``, ``pkg_delete``. -* **pkgdmg**: Package management based on Apple's Installer.app and DiskUtility.app Required binaries: ``/usr/sbin/installer``, ``/usr/bin/hdiutil``, ``/usr/bin/curl``. -* **portage**: Provides packaging support for Gentoo's portage system. Default for ``operatingsystem`` == ``gentoo``. Required binaries: ``/usr/bin/emerge``, ``/usr/bin/eix``. -* **ports**: Support for FreeBSD's ports. Again, this still mixes packages - and ports. Default for ``operatingsystem`` == ``freebsd``. Required binaries: ``/usr/local/sbin/portupgrade``, ``/usr/local/sbin/portversion``, ``/usr/local/sbin/pkg_deinstall``, ``/usr/sbin/pkg_info``. -* **rpm**: RPM packaging support; should work anywhere with a working ``rpm`` - binary. Required binaries: ``rpm``. -* **sun**: Sun's packaging system. Requires that you specify the source for - the packages you're managing. Default for ``operatingsystem`` == ``solaris``. Required binaries: ``/usr/bin/pkginfo``, ``/usr/sbin/pkgrm``, ``/usr/sbin/pkgadd``. -* **sunfreeware**: Package management using sunfreeware.com's ``pkg-get`` command on Solaris. - At this point, support is exactly the same as ``blastwave`` support and - has not actually been tested. Required binaries: ``pkg-get``. -* **up2date**: Support for Red Hat's proprietary ``up2date`` package update - mechanism. Default for ``operatingsystem`` == ``redhat``. Required binaries: ``/usr/sbin/up2date-nox``. -* **yum**: Support via ``yum``. Default for ``operatingsystem`` == ``fedoracentos``. Required binaries: ``yum``, ``rpm``. - -#### responsefile -A file containing any necessary answers to questions asked by -the package. This is currently only used on Solaris. The -value will be validated according to system rules, but it should -generally be a fully qualified path. - -#### root -A read-only parameter set by the package. - -#### source -Where to find the actual package. This must be a local file -(or on a network file system) or a URL that your specific -packaging type understands; Puppet will not retrieve files for you. - -#### status -A read-only parameter set by the package. - -#### type -Deprecated form of ``provider``. - -#### vendor -A read-only parameter set by the package. - - - - ----------------- - - -

resources

-This is a metatype that can manage other resource types. Any -metaparams specified here will be passed on to any generated resources, -so you can purge umanaged resources but set ``noop`` to true so the -purging is only logged and does not actually happen. - - -### Resources Parameters -#### name (*namevar*) -The name of the type to be managed. - -#### purge -Purge unmanaged resources. This will delete any resource -that is not specified in your configuration -and is not required by any specified resources. Valid values are ``true``, ``false``. - -#### unless_system_user -This keeps system users from being purged. By default, it -does not purge users whose UIDs are less than or equal to 500, but you can specify -a different UID as the inclusive limit. Valid values are ``true``, ``false``. Values can also match ``(?-mix:^\d+$)``. - - - - ----------------- - - -

schedule

-Defined schedules for Puppet. The important thing to understand -about how schedules are currently implemented in Puppet is that they -can only be used to stop an element from being applied, they never -guarantee that it is applied. - -Every time Puppet applies its configuration, it will collect the -list of elements whose schedule does not eliminate them from -running right then, but there is currently no system in place to -guarantee that a given element runs at a given time. If you -specify a very restrictive schedule and Puppet happens to run at a -time within that schedule, then the elements will get applied; -otherwise, that work may never get done. - -Thus, it behooves you to use wider scheduling (e.g., over a couple of -hours) combined with periods and repetitions. For instance, if you -wanted to restrict certain elements to only running once, between -the hours of two and 4 AM, then you would use this schedule: - - schedule { maint: - range => "2 - 4", - period => daily, - repeat => 1 - } - -With this schedule, the first time that Puppet runs between 2 and 4 AM, -all elements with this schedule will get applied, but they won't -get applied again between 2 and 4 because they will have already -run once that day, and they won't get applied outside that schedule -because they will be outside the scheduled range. - -Puppet automatically creates a schedule for each valid period with the -same name as that period (e.g., hourly and daily). Additionally, -a schedule named *puppet* is created and used as the default, -with the following attributes: - - schedule { puppet: - period => hourly, - repeat => 2 - } - -This will cause elements to be applied every 30 minutes by default. - - - -### Schedule Parameters -#### name (*namevar*) -The name of the schedule. This name is used to retrieve the -schedule when assigning it to an object: - - schedule { daily: - period => daily, - range => [2, 4] - } - - exec { "/usr/bin/apt-get update": - schedule => daily - } - - -#### period -The period of repetition for an element. Choose from among -a fixed list of *hourly*, *daily*, *weekly*, and *monthly*. -The default is for an element to get applied every time that -Puppet runs, whatever that period is. - -Note that the period defines how often a given element will get -applied but not when; if you would like to restrict the hours -that a given element can be applied (e.g., only at night during -a maintenance window) then use the ``range`` attribute. - -If the provided periods are not sufficient, you can provide a -value to the *repeat* attribute, which will cause Puppet to -schedule the affected elements evenly in the period the -specified number of times. Take this schedule: - - schedule { veryoften: - period => hourly, - repeat => 6 - } - -This can cause Puppet to apply that element up to every 10 minutes. - -At the moment, Puppet cannot guarantee that level of -repetition; that is, it can run up to every 10 minutes, but -internal factors might prevent it from actually running that -often (e.g., long-running Puppet runs will squash conflictingly -scheduled runs). - -See the ``periodmatch`` attribute for tuning whether to match -times by their distance apart or by their specific value. Valid values are ``hourly``, ``daily``, ``weekly``, ``monthly``. - -#### periodmatch -Whether periods should be matched by number (e.g., the two times -are in the same hour) or by distance (e.g., the two times are -60 minutes apart). *number*/**distance** Valid values are ``number``, ``distance``. - -#### range -The earliest and latest that an element can be applied. This -is always a range within a 24 hour period, and hours must be -specified in numbers between 0 and 23, inclusive. Minutes and -seconds can be provided, using the normal colon as a separator. -For instance: - - schedule { maintenance: - range => "1:30 - 4:30" - } - -This is mostly useful for restricting certain elements to being -applied in maintenance windows or during off-peak hours. - -#### repeat -How often the application gets repeated in a given period. -Defaults to 1. Must be an integer. - - - - ----------------- - - -

service

-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. - - -### Service Parameters -#### binary -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. - -#### enable -Whether a service should be enabled to start at boot. -This state behaves quite differently depending on the platform; -wherever possible, it relies on local tools to enable or disable -a given service. *true*/*false*/*runlevels* Valid values are ``true``, ``false``. - -#### ensure -Whether a service should be running. **true**/*false* Valid values are ``running`` (also called ``true``), ``stopped`` (also called ``false``). - -#### hasrestart -Specify that an init script has a ``restart`` option. Otherwise, -the init script's ``stop`` and ``start`` methods are used. Valid values are ``true``, ``false``. - -#### hasstatus -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. - -#### name (*namevar*) -The name of the service to run. This name -is used to find the service in whatever service subsystem it -is in. - -#### path -The search path for finding init scripts. - -#### pattern -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. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **base**: The simplest form of service support. You have to specify - enough about your service for this to work; the minimum you can specify - is a binary for starting the process, and this same binary will be searched - for in the process table to stop the service. It is preferable to - specify start, stop, and status commands, akin to how you would do - so using ``init``. Required binaries: ``kill``. -* **debian**: Debian's form of ``init``-style management. The only difference - is that this supports service enabling and disabling via ``update-rc.d``. Default for ``operatingsystem`` == ``debian``. Required binaries: ``/usr/sbin/update-rc.d``. -* **gentoo**: Gentoo's form of ``init``-style service - management; uses ``rc-update`` for service enabling and disabling. Default for ``operatingsystem`` == ``gentoo``. Required binaries: ``/sbin/rc-update``. -* **init**: Standard init service management. This provider assumes that the - init script has not ``status`` command, because so few scripts do, - so you need to either provide a status command or specify via ``hasstatus`` - that one already exists in the init script. -* **redhat**: Red Hat's (and probably many others) form of ``init``-style service - management; uses ``chkconfig`` for service enabling and disabling. Default for ``operatingsystem`` == ``redhatfedorasuse``. Required binaries: ``/sbin/chkconfig``. -* **smf**: Support for Sun's new Service Management Framework. Starting a service - is effectively equivalent to enabling it, so there is only support - for starting and stopping services, which also enables and disables them, - respectively. Default for ``operatingsystem`` == ``solaris``. Required binaries: ``/usr/bin/svcs``, ``/usr/sbin/svcadm``. - -#### restart -Specify a *restart* command manually. If left -unspecified, the service will be stopped and then started. - -#### running -A place-holder parameter that wraps ``ensure``, because -``running`` is deprecated. You should use ``ensure`` instead -of this, but using this will still work, albeit with a -warning. - -#### start -Specify a *start* command manually. Most service subsystems -support a ``start`` command, so this will not need to be -specified. - -#### status -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. - -#### stop -Specify a *stop* command manually. - -#### type -Deprecated form of ``provder``. - - - - ----------------- - - -

sshkey

-Installs and manages ssh host keys. At this point, this type -only knows how to install keys into /etc/ssh/ssh_known_hosts, and -it cannot manage user authorized keys yet. - - -### Sshkey Parameters -#### alias -Any alias the host might have. Multiple values must be -specified as an array. Note that this state has the same name -as one of the metaparams; using this state to set aliases will -make those aliases available in your Puppet scripts. - -#### ensure -The basic state that the object should be in. Valid values are ``absent``, ``present``. - -#### key -The key itself; generally a long string of hex digits. - -#### name (*namevar*) -The host name. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **parsed**: - -#### target -The file in which to store the mount table. Only used by -those providers that write to disk (i.e., not NetInfo). - -#### type -The encryption type used. Probably ssh-dss or ssh-rsa. Valid values are ``ssh-dss`` (also called ``dsa``), ``ssh-rsa`` (also called ``rsa``). - - - - ----------------- - - -

tidy

-Remove unwanted files based on specific criteria. Multiple -criteria are OR'd together, so a file that is too large but is not -old enough will still get tidied. - - -### Tidy Parameters -#### age -Tidy files whose age is equal to or greater than -the specified time. You can choose seconds, minutes, -hours, days, or weeks by specifying the first letter of any -of those words (e.g., '1w'). - -#### backup -Whether files should be backed up before -being replaced. The preferred method of backing files up is via -a ``filebucket``, which stores files by their MD5 sums and allows -easy retrieval without littering directories with backups. You -can specify a local filebucket or a network-accessible -server-based filebucket. Alternatively, if you specify any -value that begins with a ``.`` (e.g., ``.puppet-bak``), then -Puppet will use copy the file in the same directory with that -value as the extension of the backup. - -Puppet automatically creates a local filebucket named ``puppet`` and -defaults to backing up there. To use a server-based filebucket, -you must specify one in your configuration: - - filebucket { main: - server => puppet - } - -The ``puppetmasterd`` daemon creates a filebucket by default, -so you can usually back up to your main server with this -configuration. Once you've described the bucket in your -configuration, you can use it in any file: - - file { "/my/file": - source => "/path/in/nfs/or/something", - backup => main - } - -This will back the file up to the central server. - -At this point, the benefits of using a filebucket are that you do not -have backup files lying around on each of your machines, a given -version of a file is only backed up once, and you can restore -any given file manually, no matter how old. Eventually, -transactional support will be able to automatically restore -filebucketed files. - -#### path (*namevar*) -The path to the file or directory to manage. Must be fully -qualified. - -#### recurse -If target is a directory, recursively descend -into the directory looking for files to tidy. - -#### rmdirs -Tidy directories in addition to files; that is, remove -directories whose age is older than the specified criteria. -This will only remove empty directories, so all contained -files must also be tidied before a directory gets removed. - -#### size -Tidy files whose size is equal to or greater than -the specified size. Unqualified values are in kilobytes, but -*b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*, -and *megabytes*, respectively. Only the first character is -significant, so the full word can also be used. - -#### type -Set the mechanism for determining age. Valid values are ``atime``, ``mtime``, ``ctime``. - - - - ----------------- - - -

user

-Manage users. Currently can create and modify users, but -cannot delete them. Theoretically all of the parameters are -optional, but if no parameters are specified the comment will -be set to the user name in order to make the internals work out -correctly. - -This element type uses the prescribed native tools for creating -groups and generally uses POSIX APIs for retrieving information -about them. It does not directly modify /etc/passwd or anything. - -For most platforms, the tools used are ``useradd`` and its ilk; -for Mac OS X, NetInfo is used. This is currently unconfigurable, -but if you desperately need it to be so, please contact us. - - -### User Parameters -#### allowdupe -Whether to allow duplicate UIDs. Valid values are ``true``, ``false``. - -#### comment -A description of the user. Generally is a user's full name. - -#### ensure -The basic state that the object should be in. Valid values are ``absent``, ``present``. - -#### gid -The user's primary group. Can be specified numerically or -by name. - -#### groups -The groups of which the user is a member. The primary -group should not be listed. Multiple groups should be -specified as an array. - -#### home -The home directory of the user. The directory must be created -separately and is not currently checked for existence. - -#### membership -Whether specified groups should be treated as the only groups -of which the user is a member or whether they should merely -be treated as the minimum membership list. Valid values are ``inclusive``, ``minimum``. - -#### name (*namevar*) -User name. While limitations are determined for -each operating system, it is generally a good idea to keep to -the degenerate 8 characters, beginning with a letter. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **netinfo**: User management in NetInfo. Default for ``operatingsystem`` == ``darwin``. Required binaries: ``nireport``, ``niutil``. -* **pw**: User management via ``pw`` on FreeBSD. Default for ``operatingsystem`` == ``freebsd``. Required binaries: ``pw``. -* **useradd**: User management via ``useradd`` and its ilk. Required binaries: ``useradd``, ``usermod``, ``userdel``. - -#### shell -The user's login shell. The shell must exist and be -executable. - -#### uid -The user ID. Must be specified numerically. For new users -being created, if no user ID is specified then one will be -chosen automatically, which will likely result in the same user -having different IDs on different systems, which is not -recommended. - - - - ----------------- - - -

yumrepo

-The client-side description of a yum repository. Repository -configurations are found by parsing /etc/yum.conf and -the files indicated by reposdir in that file (see yum.conf(5) -for details) - -Most parameters are identical to the ones documented -in yum.conf(5) - -Continuation lines that yum supports for example for the -baseurl are not supported. No attempt is made to access -files included with the **include** directive - - -### Yumrepo Parameters -#### baseurl -The URL for this repository. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### descr -A human readable description of the repository. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### enabled -Whether this repository is enabled or disabled. Possible -values are '0', and '1'. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:(0|1))``. - -#### enablegroups -Determines whether yum will allow the use of -package groups for this repository. Possible -values are '0', and '1'. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:(0|1))``. - -#### exclude -List of shell globs. Matching packages will never be -considered in updates or installs for this repo. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### failovermethod -Either 'roundrobin' or 'priority'. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:roundrobin|priority)``. - -#### gpgcheck -Whether to check the GPG signature on packages installed -from this repository. Possible values are '0', and '1'. - -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:(0|1))``. - -#### gpgkey -The URL for the GPG key with which packages from this -repository are signed. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### include -A URL from which to include the config. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### includepkgs -List of shell globs. If this is set, only packages -matching one of the globs will be considered for -update or install. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### keepalive -Either '1' or '0'. This tells yum whether or not HTTP/1.1 -keepalive should be used with this repository. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:(0|1))``. - -#### metadata_expire -Number of seconds after which the metadata will expire. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:[0-9]+)``. - -#### mirrorlist -The URL that holds the list of mirrors for this repository. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:.*)``. - -#### name (*namevar*) -The name of the repository. - -#### timeout -Number of seconds to wait for a connection before timing -out. -Set this to 'absent' to remove it from the file completely Valid values are ``absent``. Values can also match ``(?-mix:[0-9]+)``. - - - - ----------------- - - -

zone

-Solaris zones. - - -### Zone Parameters -#### autoboot -Whether the zone should automatically boot. Valid values are ``true``, ``false``. - -#### ensure -The running state of the zone. The valid states directly reflect -the states that ``zoneadm`` provides. The states are linear, -in that a zone must be ``configured`` then ``installed``, and -only then can be ``running``. Note also that ``halt`` is currently -used to stop zones. Valid values are ``absent``, ``configured``, ``installed``, ``running``. - -#### id -The numerical ID of the zone. This number is autogenerated -and cannot be changed. - -#### inherit -The list of directories that the zone inherits from the global -zone. All directories must be fully qualified. - -#### ip -The IP address of the zone. IP addresses must be specified -with the interface, separated by a colon, e.g.: bge0:192.168.0.1. -For multiple interfaces, specify them in an array. - -#### name (*namevar*) -The name of the zone. - -#### path -The root of the zone's filesystem. Must be a fully qualified -file name. If you include '%s' in the path, then it will be -replaced with the zone's name. At this point, you cannot use -Puppet to move a zone. - -#### pool -The resource pool for this zone. - -#### provider -The specific backend for provider to use. You will -seldom need to specify this -- Puppet will usually discover the -appropriate provider for your platform. Available providers are: - -* **solaris**: Provider for Solaris Zones. Default for ``operatingsystem`` == ``solaris``. Required binaries: ``/usr/sbin/zoneadm``, ``/usr/sbin/zonecfg``. - -#### realhostname -The actual hostname of the zone. - -#### shares -Number of FSS CPU shares allocated to the zone. - -#### sysidcfg -The text to go into the sysidcfg file when the zone is first -booted. The best way is to use a template: - -

-    # $templatedir/sysidcfg
-    system_locale=en_US
-    timezone=GMT
-    terminal=xterms
-    security_policy=NONE
-    root_password=<%= password %>
-    timeserver=localhost
-    name_service=DNS {domain_name=<%= domain %>
-            name_server=<%= nameserver %>}
-    network_interface=primary {hostname=<%= realhostname %>
-            ip_address=<%= ip %>
-            netmask=<%= netmask %>
-            protocol_ipv6=no
-            default_route=<%= defaultroute %>}
-    nfs4_domain=dynamic
-
- -And then call that: - - zone { myzone: - ip => "bge0:192.168.0.23", - sysidcfg => template(sysidcfg), - path => "/opt/zones/myzone", - realhostname => "fully.qualified.domain.name" - } - -The sysidcfg only matters on the first booting of the zone, -so Puppet only checks for it at that time. - - - - - ----------------- - - -*This page autogenerated on Fri Jan 26 16:40:46 CST 2007* diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb index 9825efc61..0e31bb41c 100644 --- a/lib/puppet/metatype/metaparams.rb +++ b/lib/puppet/metatype/metaparams.rb @@ -1,417 +1,417 @@ require 'puppet' require 'puppet/type' class Puppet::Type # Add all of the meta parameters. #newmetaparam(:onerror) do # desc "How to handle errors -- roll back innermost # transaction, roll back entire transaction, ignore, etc. Currently # non-functional." #end newmetaparam(:noop) do desc "Boolean flag indicating whether work should actually be done." newvalues(:true, :false) munge do |value| case value when true, :true, "true": @resource.noop = true when false, :false, "false": @resource.noop = false end end end newmetaparam(:schedule) do desc "On what schedule the object should be managed. You must create a schedule object, and then reference the name of that object to use that for your schedule:: schedule { daily: period => daily, range => \"2-4\" } exec { \"/usr/bin/apt-get update\": schedule => daily } The creation of the schedule object does not need to appear in the configuration before objects that use it." end newmetaparam(:check) do desc "Propertys which should have their values retrieved but which should not actually be modified. This is currently used internally, but will eventually be used for querying, so that you could specify that you wanted to check the install state of all packages, and then query the Puppet client daemon to get reports on all packages." munge do |args| # If they've specified all, collect all known properties if args == :all args = @resource.class.properties.find_all do |property| # Only get properties supported by our provider if @resource.provider @resource.provider.class.supports_parameter?(property) else true end end.collect do |property| property.name end end unless args.is_a?(Array) args = [args] end unless defined? @resource self.devfail "No parent for %s, %s?" % [self.class, self.name] end args.each { |property| unless property.is_a?(Symbol) property = property.intern end next if @resource.propertydefined?(property) unless propertyklass = @resource.class.validproperty?(property) if @resource.class.validattr?(property) next else raise Puppet::Error, "%s is not a valid attribute for %s" % [property, self.class.name] end end next unless propertyklass.checkable? @resource.newattr(property) } end end # We've got four relationship metaparameters, so this method is used # to reduce code duplication between them. def store_relationship(param, values) # We need to support values passed in as an array or as a # resource reference. result = [] # 'values' could be an array or a reference. If it's an array, # it could be an array of references or an array of arrays. if values.is_a?(Puppet::Type) result << [values.class.name, values.title] else unless values.is_a?(Array) devfail "Relationships must be resource references" end if values[0].is_a?(String) or values[0].is_a?(Symbol) # we're a type/title array reference values[0] = symbolize(values[0]) result << values else # we're an array of stuff values.each do |value| if value.is_a?(Puppet::Type) result << [value.class.name, value.title] elsif value.is_a?(Array) value[0] = symbolize(value[0]) result << value else devfail "Invalid relationship %s" % value.inspect end end end end if existing = self[param] result = existing + result end result end newmetaparam(:loglevel) do desc "Sets the level that information will be logged. The log levels have the biggest impact when logs are sent to syslog (which is currently the default)." defaultto :notice newvalues(*Puppet::Util::Log.levels) newvalues(:verbose) munge do |loglevel| val = super(loglevel) if val == :verbose val = :info end val end end newmetaparam(:alias) do desc "Creates an alias for the object. Puppet uses this internally when you provide a symbolic name:: file { sshdconfig: path => $operatingsystem ? { solaris => \"/usr/local/etc/ssh/sshd_config\", default => \"/etc/ssh/sshd_config\" }, source => \"...\" } service { sshd: subscribe => file[sshdconfig] } When you use this feature, the parser sets ``sshdconfig`` as the name, and the library sets that as an alias for the file so the dependency lookup for ``sshd`` works. You can use this parameter yourself, but note that only the library can use these aliases; for instance, the following code will not work:: file { \"/etc/ssh/sshd_config\": owner => root, group => root, alias => sshdconfig } file { sshdconfig: mode => 644 } There's no way here for the Puppet parser to know that these two stanzas should be affecting the same file. See the `LanguageTutorial language tutorial`:trac: for more information. " munge do |aliases| unless aliases.is_a?(Array) aliases = [aliases] end @resource.info "Adding aliases %s" % aliases.collect { |a| a.inspect }.join(", ") aliases.each do |other| if obj = @resource.class[other] unless obj == @resource self.fail( "%s can not create alias %s: object already exists" % [@resource.title, other] ) end next end @resource.class.alias(other, @resource) end end end newmetaparam(:tag) do desc "Add the specified tags to the associated resource. While all resources are automatically tagged with as much information as possible (e.g., each class and definition containing the resource), it can be useful to add your own tags to a given resource. Tags are currently useful for things like applying a subset of a host's configuration:: puppetd --test --tags mytag This way, when you're testing a configuration you can run just the portion you're testing." munge do |tags| tags = [tags] unless tags.is_a? Array tags.each do |tag| @resource.tag(tag) end end end class RelationshipMetaparam < Puppet::Parameter class << self attr_accessor :direction, :events, :callback, :subclasses end @subclasses = [] def self.inherited(sub) @subclasses << sub end def munge(rels) @resource.store_relationship(self.class.name, rels) end # Create edges from each of our relationships. :in # relationships are specified by the event-receivers, and :out # relationships are specified by the event generator. This # way 'source' and 'target' are consistent terms in both edges # and events -- that is, an event targets edges whose source matches # the event's source. The direction of the relationship determines # which resource is applied first and which resource is considered # to be the event generator. def to_edges @value.collect do |value| # we just have a name and a type, and we need to convert it # to an object... tname, name = value object = nil if type = Puppet::Type.type(tname) object = type[name] else # try to treat it as a component object = Puppet::Type::Component["#{tname}[#{name}]"] end # Either of the two retrieval attempts could have returned # nil. unless object - self.fail "Could not retrieve dependency '%s[%s]'" % - [tname.to_s.capitalize, name] + self.fail "Could not retrieve dependency '%s[%s]' of %s" % + [tname.to_s.capitalize, self.ref, name] end # Are we requiring them, or vice versa? See the method docs # for futher info on this. if self.class.direction == :in source = object target = @resource else source = @resource target = object end if method = self.class.callback subargs = { :event => self.class.events, :callback => method } self.debug("subscribes to %s" % [object.ref]) else # If there's no callback, there's no point in even adding # a label. subargs = nil self.debug("requires %s" % [object.ref]) end rel = Puppet::Relationship.new(source, target, subargs) end end end def self.relationship_params RelationshipMetaparam.subclasses end # Note that the order in which the relationships params is defined # matters. The labelled params (notify and subcribe) must be later, # so that if both params are used, those ones win. It's a hackish # solution, but it works. newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do desc "One or more objects that this object depends on. This is used purely for guaranteeing that changes to required objects happen before the dependent object. For instance:: # Create the destination directory before you copy things down file { \"/usr/local/scripts\": ensure => directory } file { \"/usr/local/scripts/myscript\": source => \"puppet://server/module/myscript\", mode => 755, require => File[\"/usr/local/scripts\"] } Multiple dependencies can be specified by providing a comma-seperated list of resources, enclosed in square brackets:: require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ] Note that Puppet will autorequire everything that it can, and there are hooks in place so that it's easy for resources to add new ways to autorequire objects, so if you think Puppet could be smarter here, let us know. In fact, the above code was redundant -- Puppet will autorequire any parent directories that are being managed; it will automatically realize that the parent directory should be created before the script is pulled down. Currently, exec resources will autorequire their CWD (if it is specified) plus any fully qualified paths that appear in the command. For instance, if you had an ``exec`` command that ran the ``myscript`` mentioned above, the above code that pulls the file down would be automatically listed as a requirement to the ``exec`` code, so that you would always be running againts the most recent version. " end newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do desc "One or more objects that this object depends on. Changes in the subscribed to objects result in the dependent objects being refreshed (e.g., a service will get restarted). For instance:: class nagios { file { \"/etc/nagios/nagios.conf\": source => \"puppet://server/module/nagios.conf\", alias => nagconf # just to make things easier for me } service { nagios: running => true, subscribe => file[nagconf] } } Currently the ``exec``, ``mount`` and ``service`` type support refreshing. " end newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do desc %{This parameter is the opposite of **require** -- it guarantees that the specified object is applied later than the specifying object:: file { "/var/nagios/configuration": source => "...", recurse => true, before => exec["nagios-rebuid"] } exec { "nagios-rebuild": command => "/usr/bin/make", cwd => "/var/nagios/configuration" } This will make sure all of the files are up to date before the make command is run.} end newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do desc %{This parameter is the opposite of **subscribe** -- it sends events to the specified object:: file { "/etc/sshd_config": source => "....", notify => service[sshd] } service { sshd: ensure => running } This will restart the sshd service if the sshd config file changes.} end end # Puppet::Type # $Id$ diff --git a/lib/puppet/network/handler/facts.rb b/lib/puppet/network/handler/facts.rb index e0b93f942..4767e8be4 100755 --- a/lib/puppet/network/handler/facts.rb +++ b/lib/puppet/network/handler/facts.rb @@ -1,70 +1,68 @@ require 'yaml' require 'puppet/util/fact_store' class Puppet::Network::Handler # Receive logs from remote hosts. class Facts < Handler desc "An interface for storing and retrieving client facts. Currently only used internally by Puppet." @interface = XMLRPC::Service::Interface.new("facts") { |iface| iface.add_method("void set(string, string)") iface.add_method("string get(string)") iface.add_method("integer store_date(string)") } def initialize(hash = {}) super backend = Puppet[:factstore] unless klass = Puppet::Util::FactStore.store(backend) raise Puppet::Error, "Could not find fact store %s" % backend end @backend = klass.new end # Get the facts from our back end. def get(node) if facts = @backend.get(node) return strip_internal(facts) else return nil end end # Set the facts in the backend. def set(node, facts) @backend.set(node, add_internal(facts)) nil end # Retrieve a client's storage date. def store_date(node) if facts = get(node) facts[:_puppet_timestamp].to_i else nil end end private # Add internal data to the facts for storage. def add_internal(facts) facts = facts.dup facts[:_puppet_timestamp] = Time.now facts end # Strip out that internal data. def strip_internal(facts) facts = facts.dup facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } facts end end end - -# $Id$ diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index e5bfa8122..ace383e9f 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -1,145 +1,143 @@ require 'openssl' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' class Puppet::Network::Handler class MasterError < Puppet::Error; end class Master < Handler desc "Puppet's configuration interface. Used for all interactions related to generating client configurations." include Puppet::Util attr_accessor :ast attr_reader :ca @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| iface.add_method("string getconfig(string)") iface.add_method("int freshness()") } # Tell a client whether there's a fresh config for it def freshness(client = nil, clientip = nil) client ||= Facter.value("hostname") config_handler.version(client, clientip) end def initialize(hash = {}) args = {} # Allow specification of a code snippet or of a file if code = hash[:Code] args[:Code] = code elsif man = hash[:Manifest] args[:Manifest] = man end if hash[:Local] @local = hash[:Local] else @local = false end args[:Local] = local? if hash.include?(:CA) and hash[:CA] @ca = Puppet::SSLCertificates::CA.new() else @ca = nil end Puppet.debug("Creating interpreter") if hash.include?(:UseNodes) args[:UseNodes] = hash[:UseNodes] elsif @local args[:UseNodes] = false end # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. if hash.include?(:Classes) args[:Classes] = hash[:Classes] end @config_handler = Puppet::Network::Handler.handler(:configuration).new(args) end # Call our various handlers; this handler is getting deprecated. def getconfig(facts, format = "marshal", client = nil, clientip = nil) facts = decode_facts(facts) client, clientip = clientname(client, clientip, facts) # Pass the facts to the fact handler fact_handler.set(client, facts) # And get the configuration from the config handler return config_handler.configuration(client) end def local=(val) @local = val config_handler.local = val fact_handler.local = val end private # Manipulate the client name as appropriate. def clientname(name, ip, facts) # Always use the hostname from Facter. client = facts["hostname"] clientip = facts["ipaddress"] if Puppet[:node_name] == 'cert' if name client = name end if ip clientip = ip end end return client, clientip end def config_handler unless defined? @config_handler @config_handler = Puppet::Network::Handler.handler(:config).new :local => local? end @config_handler end # def decode_facts(facts) if @local # we don't need to do anything, since we should already # have raw objects Puppet.debug "Our client is local" else Puppet.debug "Our client is remote" begin facts = YAML.load(CGI.unescape(facts)) rescue => detail raise XMLRPC::FaultException.new( 1, "Could not rebuild facts" ) end end return facts end def fact_handler unless defined? @fact_handler @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? end @fact_handler end end end - -# $Id$ diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb new file mode 100644 index 000000000..c44f0f903 --- /dev/null +++ b/lib/puppet/parser/ast/definition.rb @@ -0,0 +1,226 @@ +require 'puppet/parser/ast/branch' + +class Puppet::Parser::AST + # Evaluate the stored parse tree for a given component. This will + # receive the arguments passed to the component and also the type and + # name of the component. + class Definition < AST::Branch + include Puppet::Util + include Puppet::Util::Warnings + include Puppet::Util::MethodHelper + class << self + attr_accessor :name + end + + # The class name + @name = :definition + + attr_accessor :classname, :arguments, :code, :scope, :keyword + attr_accessor :exported, :namespace, :parser, :virtual + + # These are retrieved when looking up the superclass + attr_accessor :name + + attr_reader :parentclass + + def child_of?(klass) + false + end + + def evaluate_resource(hash) + origscope = hash[:scope] + title = hash[:title] + args = symbolize_options(hash[:arguments] || {}) + + name = args[:name] || title + + exported = hash[:exported] + virtual = hash[:virtual] + + pscope = origscope + scope = subscope(pscope, title) + + if virtual or origscope.virtual? + scope.virtual = true + end + + if exported or origscope.exported? + scope.exported = true + end + + # Additionally, add a tag for whatever kind of class + # we are + if @classname != "" and ! @classname.nil? + @classname.split(/::/).each { |tag| scope.tag(tag) } + end + + [name, title].each do |str| + unless str.nil? or str =~ /[^\w]/ or str == "" + scope.tag(str) + end + end + + # define all of the arguments in our local scope + if self.arguments + # Verify that all required arguments are either present or + # have been provided with defaults. + self.arguments.each { |arg, default| + arg = symbolize(arg) + unless args.include?(arg) + if defined? default and ! default.nil? + default = default.safeevaluate :scope => scope + args[arg] = default + #Puppet.debug "Got default %s for %s in %s" % + # [default.inspect, arg.inspect, @name.inspect] + else + parsefail "Must pass %s to %s of type %s" % + [arg,title,@classname] + end + end + } + end + + # Set each of the provided arguments as variables in the + # component's scope. + args.each { |arg,value| + unless validattr?(arg) + parsefail "%s does not accept attribute %s" % [@classname, arg] + end + + exceptwrap do + scope.setvar(arg.to_s,args[arg]) + end + } + + unless args.include? :title + scope.setvar("title",title) + end + + unless args.include? :name + scope.setvar("name",name) + end + + if self.code + return self.code.safeevaluate(:scope => scope) + else + return nil + end + end + + def initialize(hash = {}) + @arguments = nil + @parentclass = nil + super + + # Convert the arguments to a hash for ease of later use. + if @arguments + unless @arguments.is_a? Array + @arguments = [@arguments] + end + oldargs = @arguments + @arguments = {} + oldargs.each do |arg, val| + @arguments[arg] = val + end + else + @arguments = {} + end + + # Deal with metaparams in the argument list. + @arguments.each do |arg, defvalue| + next unless Puppet::Type.metaparamclass(arg) + if defvalue + warnonce "%s is a metaparam; this value will inherit to all contained elements" % arg + else + raise Puppet::ParseError, + "%s is a metaparameter; please choose another name" % + name + end + end + end + + def find_parentclass + @parser.findclass(namespace, parentclass) + end + + # Set our parent class, with a little check to avoid some potential + # weirdness. + def parentclass=(name) + if name == self.classname + parsefail "Parent classes must have dissimilar names" + end + + @parentclass = name + end + + # Hunt down our class object. + def parentobj + if @parentclass + # Cache our result, since it should never change. + unless defined?(@parentobj) + unless tmp = find_parentclass + parsefail "Could not find %s %s" % [self.class.name, @parentclass] + end + + if tmp == self + parsefail "Parent classes must have dissimilar names" + end + + @parentobj = tmp + end + @parentobj + else + nil + end + end + + # Create a new subscope in which to evaluate our code. + def subscope(scope, name = nil) + args = { + :type => self.classname, + :keyword => self.keyword, + :namespace => self.namespace + } + + args[:name] = name if name + scope = scope.newscope(args) + scope.source = self + + return scope + end + + def to_s + classname + end + + # Check whether a given argument is valid. Searches up through + # any parent classes that might exist. + def validattr?(param) + param = param.to_s + + if @arguments.include?(param) + # It's a valid arg for us + return true + elsif param == "name" + return true +# elsif defined? @parentclass and @parentclass +# # Else, check any existing parent +# if parent = @scope.lookuptype(@parentclass) and parent != [] +# return parent.validarg?(param) +# elsif builtin = Puppet::Type.type(@parentclass) +# return builtin.validattr?(param) +# else +# raise Puppet::Error, "Could not find parent class %s" % +# @parentclass +# end + elsif Puppet::Type.metaparam?(param) + return true + else + # Or just return false + return false + end + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 9b60c692f..f3b0602b1 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -1,80 +1,80 @@ -require 'puppet/parser/ast/component' +require 'puppet/parser/ast/definition' class Puppet::Parser::AST # The code associated with a class. This is different from components # in that each class is a singleton -- only one will exist for a given # node. - class HostClass < AST::Component + class HostClass < AST::Definition @name = :class # Are we a child of the passed class? Do a recursive search up our # parentage tree to figure it out. def child_of?(klass) return false unless self.parentclass if klass == self.parentobj return true else return self.parentobj.child_of?(klass) end end # Evaluate the code associated with this class. def evaluate(hash) scope = hash[:scope] args = hash[:arguments] # Verify that we haven't already been evaluated, and if we have been evaluated, # make sure that we match the class. if existing_scope = scope.class_scope(self) #if existing_scope.source.object_id == self.object_id Puppet.debug "%s class already evaluated" % @type return nil end pnames = nil if pklass = self.parentobj pklass.safeevaluate :scope => scope scope = parent_scope(scope, pklass) pnames = scope.namespaces end unless hash[:nosubscope] scope = subscope(scope) end if pnames pnames.each do |ns| scope.add_namespace(ns) end end # Set the class before we do anything else, so that it's set # during the evaluation and can be inspected. scope.setclass(self) # Now evaluate our code, yo. if self.code return self.code.evaluate(:scope => scope) else return nil end end def initialize(hash) @parentclass = nil super end def parent_scope(scope, klass) if s = scope.class_scope(klass) return s else raise Puppet::DevError, "Could not find scope for %s" % klass.fqname end end end end # $Id$ diff --git a/lib/puppet/parser/compile.rb b/lib/puppet/parser/compile.rb index 710f90273..7159947bf 100644 --- a/lib/puppet/parser/compile.rb +++ b/lib/puppet/parser/compile.rb @@ -1,559 +1,565 @@ # Created by Luke A. Kanies on 2007-08-13. # Copyright (c) 2007. All rights reserved. require 'puppet/external/gratr/digraph' require 'puppet/external/gratr/import' require 'puppet/external/gratr/dot' require 'puppet/node' require 'puppet/util/errors' # Maintain a graph of scopes, along with a bunch of data # about the individual configuration we're compiling. class Puppet::Parser::Compile include Puppet::Util include Puppet::Util::Errors attr_reader :topscope, :parser, :node, :facts, :collections attr_accessor :extraction_format attr_writer :ast_nodes # Add a collection to the global list. def add_collection(coll) @collections << coll end # Do we use nodes found in the code, vs. the external node sources? def ast_nodes? defined?(@ast_nodes) and @ast_nodes 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? or scope.nodescope? raise Puppet::ParseError, "Cannot have classes, nodes, or definitions with the same name" else raise Puppet::DevError, "Somehow evaluated the same class twice" end end @class_scopes[name] = scope tag(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 @class_scopes.keys.reject { |k| k == "" } end # Compile our configuration. This mostly revolves around finding and evaluating classes. # This is the main entry into our configuration. def compile # Set the client's parameters into the top scope. set_node_parameters() evaluate_main() evaluate_ast_node() - evaluate_classes() + evaluate_node_classes() evaluate_generators() fail_on_unevaluated() finish() if Puppet[:storeconfigs] store() end return extract() end # FIXME There are no tests for this. def delete_collection(coll) @collections.delete(coll) if @collections.include?(coll) end # FIXME There are no tests for this. def delete_resource(resource) @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) @resource_graph.remove_vertex!(resource) if @resource_graph.vertex?(resource) 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 each class in turn. If there are any classes we can't find, - # just tag the configuration and move on. - def evaluate_classes(classes = nil) - classes ||= node.classes + # Evaluate all of the classes specified by the node. + def evaluate_node_classes + evaluate_classes(@node.classes, @parser.findclass("", "")) + end + + # Evaluate each specified class in turn. If there are any classes we can't + # find, just tag the configuration 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, source) found = [] classes.each do |name| if klass = @parser.findclass("", name) # This will result in class_set getting called, which # will in turn result in tags. Yay. klass.safeevaluate(:scope => topscope) found << name else Puppet.info "Could not find class %s for %s" % [name, node.name] tag(name) end end found end # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) raise ArgumentError, "Invalid extraction format %s" % value end @extraction_format = value end # Return a resource by either its ref or its type and title. def findresource(string, name = nil) if name string = "%s[%s]" % [string.capitalize, name] end @resource_table[string] end # Set up our configuration. 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, "Compile objects do not accept %s" % param end end @extraction_format ||= :transportable initvars() 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[:compile] = 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 @resource_table.values end # Store a resource override. def store_override(override) override.override = true # If possible, merge the override in immediately. if resource = @resource_table[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 store_resource(scope, resource) # This might throw an exception verify_uniqueness(resource) # Store it in the global table. @resource_table[resource.ref] = 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. @resource_graph.add_edge!(scope, resource) 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 astnode = @parser.nodes["default"] end unless astnode raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") end astnode.safeevaluate :scope => topscope 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 @collections.each do |collection| if collection.evaluate found_something = true end 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 ary.each do |resource| resource.evaluate end # If we evaluated, let the loop know. return true 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 if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host configuration" end end end # Find and evaluate our main object, if possible. def evaluate_main if klass = @parser.findclass("", "") # Set the source, so objects can tell where they were defined. topscope.source = klass klass.safeevaluate :scope => topscope, :nosubscope => true end end # Turn our configuration graph into whatever the client is expecting. def extract send("extract_to_%s" % extraction_format) end # Create the traditional TransBuckets and TransObjects from our configuration # graph. This will hopefully be deprecated soon. def extract_to_transportable top = nil current = nil buckets = {} # I'm *sure* there's a simple way to do this using a breadth-first search # or something, but I couldn't come up with, and this is both fast # and simple, so I'm not going to worry about it too much. @scope_graph.vertices.each do |scope| # For each scope, we need to create a TransBucket, and then # put all of the scope's resources into that bucket, translating # each resource into a TransObject. # Unless the bucket's already been created, make it now and add # it to the cache. unless bucket = buckets[scope] bucket = buckets[scope] = scope.to_trans end # First add any contained scopes @scope_graph.adjacent(scope, :direction => :out).each do |vertex| # If there's not already a bucket, then create and cache it. unless child_bucket = buckets[vertex] child_bucket = buckets[vertex] = vertex.to_trans end bucket.push child_bucket end # Then add the resources. if @resource_graph.vertex?(scope) @resource_graph.adjacent(scope, :direction => :out).each do |vertex| # Some resources don't get translated, e.g., virtual resources. if obj = vertex.to_trans bucket.push obj end end end end # Retrive the bucket for the top-level scope and set the appropriate metadata. result = buckets[topscope] result.copy_type_and_name(topscope) unless classlist.empty? result.classes = classlist end # Clear the cache to encourage the GC buckets.clear return result end # Make sure the entire configuration 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 @resource_table.each { |name, resource| resource.finish if resource.respond_to?(:finish) } 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 table for all defined resources. @resource_table = {} # 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 list of tags we've generated; most class names. @tags = [] # Create our initial scope, our scope graph, and add the initial scope to the graph. @topscope = Puppet::Parser::Scope.new(:compile => self, :type => "main", :name => "top", :parser => self.parser) # For maintaining scope relationships. @scope_graph = GRATR::Digraph.new @scope_graph.add_vertex!(@topscope) # For maintaining the relationship between scopes and their resources. @resource_graph = GRATR::Digraph.new 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 configuration 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, @resource_table.values) 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 configuration 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 # Add a tag. def tag(*names) names.each do |name| name = name.to_s @tags << name unless @tags.include?(name) end nil end # Return the list of tags. def tags @tags.dup 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 = @resource_table.find_all do |name, object| ! object.builtin? and ! object.evaluated? end.collect { |name, object| object } if ary.empty? return nil else return ary end end # Verify that the given resource isn't defined elsewhere. def verify_uniqueness(resource) # Short-curcuit the common case, unless existing_resource = @resource_table[resource.ref] return true end if typeclass = Puppet::Type.type(resource.type) and ! typeclass.isomorphic? Puppet.info "Allowing duplicate %s" % typeclass.name return true end # Either it's a defined type, which are never # isomorphic, or it's a non-isomorphic type, so # we should throw an exception. msg = "Duplicate definition: %s is already defined" % resource.ref if existing_resource.file and existing_resource.line msg << " in file %s at line %s" % [existing_resource.file, existing_resource.line] end if resource.line or resource.file msg << "; cannot redefine" end raise Puppet::ParseError.new(msg) end end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 895b4f083..05d694310 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,307 +1,307 @@ # Grr require 'puppet/util/autoload' require 'puppet/parser/scope' module Puppet::Parser module Functions # A module for managing parser functions. Each specified function # becomes an instance method on the Scope class. class << self include Puppet::Util end def self.autoloader unless defined? @autoloader @autoloader = Puppet::Util::Autoload.new(self, "puppet/parser/functions", :wrap => false ) end @autoloader end # Create a new function type. def self.newfunction(name, options = {}, &block) @functions ||= {} name = symbolize(name) if @functions.include? name raise Puppet::DevError, "Function %s already defined" % name end # We want to use a separate, hidden module, because we don't want # people to be able to call them directly. unless defined? FCollection eval("module FCollection; end") end ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, "Invalid statement type %s" % ftype.inspect end fname = "function_" + name.to_s Puppet::Parser::Scope.send(:define_method, fname, &block) # Someday we'll support specifying an arity, but for now, nope #@functions[name] = {:arity => arity, :type => ftype} @functions[name] = {:type => ftype, :name => fname} if options[:doc] @functions[name][:doc] = options[:doc] end end # Determine if a given name is a function def self.function(name) name = symbolize(name) unless @functions.include? name autoloader.load(name) end if @functions.include? name return @functions[name][:name] else return false end end def self.functiondocs autoloader.loadall ret = "" @functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| #ret += "%s\n%s\n" % [name, hash[:type]] ret += "%s\n%s\n" % [name, "-" * name.to_s.length] if hash[:doc] ret += hash[:doc].gsub(/\n\s*/, ' ') else ret += "Undocumented.\n" end ret += "\n\n- **Type**: %s\n\n" % hash[:type] end return ret end def self.functions @functions.keys end # Determine if a given function returns a value or not. def self.rvalue?(name) name = symbolize(name) if @functions.include? name case @functions[name][:type] when :statement: return false when :rvalue: return true end else return false end end # Include the specified classes newfunction(:include, :doc => "Evaluate one or more classes.") do |vals| vals = [vals] unless vals.is_a?(Array) - klasses = compile.evaluate_classes(vals) + klasses = compile.evaluate_classes(vals, self) missing = vals.find_all do |klass| ! klasses.include?(klass) end unless missing.empty? # Throw an error if we didn't evaluate all of the classes. str = "Could not find class" if missing.length > 1 str += "es" end str += " " + missing.join(", ") if n = namespaces and ! n.empty? and n != [""] str += " in namespaces %s" % @namespaces.join(", ") end self.fail Puppet::ParseError, str end end # Tag the current scope with each passed name newfunction(:tag, :doc => "Add the specified tags to the containing class or definition. All contained objects will then acquire that tag, also. ") do |vals| self.tag(*vals) end # Test whether a given tag is set. This functions as a big OR -- if any of the # specified tags are unset, we return false. newfunction(:tagged, :type => :rvalue, :doc => "A boolean function that tells you whether the current container is tagged with the specified tags. The tags are ANDed, so that all of the specified tags must be included for the function to return true.") do |vals| classlist = compile.classlist retval = true vals.each do |val| unless classlist.include?(val) or self.tags.include?(val) retval = false break end end return retval end # Test whether a given class or definition is defined newfunction(:defined, :type => :rvalue, :doc => "Determine whether a given type is defined, either as a native type or a defined type, or whether a class is defined. This is useful for checking whether a class is defined and only including it if it is. This function can also test whether a resource has been defined, using resource references (e.g., ``if defined(File['/tmp/myfile'] { ... }``). This function is unfortunately dependent on the parse order of the configuration when testing whether a resource is defined.") do |vals| result = false vals.each do |val| case val when String: # For some reason, it doesn't want me to return from here. if Puppet::Type.type(val) or finddefine(val) or findclass(val) result = true break end when Puppet::Parser::Resource::Reference: if findresource(val.to_s) result = true break end else raise ArgumentError, "Invalid argument of type %s to 'defined'" % val.class end end result end newfunction(:fail, :doc => "Fail with a parse error.") do |vals| vals = vals.collect { |s| s.to_s }.join(" ") if vals.is_a? Array raise Puppet::ParseError, vals.to_s end # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end newfunction(:template, :type => :rvalue, :doc => "Evaluate a template and return its value. See `the templating docs`_ for more information. Note that if multiple templates are specified, their output is all concatenated and returned as the output of the function. .. _the templating docs: /trac/puppet/wiki/PuppetTemplating ") do |vals| require 'erb' vals.collect do |file| # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template %s" % file wrapper = Puppet::Parser::TemplateWrapper.new(self, file) begin wrapper.result() rescue => detail raise Puppet::ParseError, "Failed to parse template %s: %s" % [file, detail] end end.join("") end # This is just syntactic sugar for a collection, although it will generally # be a good bit faster. newfunction(:realize, :doc => "Make a virtual object real. This is useful when you want to know the name of the virtual object and don't want to bother with a full collection. It is slightly faster than a collection, and, of course, is a bit shorter. You must pass the object using a reference; e.g.: ``realize User[luke]``." ) do |vals| coll = Puppet::Parser::Collector.new(self, :nomatter, nil, nil, :virtual) vals = [vals] unless vals.is_a?(Array) coll.resources = vals compile.add_collection(coll) end newfunction(:search, :doc => "Add another namespace for this class to search. This allows you to create classes with sets of definitions and add those classes to another class's search path.") do |vals| vals.each do |val| add_namespace(val) end end newfunction(:file, :type => :rvalue, :doc => "Return the contents of a file. Multiple files can be passed, and the first file that exists will be read in.") do |vals| ret = nil vals.each do |file| unless file =~ /^#{File::SEPARATOR}/ raise Puppet::ParseError, "Files must be fully qualified" end if FileTest.exists?(file) ret = File.read(file) break end end if ret ret else raise Puppet::ParseError, "Could not find any files from %s" % vals.join(", ") end end newfunction(:generate, :type => :rvalue, :doc => "Calls an external command and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| unless args[0] =~ /^#{File::SEPARATOR}/ raise Puppet::ParseError, "Generators must be fully qualified" end unless args[0] =~ /^[-#{File::SEPARATOR}\w.]+$/ raise Puppet::ParseError, "Generators can only contain alphanumerics, file separators, and dashes" end if args[0] =~ /\.\./ raise Puppet::ParseError, "Can not use generators with '..' in them." end begin output = Puppet::Util.execute(args) rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, "Failed to execute generator %s: %s" % [args[0], detail] end output end end end # $Id$ diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 660fa8169..be1d73047 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -1,454 +1,454 @@ # I pulled this into a separate file, because I got # tired of rebuilding the parser.rb file all the time. class Puppet::Parser::Parser require 'puppet/parser/functions' ASTSet = Struct.new(:classes, :definitions, :nodes) # Define an accessor method for each table. We hide the existence of # the struct. [:classes, :definitions, :nodes].each do |name| define_method(name) do @astset.send(name) end end AST = Puppet::Parser::AST attr_reader :version, :environment attr_accessor :files # Add context to a message; useful for error messages and such. def addcontext(message, obj = nil) obj ||= @lexer message += " on line %s" % obj.line if file = obj.file message += " in file %s" % file end return message end # Create an AST array out of all of the args def aryfy(*args) if args[0].instance_of?(AST::ASTArray) result = args.shift args.each { |arg| result.push arg } else result = ast AST::ASTArray, :children => args end return result end # Create an AST object, and automatically add the file and line information if # available. def ast(klass, hash = nil) hash ||= {} unless hash.include?(:line) hash[:line] = @lexer.line end unless hash.include?(:file) if file = @lexer.file hash[:file] = file end end return klass.new(hash) end # The fully qualifed name, with the full namespace. def classname(name) [@lexer.namespace, name].join("::").sub(/^::/, '') end def clear initvars end # Raise a Parse error. def error(message) if brace = @lexer.expected message += "; expected '%s'" end except = Puppet::ParseError.new(message) except.line = @lexer.line if @lexer.file except.file = @lexer.file end raise except end def file @lexer.file end def file=(file) unless FileTest.exists?(file) unless file =~ /\.pp$/ file = file + ".pp" end unless FileTest.exists?(file) raise Puppet::Error, "Could not find file %s" % file end end if @files.detect { |f| f.file == file } raise Puppet::AlreadyImportedError.new("Import loop detected") else @files << Puppet::Util::LoadedFile.new(file) @lexer.file = file end end # Find a class definition, relative to the current namespace. def findclass(namespace, name) fqfind namespace, name, classes end # Find a component definition, relative to the current namespace. def finddefine(namespace, name) fqfind namespace, name, definitions end # This is only used when nodes are looking up the code for their # parent nodes. def findnode(name) fqfind "", name, nodes end # The recursive method used to actually look these objects up. def fqfind(namespace, name, table) namespace = namespace.downcase name = name.downcase if name =~ /^::/ or namespace == "" classname = name.sub(/^::/, '') unless table[classname] self.load(classname) end return table[classname] end ary = namespace.split("::") while ary.length > 0 newname = (ary + [name]).join("::").sub(/^::/, '') if obj = table[newname] or (self.load(newname) and obj = table[newname]) return obj end # Delete the second to last object, which reduces our namespace by one. ary.pop end # If we've gotten to this point without finding it, see if the name # exists at the top namespace if obj = table[name] or (self.load(name) and obj = table[name]) return obj end return nil end # Import our files. def import(file) if Puppet[:ignoreimport] return AST::ASTArray.new(:children => []) end # use a path relative to the file doing the importing if @lexer.file dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '') else dir = "." end if dir == "" dir = "." end result = ast AST::ASTArray # We can't interpolate at this point since we don't have any # scopes set up. Warn the user if they use a variable reference pat = file if pat.index("$") Puppet.warning( "The import of #{pat} contains a variable reference;" + " variables are not interpolated for imports " + "in file #{@lexer.file} at line #{@lexer.line}" ) end files = Puppet::Module::find_manifests(pat, :cwd => dir) if files.size == 0 raise Puppet::ImportError.new("No file(s) found for import " + "of '#{pat}'") end files.collect { |file| parser = Puppet::Parser::Parser.new(:astset => @astset, :environment => @environment) parser.files = self.files Puppet.debug("importing '%s'" % file) unless file =~ /^#{File::SEPARATOR}/ file = File.join(dir, file) end begin parser.file = file rescue Puppet::AlreadyImportedError # This file has already been imported to just move on next end # This will normally add code to the 'main' class. parser.parse } end def initialize(options = {}) @astset = options[:astset] || ASTSet.new({}, {}, {}) @environment = options[:environment] initvars() end # Initialize or reset all of our variables. def initvars @lexer = Puppet::Parser::Lexer.new() @files = [] @loaded = [] end # Try to load a class, since we could not find it. def load(classname) return false if classname == "" filename = classname.gsub("::", File::SEPARATOR) loaded = false # First try to load the top-level module mod = filename.scan(/^[\w-]+/).shift unless @loaded.include?(mod) @loaded << mod begin import(mod) Puppet.info "Autoloaded module %s" % mod loaded = true rescue Puppet::ImportError => detail # We couldn't load the module end end unless filename == mod and ! @loaded.include?(mod) @loaded << mod # Then the individual file begin import(filename) Puppet.info "Autoloaded file %s from module %s" % [filename, mod] loaded = true rescue Puppet::ImportError => detail # We couldn't load the file end end return loaded end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end # Create a new class, or merge with an existing class. def newclass(name, options = {}) name = name.downcase if definitions.include?(name) raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name end code = options[:code] parent = options[:parent] # If the class is already defined, then add code to it. if other = @astset.classes[name] # Make sure the parents match if parent and other.parentclass and (parent != other.parentclass) error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) end # This might be dangerous... if parent and ! other.parentclass other.parentclass = parent end # This might just be an empty, stub class. if code tmp = name if tmp == "" tmp = "main" end Puppet.debug addcontext("Adding code to %s" % tmp) # Else, add our code to it. if other.code and code other.code.children += code.children else other.code ||= code end end else # Define it anew. # Note we're doing something somewhat weird here -- we're setting # the class's namespace to its fully qualified name. This means # anything inside that class starts looking in that namespace first. args = {:namespace => name, :classname => name, :parser => self} args[:code] = code if code args[:parentclass] = parent if parent @astset.classes[name] = ast AST::HostClass, args end return @astset.classes[name] end # Create a new definition. def newdefine(name, options = {}) name = name.downcase if @astset.classes.include?(name) raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name end # Make sure our definition doesn't already exist if other = @astset.definitions[name] error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) end ns, whatever = namesplit(name) args = { :namespace => ns, :arguments => options[:arguments], :code => options[:code], :parser => self, :classname => name } [:code, :arguments].each do |param| args[param] = options[param] if options[param] end - @astset.definitions[name] = ast AST::Component, args + @astset.definitions[name] = ast AST::Definition, args end # Create a new node. Nodes are special, because they're stored in a global # table, not according to namespaces. def newnode(names, options = {}) names = [names] unless names.instance_of?(Array) names.collect do |name| name = name.to_s.downcase if other = @astset.nodes[name] error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line]) end name = name.to_s if name.is_a?(Symbol) args = { :name => name, :parser => self } if options[:code] args[:code] = options[:code] end if options[:parent] args[:parentclass] = options[:parent] end @astset.nodes[name] = ast(AST::Node, args) @astset.nodes[name].classname = name @astset.nodes[name] end end def on_error(token,value,stack) #on '%s' at '%s' in\n'%s'" % [token,value,stack] #error = "line %s: parse error after '%s'" % # [@lexer.line,@lexer.last] error = "Syntax error at '%s'" % [value] if brace = @lexer.expected error += "; expected '%s'" % brace end except = Puppet::ParseError.new(error) except.line = @lexer.line if @lexer.file except.file = @lexer.file end raise except end # how should I do error handling here? def parse(string = nil) if string self.string = string end begin main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) error.line = @lexer.line error.file = @lexer.file error.set_backtrace except.backtrace raise error rescue Puppet::ParseError => except except.line ||= @lexer.line except.file ||= @lexer.file raise except rescue Puppet::Error => except # and this is a framework error except.line ||= @lexer.line except.file ||= @lexer.file raise except rescue Puppet::DevError => except except.line ||= @lexer.line except.file ||= @lexer.file raise except rescue => except error = Puppet::DevError.new(except.message) error.line = @lexer.line error.file = @lexer.file error.set_backtrace except.backtrace raise error end if main # Store the results as the top-level class. newclass("", :code => main) end @version = Time.now.to_i return @astset ensure @lexer.clear end # See if any of the files have changed. def reparse? if file = @files.detect { |file| file.changed? } return file.stamp else return false end end def string=(string) @lexer.string = string end # Add a new file to be checked when we're checking to see if we should be # reparsed. This is basically only used by the TemplateWrapper to let the # parser know about templates that should be parsed. def watch_file(*files) files.each do |file| unless file.is_a? Puppet::Util::LoadedFile file = Puppet::Util::LoadedFile.new(file) end @files << file end end end diff --git a/spec/unit/parser/compile.rb b/spec/unit/parser/compile.rb new file mode 100755 index 000000000..d22b63545 --- /dev/null +++ b/spec/unit/parser/compile.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Parser::Compile, " when compiling" do + before do + @node = mock 'node' + @parser = mock 'parser' + @compile = Puppet::Parser::Compile.new(@node, @parser) + end + + 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| @compile.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) + @compile.compile + @compile.topscope.lookupvar("a").should == "b" + @compile.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' + one.expects(:safeevaluate).with(:scope => @compile.topscope) + three = stub 'three' + three.expects(:safeevaluate).with(:scope => @compile.topscope) + @node.stubs(:name).returns("whatever") + @compile.parser.expects(:findclass).with("", "").returns(main) + @compile.parser.expects(:findclass).with("", "one").returns(one) + @compile.parser.expects(:findclass).with("", "two").returns(nil) + @compile.parser.expects(:findclass).with("", "three").returns(three) + @compile.parser.expects(:findclass).with("", "four").returns(nil) + @node.stubs(:classes).returns(classes) + compile_stub(:evaluate_node_classes) + @compile.compile + end +end diff --git a/test/language/ast/definition.rb b/test/language/ast/definition.rb new file mode 100755 index 000000000..1bbc1a099 --- /dev/null +++ b/test/language/ast/definition.rb @@ -0,0 +1,156 @@ +#!/usr/bin/env ruby +# +# Created by Luke A. Kanies on 2006-02-20. +# Copyright (c) 2006. All rights reserved. + +$:.unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'puppettest' +require 'mocha' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' + +class TestASTDefinition < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + AST = Puppet::Parser::AST + + def test_initialize + parser = mkparser + + # Create a new definition + klass = parser.newdefine "yayness", + :arguments => [["owner", stringobj("nobody")], %w{mode}], + :code => AST::ASTArray.new( + :children => [resourcedef("file", "/tmp/$name", + "owner" => varref("owner"), "mode" => varref("mode"))] + ) + + # Test validattr? a couple different ways + [:owner, "owner", :schedule, "schedule"].each do |var| + assert(klass.validattr?(var), "%s was not considered valid" % var.inspect) + end + + [:random, "random"].each do |var| + assert(! klass.validattr?(var), "%s was considered valid" % var.inspect) + end + + end + + def test_evaluate + parser = mkparser + config = mkconfig + scope = config.topscope + klass = parser.newdefine "yayness", + :arguments => [["owner", stringobj("nobody")], %w{mode}], + :code => AST::ASTArray.new( + :children => [resourcedef("file", "/tmp/$name", + "owner" => varref("owner"), "mode" => varref("mode"))] + ) + + # Now call it a couple of times + # First try it without a required param + assert_raise(Puppet::ParseError, "Did not fail when a required parameter was not provided") do + klass.evaluate_resource(:scope => scope, + :name => "bad", + :arguments => {"owner" => "nobody"} + ) + end + + # And make sure it didn't create the file + assert_nil(config.findresource("File[/tmp/bad]"), + "Made file with invalid params") + + assert_nothing_raised do + klass.evaluate_resource(:scope => scope, + :title => "first", + :arguments => {"mode" => "755"} + ) + end + + firstobj = config.findresource("File[/tmp/first]") + assert(firstobj, "Did not create /tmp/first obj") + + assert_equal("file", firstobj.type) + assert_equal("/tmp/first", firstobj.title) + assert_equal("nobody", firstobj[:owner]) + assert_equal("755", firstobj[:mode]) + + # Make sure we can't evaluate it with the same args + assert_raise(Puppet::ParseError) do + klass.evaluate_resource(:scope => scope, + :title => "first", + :arguments => {"mode" => "755"} + ) + end + + # Now create another with different args + assert_nothing_raised do + klass.evaluate_resource(:scope => scope, + :title => "second", + :arguments => {"mode" => "755", "owner" => "daemon"} + ) + end + + secondobj = config.findresource("File[/tmp/second]") + assert(secondobj, "Did not create /tmp/second obj") + + assert_equal("file", secondobj.type) + assert_equal("/tmp/second", secondobj.title) + assert_equal("daemon", secondobj[:owner]) + assert_equal("755", secondobj[:mode]) + end + + # #539 - definitions should support both names and titles + def test_names_and_titles + parser, scope, source = mkclassframing + + [ + {:name => "one", :title => "two"}, + {:title => "mytitle"}, + ].each_with_index do |hash, i| + + # Create a definition that uses both name and title + klass = parser.newdefine "yayness%s" % i + + subscope = klass.subscope(scope, "yayness%s" % i) + + klass.expects(:subscope).returns(subscope) + + args = {:title => hash[:title]} + if hash[:name] + args[:arguments] = {:name => hash[:name]} + end + args[:scope] = scope + assert_nothing_raised("Could not evaluate definition with %s" % hash.inspect) do + klass.evaluate_resource(args) + end + + name = hash[:name] || hash[:title] + title = hash[:title] + args[:name] ||= name + + assert_equal(name, subscope.lookupvar("name"), + "Name did not get set correctly") + assert_equal(title, subscope.lookupvar("title"), + "title did not get set correctly") + + [:name, :title].each do |param| + val = args[param] + assert(subscope.tags.include?(val), + "Scope was not tagged with %s" % val) + end + end + end + + # Testing the root cause of #615. We should be using the fqname for the type, instead + # of just the short name. + def test_fully_qualified_types + parser = mkparser + klass = parser.newclass("one::two") + + assert_equal("one::two", klass.classname, "Class did not get fully qualified class name") + end +end +# $Id$ diff --git a/test/language/ast/hostclass.rb b/test/language/ast/hostclass.rb index a91d4bb97..1d2121dad 100755 --- a/test/language/ast/hostclass.rb +++ b/test/language/ast/hostclass.rb @@ -1,166 +1,166 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-02-20. # Copyright (c) 2006. All rights reserved. $:.unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'mocha' class TestASTHostClass < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST def test_hostclass scope = mkscope parser = scope.compile.parser # Create the class we're testing, first with no parent klass = parser.newclass "first", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp", "owner" => "nobody", "mode" => "755")] ) assert_nothing_raised do klass.evaluate(:scope => scope) end # Then try it again assert_nothing_raised do klass.evaluate(:scope => scope) end assert(scope.class_scope(klass), "Class was not considered evaluated") tmp = scope.findresource("File[/tmp]") assert(tmp, "Could not find file /tmp") assert_equal("nobody", tmp[:owner]) assert_equal("755", tmp[:mode]) # Now create a couple more classes. newbase = parser.newclass "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/other", "owner" => "nobody", "mode" => "644")] ) newsub = parser.newclass "newsub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/yay", "owner" => "nobody", "mode" => "755"), resourceoverride("file", "/tmp/other", "owner" => "daemon") ] ) # Override a different variable in the top scope. moresub = parser.newclass "moresub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourceoverride("file", "/tmp/other", "mode" => "755")] ) assert_nothing_raised do newsub.evaluate(:scope => scope) end assert_nothing_raised do moresub.evaluate(:scope => scope) end assert(scope.class_scope(newbase), "Did not eval newbase") assert(scope.class_scope(newsub), "Did not eval newsub") yay = scope.findresource("File[/tmp/yay]") assert(yay, "Did not find file /tmp/yay") assert_equal("nobody", yay[:owner]) assert_equal("755", yay[:mode]) other = scope.findresource("File[/tmp/other]") assert(other, "Did not find file /tmp/other") assert_equal("daemon", other[:owner]) assert_equal("755", other[:mode]) end # Make sure that classes set their namespaces to themselves. This # way they start looking for definitions in their own namespace. def test_hostclass_namespace scope = mkscope parser = scope.compile.parser # Create a new class klass = nil assert_nothing_raised do klass = parser.newclass "funtest" end # Now define a definition in that namespace define = nil assert_nothing_raised do define = parser.newdefine "funtest::mydefine" end assert_equal("funtest", klass.namespace, "component namespace was not set in the class") assert_equal("funtest", define.namespace, "component namespace was not set in the definition") newscope = klass.subscope(scope) assert_equal(["funtest"], newscope.namespaces, "Scope did not inherit namespace") # Now make sure we can find the define assert(newscope.finddefine("mydefine"), "Could not find definition in my enclosing class") end # Make sure that our scope is a subscope of the parentclass's scope. # At the same time, make sure definitions in the parent class can be # found within the subclass (#517). def test_parent_scope_from_parentclass scope = mkscope parser = scope.compile.parser parser.newclass("base") fun = parser.newdefine("base::fun") parser.newclass("middle", :parent => "base") parser.newclass("sub", :parent => "middle") scope = mkscope :parser => parser ret = nil assert_nothing_raised do - ret = scope.compile.evaluate_classes(["sub"]) + ret = scope.compile.evaluate_classes(["sub"], scope) end subscope = scope.class_scope(scope.findclass("sub")) assert(subscope, "could not find sub scope") mscope = scope.class_scope(scope.findclass("middle")) assert(mscope, "could not find middle scope") pscope = scope.class_scope(scope.findclass("base")) assert(pscope, "could not find parent scope") assert(pscope == mscope.parent, "parent scope of middle was not set correctly") assert(mscope == subscope.parent, "parent scope of sub was not set correctly") result = mscope.finddefine("fun") assert(result, "could not find parent-defined definition from middle") assert(fun == result, "found incorrect parent-defined definition from middle") result = subscope.finddefine("fun") assert(result, "could not find parent-defined definition from sub") assert(fun == result, "found incorrect parent-defined definition from sub") end end # $Id$ diff --git a/test/language/compile.rb b/test/language/compile.rb index 90cbc292e..5fde2500e 100755 --- a/test/language/compile.rb +++ b/test/language/compile.rb @@ -1,755 +1,765 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' require 'puppettest/parsertesting' require 'puppet/parser/compile' # Test our compile object. class TestCompile < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting Compile = Puppet::Parser::Compile Scope = Puppet::Parser::Scope Node = Puppet::Network::Handler.handler(:node) SimpleNode = Puppet::Node def mknode(name = "foo") @node = SimpleNode.new(name) end def mkparser # This should mock an interpreter @parser = mock 'parser' end def mkconfig(options = {}) if node = options[:node] options.delete(:node) else node = mknode end @config = Compile.new(node, mkparser, options) end def test_initialize config = nil assert_nothing_raised("Could not init config with all required options") do config = Compile.new("foo", "parser") end assert_equal("foo", config.node, "Did not set node correctly") assert_equal("parser", config.parser, "Did not set parser correctly") # We're not testing here whether we call initvars, because it's too difficult to # mock. # Now try it with some options assert_nothing_raised("Could not init config with extra options") do config = Compile.new("foo", "parser", :ast_nodes => false) end assert_equal(false, config.ast_nodes?, "Did not set ast_nodes? correctly") end def test_initvars config = mkconfig [:class_scopes, :resource_table, :exported_resources, :resource_overrides].each do |table| assert_instance_of(Hash, config.send(:instance_variable_get, "@#{table}"), "Did not set %s table correctly" % table) end assert_instance_of(Scope, config.topscope, "Did not create a topscope") graph = config.instance_variable_get("@scope_graph") assert_instance_of(GRATR::Digraph, graph, "Did not create scope graph") assert(graph.vertex?(config.topscope), "Did not add top scope as a vertex in the graph") end # Make sure we store and can retrieve references to classes and their scopes. def test_class_set_and_class_scope klass = mock 'ast_class' klass.expects(:classname).returns("myname") config = mkconfig config.expects(:tag).with("myname") assert_nothing_raised("Could not set class") do config.class_set "myname", "myscope" end # First try to retrieve it by name. assert_equal("myscope", config.class_scope("myname"), "Could not retrieve class scope by name") # Then by object assert_equal("myscope", config.class_scope(klass), "Could not retrieve class scope by object") end def test_classlist config = mkconfig config.class_set "", "empty" config.class_set "one", "yep" config.class_set "two", "nope" # Make sure our class list is correct assert_equal(%w{one two}.sort, config.classlist.sort, "Did not get correct class list") end # Make sure collections get added to our internal array def test_add_collection config = mkconfig assert_nothing_raised("Could not add collection") do config.add_collection "nope" end assert_equal(%w{nope}, config.instance_variable_get("@collections"), "Did not add collection") end # Make sure we create a graph of scopes. def test_newscope config = mkconfig graph = config.instance_variable_get("@scope_graph") assert_instance_of(Scope, config.topscope, "Did not create top scope") assert_instance_of(GRATR::Digraph, graph, "Did not create graph") assert(graph.vertex?(config.topscope), "The top scope is not a vertex in the graph") # Now that we've got the top scope, create a new, subscope subscope = nil assert_nothing_raised("Could not create subscope") do subscope = config.newscope(config.topscope) end assert_instance_of(Scope, subscope, "Did not create subscope") assert(graph.edge?(config.topscope, subscope), "An edge between top scope and subscope was not added") # Make sure a scope can find its parent. assert(config.parent(subscope), "Could not look up parent scope on compile") assert_equal(config.topscope.object_id, config.parent(subscope).object_id, "Did not get correct parent scope from compile") assert_equal(config.topscope.object_id, subscope.parent.object_id, "Scope did not correctly retrieve its parent scope") # Now create another, this time specifying options another = nil assert_nothing_raised("Could not create subscope") do another = config.newscope(subscope, :name => "testing") end assert_equal("testing", another.name, "did not set scope option correctly") assert_instance_of(Scope, another, "Did not create second subscope") assert(graph.edge?(subscope, another), "An edge between parent scope and second subscope was not added") # Make sure it can find its parent. assert(config.parent(another), "Could not look up parent scope of second subscope on compile") assert_equal(subscope.object_id, config.parent(another).object_id, "Did not get correct parent scope of second subscope from compile") assert_equal(subscope.object_id, another.parent.object_id, "Second subscope did not correctly retrieve its parent scope") # And make sure both scopes show up in the right order in the search path assert_equal([another.object_id, subscope.object_id, config.topscope.object_id], another.scope_path.collect { |p| p.object_id }, "Did not get correct scope path") end # The heart of the action. def test_compile config = mkconfig - [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| + [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| config.expects(method) end config.expects(:extract).returns(:config) assert_equal(:config, config.compile, "Did not return the results of the extraction") end # Test setting the node's parameters into the top scope. def test_set_node_parameters config = mkconfig @node.parameters = {"a" => "b", "c" => "d"} scope = config.topscope @node.parameters.each do |param, value| scope.expects(:setvar).with(param, value) end assert_nothing_raised("Could not call 'set_node_parameters'") do config.send(:set_node_parameters) end end # Test that we can evaluate the main class, which is the one named "" in namespace # "". def test_evaluate_main config = mkconfig main = mock 'main_class' config.topscope.expects(:source=).with(main) main.expects(:safeevaluate).with(:scope => config.topscope, :nosubscope => true) @parser.expects(:findclass).with("", "").returns(main) assert_nothing_raised("Could not call evaluate_main") do config.send(:evaluate_main) end end # Make sure we either don't look for nodes, or that we find and evaluate the right object. def test_evaluate_ast_node # First try it with ast_nodes disabled config = mkconfig :ast_nodes => false config.expects(:ast_nodes?).returns(false) config.parser.expects(:nodes).never assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do config.send(:evaluate_ast_node) end # Now try it with them enabled, but no node found. nodes = mock 'node_hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(4) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(nil) # It should check this last, of course. nodes.expects(:[]).with("default").returns(nil) # And make sure the lack of a node throws an exception assert_raise(Puppet::ParseError, "Did not fail when we couldn't find an ast node") do config.send(:evaluate_ast_node) end # Finally, make sure it works dandily when we have a node nodes = mock 'hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(3) node = mock 'node' node.expects(:safeevaluate).with(:scope => config.topscope) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(node) nodes.expects(:[]).with("default").never # And make sure the lack of a node throws an exception assert_nothing_raised("Failed when a node was found") do config.send(:evaluate_ast_node) end # Lastly, check when we actually find the default. nodes = mock 'hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(4) node = mock 'node' node.expects(:safeevaluate).with(:scope => config.topscope) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(nil) nodes.expects(:[]).with("default").returns(node) # And make sure the lack of a node throws an exception assert_nothing_raised("Failed when a node was found") do config.send(:evaluate_ast_node) end end # Make sure our config object handles tags appropriately. def test_tags config = mkconfig config.send(:tag, "one") assert_equal(%w{one}, config.send(:tags), "Did not add tag") config.send(:tag, "two", "three") assert_equal(%w{one two three}, config.send(:tags), "Did not add new tags") config.send(:tag, "two") assert_equal(%w{one two three}, config.send(:tags), "Allowed duplicate tag") end + def test_evaluate_node_classes + config = mkconfig + main = mock 'main' + config.parser.expects(:findclass).with("", "").returns(main) + @node.classes = %w{one two three four} + config.expects(:evaluate_classes).with(%w{one two three four}, main) + assert_nothing_raised("could not call evaluate_node_classes") do + config.send(:evaluate_node_classes) + end + end + def test_evaluate_classes config = mkconfig classes = { "one" => mock('class one'), "three" => mock('class three') } classes.each do |name, obj| config.parser.expects(:findclass).with("", name).returns(obj) obj.expects(:safeevaluate).with(:scope => config.topscope) end %w{two four}.each do |name| config.parser.expects(:findclass).with("", name).returns(nil) end config.expects(:tag).with("two") config.expects(:tag).with("four") - @node.classes = %w{one two three four} result = nil - assert_nothing_raised("could not call evaluate_classes") do - result = config.send(:evaluate_classes) + assert_nothing_raised("could not call evaluate_node_classes") do + result = config.send(:evaluate_classes, %w{one two three four}, config.topscope) end assert_equal(%w{one three}, result, "Did not return the list of evaluated classes") end def test_evaluate_collections config = mkconfig colls = [] # Make sure we return false when there's nothing there. assert(! config.send(:evaluate_collections), "Returned true when there were no collections") # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } config.instance_variable_set("@collections", colls) assert(! config.send(:evaluate_collections), "Returned true when collections both evaluated nothing") # Now have one of the colls evaluate colls.clear colls << mock("coll1-one-true") colls << mock("coll2-one-true") colls[0].expects(:evaluate).returns(true) colls[1].expects(:evaluate).returns(false) assert(config.send(:evaluate_collections), "Did not return true when one collection evaluated true") # And have them both eval true colls.clear colls << mock("coll1-both-true") colls << mock("coll2-both-true") colls[0].expects(:evaluate).returns(true) colls[1].expects(:evaluate).returns(true) assert(config.send(:evaluate_collections), "Did not return true when both collections evaluated true") end def test_unevaluated_resources config = mkconfig resources = {} config.instance_variable_set("@resource_table", resources) # First test it when the table is empty assert_nil(config.send(:unevaluated_resources), "Somehow found unevaluated resources in an empty table") # Then add a builtin resources resources["one"] = mock("builtin only") resources["one"].expects(:builtin?).returns(true) assert_nil(config.send(:unevaluated_resources), "Considered a builtin resource unevaluated") # And do both builtin and non-builtin but already evaluated resources.clear resources["one"] = mock("builtin (with eval)") resources["one"].expects(:builtin?).returns(true) resources["two"] = mock("evaled (with builtin)") resources["two"].expects(:builtin?).returns(false) resources["two"].expects(:evaluated?).returns(true) assert_nil(config.send(:unevaluated_resources), "Considered either a builtin or evaluated resource unevaluated") # Now a single unevaluated resource. resources.clear resources["one"] = mock("unevaluated") resources["one"].expects(:builtin?).returns(false) resources["one"].expects(:evaluated?).returns(false) assert_equal([resources["one"]], config.send(:unevaluated_resources), "Did not find unevaluated resource") # With two uneval'ed resources, and an eval'ed one thrown in resources.clear resources["one"] = mock("unevaluated one") resources["one"].expects(:builtin?).returns(false) resources["one"].expects(:evaluated?).returns(false) resources["two"] = mock("unevaluated two") resources["two"].expects(:builtin?).returns(false) resources["two"].expects(:evaluated?).returns(false) resources["three"] = mock("evaluated") resources["three"].expects(:builtin?).returns(false) resources["three"].expects(:evaluated?).returns(true) result = config.send(:unevaluated_resources) %w{one two}.each do |name| assert(result.include?(resources[name]), "Did not find %s in the unevaluated list" % name) end end def test_evaluate_definitions # First try the case where there's nothing to return config = mkconfig config.expects(:unevaluated_resources).returns(nil) assert_nothing_raised("Could not test for unevaluated resources") do assert(! config.send(:evaluate_definitions), "evaluate_definitions returned true when no resources were evaluated") end # Now try it with resources left to evaluate resources = [] res1 = mock("resource1") res1.expects(:evaluate) res2 = mock("resource2") res2.expects(:evaluate) resources << res1 << res2 config = mkconfig config.expects(:unevaluated_resources).returns(resources) assert_nothing_raised("Could not test for unevaluated resources") do assert(config.send(:evaluate_definitions), "evaluate_definitions returned false when resources were evaluated") end end def test_evaluate_generators # First try the case where we have nothing to do config = mkconfig config.expects(:evaluate_definitions).returns(false) config.expects(:evaluate_collections).returns(false) assert_nothing_raised("Could not call :eval_iterate") do config.send(:evaluate_generators) end # FIXME I could not get this test to work, but the code is short # enough that I'm ok with it. # It's important that collections are evaluated before definitions, # so make sure that's the case by verifying that collections get tested # twice but definitions only once. #config = mkconfig #config.expects(:evaluate_collections).returns(true).returns(false) #config.expects(:evaluate_definitions).returns(false) #config.send(:eval_iterate) end def test_store config = mkconfig Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) node = mock 'node' resource_table = mock 'resources' resource_table.expects(:values).returns(:resources) config.instance_variable_set("@node", node) config.instance_variable_set("@resource_table", resource_table) config.expects(:store_to_active_record).with(node, :resources) config.send(:store) end def test_store_to_active_record config = mkconfig node = mock 'node' node.expects(:name).returns("myname") Puppet::Rails::Host.stubs(:transaction).yields Puppet::Rails::Host.expects(:store).with(node, :resources) config.send(:store_to_active_record, node, :resources) end # Make sure that 'finish' gets called on all of our resources. def test_finish config = mkconfig table = config.instance_variable_get("@resource_table") # Add a resource that does respond to :finish yep = mock("finisher") yep.expects(:respond_to?).with(:finish).returns(true) yep.expects(:finish) table["yep"] = yep # And one that does not dnf = mock("dnf") dnf.expects(:respond_to?).with(:finish).returns(false) table["dnf"] = dnf config.send(:finish) end def test_extract config = mkconfig config.expects(:extraction_format).returns(:whatever) config.expects(:extract_to_whatever).returns(:result) assert_equal(:result, config.send(:extract), "Did not return extraction result as the method result") end # We want to make sure that the scope and resource graphs translate correctly def test_extract_to_transportable_simple # Start with a really simple graph -- one scope, one resource. config = mkconfig resources = config.instance_variable_get("@resource_graph") scopes = config.instance_variable_get("@scope_graph") # Get rid of the topscope scopes.vertices.each { |v| scopes.remove_vertex!(v) } bucket = [] scope = mock("scope") bucket.expects(:copy_type_and_name).with(scope) scope.expects(:to_trans).returns(bucket) scopes.add_vertex! scope # The topscope is the key to picking out the top of the graph. config.instance_variable_set("@topscope", scope) resource = mock "resource" resource.expects(:to_trans).returns(:resource) resources.add_edge! scope, resource result = nil assert_nothing_raised("Could not extract transportable compile") do result = config.send :extract_to_transportable end assert_equal([:resource], result, "Did not translate simple compile correctly") end def test_extract_to_transportable_complex # Now try it with a more complicated graph -- a three tier graph, each tier # having a scope and a resource. config = mkconfig resources = config.instance_variable_get("@resource_graph") scopes = config.instance_variable_get("@scope_graph") # Get rid of the topscope scopes.vertices.each { |v| scopes.remove_vertex!(v) } fakebucket = Class.new(Array) do attr_accessor :name def initialize(n) @name = n end end # Create our scopes. top = mock("top") topbucket = fakebucket.new "top" topbucket.expects(:copy_type_and_name).with(top) top.stubs(:copy_type_and_name) top.expects(:to_trans).returns(topbucket) # The topscope is the key to picking out the top of the graph. config.instance_variable_set("@topscope", top) middle = mock("middle") middle.expects(:to_trans).returns(fakebucket.new("middle")) scopes.add_edge! top, middle bottom = mock("bottom") bottom.expects(:to_trans).returns(fakebucket.new("bottom")) scopes.add_edge! middle, bottom topres = mock "topres" topres.expects(:to_trans).returns(:topres) resources.add_edge! top, topres midres = mock "midres" midres.expects(:to_trans).returns(:midres) resources.add_edge! middle, midres botres = mock "botres" botres.expects(:to_trans).returns(:botres) resources.add_edge! bottom, botres result = nil assert_nothing_raised("Could not extract transportable compile") do result = config.send :extract_to_transportable end assert_equal([[[:botres], :midres], :topres], result, "Did not translate medium compile correctly") end def test_verify_uniqueness config = mkconfig resources = config.instance_variable_get("@resource_table") resource = mock("noconflict") resource.expects(:ref).returns("File[yay]") assert_nothing_raised("Raised an exception when there should have been no conflict") do config.send(:verify_uniqueness, resource) end # Now try the case where our type is isomorphic resources["thing"] = true isoconflict = mock("isoconflict") isoconflict.expects(:ref).returns("thing") isoconflict.expects(:type).returns("testtype") faketype = mock("faketype") faketype.expects(:isomorphic?).returns(false) faketype.expects(:name).returns("whatever") Puppet::Type.expects(:type).with("testtype").returns(faketype) assert_nothing_raised("Raised an exception when was a conflict in non-isomorphic types") do config.send(:verify_uniqueness, isoconflict) end # Now test for when we actually have an exception initial = mock("initial") resources["thing"] = initial initial.expects(:file).returns(false) conflict = mock("conflict") conflict.expects(:ref).returns("thing").times(2) conflict.expects(:type).returns("conflict") conflict.expects(:file).returns(false) conflict.expects(:line).returns(false) faketype = mock("faketype") faketype.expects(:isomorphic?).returns(true) Puppet::Type.expects(:type).with("conflict").returns(faketype) assert_raise(Puppet::ParseError, "Did not fail when two isomorphic resources conflicted") do config.send(:verify_uniqueness, conflict) end end def test_store_resource # Run once when there's no conflict config = mkconfig table = config.instance_variable_get("@resource_table") resource = mock("resource") resource.expects(:ref).returns("yay") config.expects(:verify_uniqueness).with(resource) scope = mock("scope") graph = config.instance_variable_get("@resource_graph") graph.expects(:add_edge!).with(scope, resource) assert_nothing_raised("Could not store resource") do config.store_resource(scope, resource) end assert_equal(resource, table["yay"], "Did not store resource in table") # Now for conflicts config = mkconfig table = config.instance_variable_get("@resource_table") resource = mock("resource") config.expects(:verify_uniqueness).with(resource).raises(ArgumentError) assert_raise(ArgumentError, "Did not raise uniqueness exception") do config.store_resource(scope, resource) end assert(table.empty?, "Conflicting resource was stored in table") end def test_fail_on_unevaluated config = mkconfig config.expects(:fail_on_unevaluated_overrides) config.expects(:fail_on_unevaluated_resource_collections) config.send :fail_on_unevaluated end def test_store_override # First test the case when the resource is not present. config = mkconfig overrides = config.instance_variable_get("@resource_overrides") override = Object.new override.expects(:ref).returns(:myref).times(2) override.expects(:override=).with(true) assert_nothing_raised("Could not call store_override") do config.store_override(override) end assert_instance_of(Array, overrides[:myref], "Overrides table is not a hash of arrays") assert_equal(override, overrides[:myref][0], "Did not store override in appropriately named array") # And when the resource already exists. resource = mock 'resource' resources = config.instance_variable_get("@resource_table") resources[:resref] = resource override = mock 'override' resource.expects(:merge).with(override) override.expects(:override=).with(true) override.expects(:ref).returns(:resref) assert_nothing_raised("Could not call store_override when the resource already exists.") do config.store_override(override) end end def test_resource_overrides config = mkconfig overrides = config.instance_variable_get("@resource_overrides") overrides[:test] = :yay resource = mock 'resource' resource.expects(:ref).returns(:test) assert_equal(:yay, config.resource_overrides(resource), "Did not return overrides from table") end def test_fail_on_unevaluated_resource_collections config = mkconfig collections = config.instance_variable_get("@collections") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do config.send :fail_on_unevaluated_resource_collections end # And that we're fine when we've got collections but with no resources collections << mock('coll') collections[0].expects(:resources).returns(nil) assert_nothing_raised("Failed when no resource collections were present") do config.send :fail_on_unevaluated_resource_collections end # But that we do fail when we've got resource collections left. collections.clear # return both an array and a string, because that's tested internally collections << mock('coll returns one') collections[0].expects(:resources).returns(:something) collections << mock('coll returns many') collections[1].expects(:resources).returns([:one, :two]) assert_raise(Puppet::ParseError, "Did not fail on unevaluated resource collections") do config.send :fail_on_unevaluated_resource_collections end end def test_fail_on_unevaluated_overrides config = mkconfig overrides = config.instance_variable_get("@resource_overrides") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do config.send :fail_on_unevaluated_overrides end # But that we fail if there are any overrides left in the table. overrides[:yay] = [] overrides[:foo] = [] overrides[:bar] = [mock("override")] overrides[:bar][0].expects(:ref).returns("yay") assert_raise(Puppet::ParseError, "Failed to fail when overrides remain") do config.send :fail_on_unevaluated_overrides end end def test_find_resource config = mkconfig resources = config.instance_variable_get("@resource_table") assert_nothing_raised("Could not call findresource when the resource table was empty") do assert_nil(config.findresource("yay", "foo"), "Returned a non-existent resource") assert_nil(config.findresource("yay[foo]"), "Returned a non-existent resource") end resources["Foo[bar]"] = :yay assert_nothing_raised("Could not call findresource when the resource table was not empty") do assert_equal(:yay, config.findresource("foo", "bar"), "Returned a non-existent resource") assert_equal(:yay, config.findresource("Foo[bar]"), "Returned a non-existent resource") end end # #620 - Nodes and classes should conflict, else classes don't get evaluated def test_nodes_and_classes_name_conflict # Test node then class config = mkconfig node = stub :nodescope? => true klass = stub :nodescope? => false config.class_set("one", node) assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do config.class_set("one", klass) end # and class then node config = mkconfig node = stub :nodescope? => true klass = stub :nodescope? => false config.class_set("two", klass) assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do config.class_set("two", node) end end end diff --git a/test/language/parser.rb b/test/language/parser.rb index 7c43746e7..0406e99ba 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -1,1197 +1,1197 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppet' require 'puppet/parser/parser' require 'puppettest' class TestParser < Test::Unit::TestCase include PuppetTest::ParserTesting def setup super Puppet[:parseonly] = true #@lexer = Puppet::Parser::Lexer.new() end def test_each_file textfiles { |file| parser = mkparser Puppet.debug("parsing %s" % file) if __FILE__ == $0 assert_nothing_raised() { parser.file = file parser.parse } Puppet::Type.eachtype { |type| type.each { |obj| assert(obj.file, "File is not set on %s" % obj.ref) assert(obj.name, "Name is not set on %s" % obj.ref) assert(obj.line, "Line is not set on %s" % obj.ref) } } Puppet::Type.allclear } end def test_failers failers { |file| parser = mkparser Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 assert_raise(Puppet::ParseError, "Did not fail while parsing %s" % file) { parser.file = file ast = parser.parse config = mkconfig(parser) config.compile #ast.classes[""].evaluate :scope => config.topscope } Puppet::Type.allclear } end def test_arrayrvalues parser = mkparser ret = nil file = tempfile() assert_nothing_raised { parser.string = "file { \"#{file}\": mode => [755, 640] }" } assert_nothing_raised { ret = parser.parse } end def mkmanifest(file) name = File.join(tmpdir, "file%s" % rand(100)) @@tmpfiles << name File.open(file, "w") { |f| f.puts "file { \"%s\": ensure => file, mode => 755 }\n" % name } end def test_importglobbing basedir = File.join(tmpdir(), "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) subdir = "subdir" Dir.mkdir(File.join(basedir, subdir)) manifest = File.join(basedir, "manifest") File.open(manifest, "w") { |f| f.puts "import \"%s/*\"" % subdir } 4.times { |i| path = File.join(basedir, subdir, "subfile%s" % i) mkmanifest(path) } assert_nothing_raised("Could not parse multiple files") { parser = mkparser parser.file = manifest parser.parse } end def test_nonexistent_import basedir = File.join(tmpdir(), "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) manifest = File.join(basedir, "manifest") File.open(manifest, "w") do |f| f.puts "import \" no such file \"" end assert_raise(Puppet::ParseError) { parser = mkparser parser.file = manifest parser.parse } end def test_trailingcomma path = tempfile() str = %{file { "#{path}": ensure => file, } } parser = mkparser parser.string = str assert_nothing_raised("Could not parse trailing comma") { parser.parse } end def test_importedclasses imported = tempfile() importer = tempfile() made = tempfile() File.open(imported, "w") do |f| f.puts %{class foo { file { "#{made}": ensure => file }}} end File.open(importer, "w") do |f| f.puts %{import "#{imported}"\ninclude foo} end parser = mkparser parser.file = importer # Make sure it parses fine assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, made) end # Make sure fully qualified and unqualified files can be imported def test_fqfilesandlocalfiles dir = tempfile() Dir.mkdir(dir) importer = File.join(dir, "site.pp") fullfile = File.join(dir, "full.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "#{fullfile}"\ninclude full\nimport "local.pp"\ninclude local} end fullmaker = tempfile() files << fullmaker File.open(fullfile, "w") do |f| f.puts %{class full { file { "#{fullmaker}": ensure => file }}} end localmaker = tempfile() files << localmaker File.open(localfile, "w") do |f| f.puts %{class local { file { "#{localmaker}": ensure => file }}} end parser = mkparser parser.file = importer # Make sure it parses assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, *files) end # Make sure the parser adds '.pp' when necessary def test_addingpp dir = tempfile() Dir.mkdir(dir) importer = File.join(dir, "site.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "local"\ninclude local} end file = tempfile() files << file File.open(localfile, "w") do |f| f.puts %{class local { file { "#{file}": ensure => file }}} end parser = mkparser parser.file = importer assert_nothing_raised { parser.parse } end # Make sure that file importing changes file relative names. def test_changingrelativenames dir = tempfile() Dir.mkdir(dir) Dir.mkdir(File.join(dir, "subdir")) top = File.join(dir, "site.pp") subone = File.join(dir, "subdir/subone") subtwo = File.join(dir, "subdir/subtwo") files = [] file = tempfile() files << file File.open(subone + ".pp", "w") do |f| f.puts %{class one { file { "#{file}": ensure => file }}} end otherfile = tempfile() files << otherfile File.open(subtwo + ".pp", "w") do |f| f.puts %{import "subone"\n class two inherits one { file { "#{otherfile}": ensure => file } }} end File.open(top, "w") do |f| f.puts %{import "subdir/subtwo"} end parser = mkparser parser.file = top assert_nothing_raised { parser.parse } end # Defaults are purely syntactical, so it doesn't make sense to be able to # collect them. def test_uncollectabledefaults string = "@Port { protocols => tcp }" assert_raise(Puppet::ParseError) { mkparser.parse(string) } end # Verify that we can parse collections def test_collecting text = "Port <| |>" parser = mkparser parser.string = text ret = nil assert_nothing_raised { ret = parser.parse } ret.classes[""].code.each do |obj| assert_instance_of(AST::Collection, obj) end end def test_emptyfile file = tempfile() File.open(file, "w") do |f| f.puts %{} end parser = mkparser parser.file = file assert_nothing_raised { parser.parse } end def test_multiple_nodes_named file = tempfile() other = tempfile() File.open(file, "w") do |f| f.puts %{ node nodeA, nodeB { file { "#{other}": ensure => file } } } end parser = mkparser parser.file = file ast = nil assert_nothing_raised { ast = parser.parse } end def test_emptyarrays str = %{$var = []\n} parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end # Make sure function names aren't reserved words. def test_functionnamecollision str = %{tag yayness tag(rahness) file { "/tmp/yayness": tag => "rahness", ensure => exists } } parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end def test_metaparams_in_definition_prototypes parser = mkparser assert_raise(Puppet::ParseError) { parser.parse %{define mydef($schedule) {}} } assert_nothing_raised { parser.parse %{define adef($schedule = false) {}} parser.parse %{define mydef($schedule = daily) {}} } end def test_parsingif parser = mkparser exec = proc do |val| %{exec { "/bin/echo #{val}": logoutput => true }} end str1 = %{if true { #{exec.call("true")} }} ret = nil assert_nothing_raised { ret = parser.parse(str1).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) parser = mkparser str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }} assert_nothing_raised { ret = parser.parse(str2).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) assert_instance_of(Puppet::Parser::AST::Else, ret.else) end def test_hostclass parser = mkparser assert_nothing_raised { parser.parse %{class myclass { class other {} }} } assert(parser.classes["myclass"], "Could not find myclass") assert(parser.classes["myclass::other"], "Could not find myclass::other") assert_nothing_raised { parser.parse "class base {} class container { class deep::sub inherits base {} }" } sub = parser.classes["container::deep::sub"] assert(sub, "Could not find sub") # Now try it with a parent class being a fq class assert_nothing_raised { parser.parse "class container::one inherits container::deep::sub {}" } sub = parser.classes["container::one"] assert(sub, "Could not find one") assert_equal("container::deep::sub", sub.parentclass) # Finally, try including a qualified class assert_nothing_raised("Could not include fully qualified class") { parser.parse "include container::deep::sub" } end def test_topnamespace parser = mkparser # Make sure we put the top-level code into a class called "" in # the "" namespace assert_nothing_raised do out = parser.parse "" assert_instance_of(Puppet::Parser::Parser::ASTSet, out) assert_nil(parser.classes[""], "Got a 'main' class when we had no code") end # Now try something a touch more complicated parser.initvars assert_nothing_raised do out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }" assert_instance_of(Puppet::Parser::Parser::ASTSet, out) assert_equal("", parser.classes[""].classname) assert_equal("", parser.classes[""].namespace) end end # Make sure virtual and exported resources work appropriately. def test_virtualresources tests = [:virtual] if Puppet.features.rails? Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual at = "@" else at = "@@" end check = proc do |res, msg| if res.is_a?(Puppet::Parser::Resource) txt = res.ref else txt = res.class end # Real resources get marked virtual when exported if form == :virtual or res.is_a?(Puppet::Parser::Resource) assert(res.virtual, "#{msg} #{at}#{txt} is not virtual") end if form == :virtual assert(! res.exported, "#{msg} #{at}#{txt} is exported") else assert(res.exported, "#{msg} #{at}#{txt} is not exported") end end ret = nil assert_nothing_raised do ret = parser.parse("#{at}file { '/tmp/testing': owner => root }") end assert_instance_of(AST::ASTArray, ret.classes[""].code) resdef = ret.classes[""].code[0] assert_instance_of(AST::ResourceDef, resdef) assert_equal("/tmp/testing", resdef.title.value) # We always get an astarray back, so... check.call(resdef, "simple resource") # Now let's try it with multiple resources in the same spec assert_nothing_raised do ret = parser.parse("#{at}file { ['/tmp/1', '/tmp/2']: owner => root }") end ret.classes[""].each do |res| assert_instance_of(AST::ResourceDef, res) check.call(res, "multiresource") end # Now evaluate these scope = mkscope klass = parser.newclass "" scope.source = klass assert_nothing_raised do ret.classes[""].evaluate :scope => scope end # Make sure we can find both of them %w{/tmp/1 /tmp/2}.each do |title| res = scope.findresource("File[#{title}]") assert(res, "Could not find %s" % title) check.call(res, "found multiresource") end end end def test_collections tests = [:virtual] if Puppet.features.rails? Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual arrow = "<||>" else arrow = "<<||>>" end ret = nil assert_nothing_raised do ret = parser.parse("File #{arrow}") end coll = ret.classes[""].code[0] assert_instance_of(AST::Collection, coll) assert_equal(form, coll.form) end end def test_collectionexpressions %w{== !=}.each do |oper| str = "File <| title #{oper} '/tmp/testing' |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(:virtual, query.form) assert_equal("title", query.test1.value) assert_equal("/tmp/testing", query.test2.value) assert_equal(oper, query.oper) end end def test_collectionstatements %w{and or}.each do |joiner| str = "File <| title == '/tmp/testing' #{joiner} owner == root |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(joiner, query.oper) assert_instance_of(AST::CollExpr, query.test1) assert_instance_of(AST::CollExpr, query.test2) end end def test_collectionstatements_with_parens [ "(title == '/tmp/testing' and owner == root) or owner == wheel", "(title == '/tmp/testing')" ].each do |test| str = "File <| #{test} |>" parser = mkparser res = nil assert_nothing_raised("Could not parse '#{test}'") do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) #assert_equal(joiner, query.oper) #assert_instance_of(AST::CollExpr, query.test1) #assert_instance_of(AST::CollExpr, query.test2) end end # We've had problems with files other than site.pp importing into main. def test_importing_into_main top = tempfile() other = tempfile() File.open(top, "w") do |f| f.puts "import '#{other}'" end file = tempfile() File.open(other, "w") do |f| f.puts "file { '#{file}': ensure => present }" end interp = mkinterp :Manifest => top, :UseNodes => false code = nil assert_nothing_raised do code = interp.compile(mknode).flatten end assert(code.length == 1, "Did not get the file") assert_instance_of(Puppet::TransObject, code[0]) end def test_fully_qualified_definitions parser = mkparser assert_nothing_raised("Could not parse fully-qualified definition") { parser.parse %{define one::two { }} } assert(parser.definitions["one::two"], "Could not find one::two with no namespace") # Now try using the definition assert_nothing_raised("Could not parse fully-qualified definition usage") { parser.parse %{one::two { yayness: }} } end # #524 def test_functions_with_no_arguments parser = mkparser assert_nothing_raised("Could not parse statement function with no args") { parser.parse %{tag()} } assert_nothing_raised("Could not parse rvalue function with no args") { parser.parse %{$testing = template()} } end def test_module_import basedir = File.join(tmpdir(), "module-import") @@tmpfiles << basedir Dir.mkdir(basedir) modfiles = [ "init.pp", "mani1.pp", "mani2.pp", "sub/smani1.pp", "sub/smani2.pp" ] modpath = File.join(basedir, "modules") Puppet[:modulepath] = modpath modname = "amod" manipath = File::join(modpath, modname, Puppet::Module::MANIFESTS) FileUtils::mkdir_p(File::join(manipath, "sub")) targets = [] modfiles.each do |fname| target = File::join(basedir, File::basename(fname, '.pp')) targets << target txt = %[ file { '#{target}': content => "#{fname}" } ] if fname == "init.pp" txt = %[import 'mani1' \nimport '#{modname}/mani2'\nimport '#{modname}/sub/*.pp'\n ] + txt end File::open(File::join(manipath, fname), "w") do |f| f.puts txt end end manifest_texts = [ "import '#{modname}'", "import '#{modname}/init'", "import '#{modname}/init.pp'" ] manifest = File.join(modpath, "manifest.pp") manifest_texts.each do |txt| File.open(manifest, "w") { |f| f.puts txt } assert_nothing_raised { parser = mkparser parser.file = manifest parser.parse } assert_creates(manifest, *targets) end end # #544 def test_ignoreimports parser = mkparser assert(! Puppet[:ignoreimport], ":ignoreimport defaulted to true") assert_raise(Puppet::ParseError, "Did not fail on missing import") do parser.parse("import 'nosuchfile'") end assert_nothing_raised("could not set :ignoreimport") do Puppet[:ignoreimport] = true end assert_nothing_raised("Parser did not follow :ignoreimports") do parser.parse("import 'nosuchfile'") end end def test_multiple_imports_on_one_line one = tempfile two = tempfile base = tempfile File.open(one, "w") { |f| f.puts "$var = value" } File.open(two, "w") { |f| f.puts "$var = value" } File.open(base, "w") { |f| f.puts "import '#{one}', '#{two}'" } parser = mkparser parser.file = base # Importing is logged at debug time. Puppet::Util::Log.level = :debug assert_nothing_raised("Parser could not import multiple files at once") do parser.parse end [one, two].each do |file| assert(@logs.detect { |l| l.message =~ /importing '#{file}'/}, "did not import %s" % file) end end def test_cannot_assign_qualified_variables parser = mkparser assert_raise(Puppet::ParseError, "successfully assigned a qualified variable") do parser.parse("$one::two = yay") end end # #588 def test_globbing_with_directories dir = tempfile Dir.mkdir(dir) subdir = File.join(dir, "subdir") Dir.mkdir(subdir) file = File.join(dir, "file.pp") maker = tempfile File.open(file, "w") { |f| f.puts "file { '#{maker}': ensure => file }" } parser = mkparser assert_nothing_raised("Globbing failed when it matched a directory") do parser.import("%s/*" % dir) end end # #629 - undef keyword def test_undef parser = mkparser result = nil assert_nothing_raised("Could not parse assignment to undef") { result = parser.parse %{$variable = undef} } main = result.classes[""].code children = main.children assert_instance_of(AST::VarDef, main.children[0]) assert_instance_of(AST::Undef, main.children[0].value) end # Prompted by #729 -- parsing should not modify the interpreter. def test_parse parser = mkparser str = "file { '/tmp/yay': ensure => file }\nclass yay {}\nnode foo {}\ndefine bar {}\n" result = nil assert_nothing_raised("Could not parse") do result = parser.parse(str) end assert_instance_of(Puppet::Parser::Parser::ASTSet, result, "Did not get a ASTSet back from parsing") assert_instance_of(AST::HostClass, result.classes["yay"], "Did not create 'yay' class") assert_instance_of(AST::HostClass, result.classes[""], "Did not create main class") - assert_instance_of(AST::Component, result.definitions["bar"], "Did not create 'bar' definition") + assert_instance_of(AST::Definition, result.definitions["bar"], "Did not create 'bar' definition") assert_instance_of(AST::Node, result.nodes["foo"], "Did not create 'foo' node") end # Make sure our node gets added to the node table. def test_newnode parser = mkparser # First just try calling it directly assert_nothing_raised { parser.newnode("mynode", :code => :yay) } assert_equal(:yay, parser.nodes["mynode"].code) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { parser.newnode("mynode", {}) } # Now try one with no code assert_nothing_raised { parser.newnode("simplenode", :parent => :foo) } # Now define the parent node parser.newnode(:foo) # And make sure we get things back correctly assert_equal(:foo, parser.nodes["simplenode"].parentclass) assert_nil(parser.nodes["simplenode"].code) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { parser.newnode("mynode", {}) } # Test multiple names names = ["one", "two", "three"] assert_nothing_raised { parser.newnode(names, {:code => :yay, :parent => :foo}) } names.each do |name| assert_equal(:yay, parser.nodes[name].code) assert_equal(:foo, parser.nodes[name].parentclass) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { parser.newnode(name, {}) } end end def test_newdefine parser = mkparser assert_nothing_raised { parser.newdefine("mydefine", :code => :yay, :arguments => ["a", stringobj("b")]) } mydefine = parser.definitions["mydefine"] assert(mydefine, "Could not find definition") assert_equal("", mydefine.namespace) assert_equal("mydefine", mydefine.classname) assert_raise(Puppet::ParseError) do parser.newdefine("mydefine", :code => :yay, :arguments => ["a", stringobj("b")]) end # Now define the same thing in a different scope assert_nothing_raised { parser.newdefine("other::mydefine", :code => :other, :arguments => ["a", stringobj("b")]) } other = parser.definitions["other::mydefine"] assert(other, "Could not find definition") assert(parser.definitions["other::mydefine"], "Could not find other::mydefine") assert_equal(:other, other.code) assert_equal("other", other.namespace) assert_equal("other::mydefine", other.classname) end def test_newclass scope = mkscope parser = scope.compile.parser mkcode = proc do |ary| classes = ary.collect do |string| AST::FlatString.new(:value => string) end AST::ASTArray.new(:children => classes) end # First make sure that code is being appended code = mkcode.call(%w{original code}) klass = nil assert_nothing_raised { klass = parser.newclass("myclass", :code => code) } assert(klass, "Did not return class") assert(parser.classes["myclass"], "Could not find definition") assert_equal("myclass", parser.classes["myclass"].classname) assert_equal(%w{original code}, parser.classes["myclass"].code.evaluate(:scope => scope)) # Newclass behaves differently than the others -- it just appends # the code to the existing class. code = mkcode.call(%w{something new}) assert_nothing_raised do klass = parser.newclass("myclass", :code => code) end assert(klass, "Did not return class when appending") assert_equal(%w{original code something new}, parser.classes["myclass"].code.evaluate(:scope => scope)) # Now create the same class name in a different scope assert_nothing_raised { klass = parser.newclass("other::myclass", :code => mkcode.call(%w{something diff})) } assert(klass, "Did not return class") other = parser.classes["other::myclass"] assert(other, "Could not find class") assert_equal("other::myclass", other.classname) assert_equal("other::myclass", other.namespace) assert_equal(%w{something diff}, other.code.evaluate(:scope => scope)) # Make sure newclass deals correctly with nodes with no code klass = parser.newclass("nocode") assert(klass, "Did not return class") assert_nothing_raised do klass = parser.newclass("nocode", :code => mkcode.call(%w{yay test})) end assert(klass, "Did not return class with no code") assert_equal(%w{yay test}, parser.classes["nocode"].code.evaluate(:scope => scope)) # Then try merging something into nothing parser.newclass("nocode2", :code => mkcode.call(%w{foo test})) assert(klass, "Did not return class with no code") assert_nothing_raised do klass = parser.newclass("nocode2") end assert(klass, "Did not return class with no code") assert_equal(%w{foo test}, parser.classes["nocode2"].code.evaluate(:scope => scope)) # And lastly, nothing and nothing klass = parser.newclass("nocode3") assert(klass, "Did not return class with no code") assert_nothing_raised do klass = parser.newclass("nocode3") end assert(klass, "Did not return class with no code") assert_nil(parser.classes["nocode3"].code) end # Make sure you can't have classes and defines with the same name in the # same scope. def test_classes_beat_defines parser = mkparser assert_nothing_raised { parser.newclass("yay::funtest") } assert_raise(Puppet::ParseError) do parser.newdefine("yay::funtest") end assert_nothing_raised { parser.newdefine("yay::yaytest") } assert_raise(Puppet::ParseError) do parser.newclass("yay::yaytest") end end def test_namesplit parser = mkparser assert_nothing_raised do {"base::sub" => %w{base sub}, "main" => ["", "main"], "one::two::three::four" => ["one::two::three", "four"], }.each do |name, ary| result = parser.namesplit(name) assert_equal(ary, result, "%s split to %s" % [name, result]) end end end # Now make sure we get appropriate behaviour with parent class conflicts. def test_newclass_parentage parser = mkparser parser.newclass("base1") parser.newclass("one::two::three") # First create it with no parentclass. assert_nothing_raised { parser.newclass("sub") } assert(parser.classes["sub"], "Could not find definition") assert_nil(parser.classes["sub"].parentclass) # Make sure we can't set the parent class to ourself. assert_raise(Puppet::ParseError) { parser.newclass("sub", :parent => "sub") } # Now create another one, with a parentclass. assert_nothing_raised { parser.newclass("sub", :parent => "base1") } # Make sure we get the right parent class, and make sure it's not an object. assert_equal("base1", parser.classes["sub"].parentclass) # Now make sure we get a failure if we try to conflict. assert_raise(Puppet::ParseError) { parser.newclass("sub", :parent => "one::two::three") } # Make sure that failure didn't screw us up in any way. assert_equal("base1", parser.classes["sub"].parentclass) # But make sure we can create a class with a fq parent assert_nothing_raised { parser.newclass("another", :parent => "one::two::three") } assert_equal("one::two::three", parser.classes["another"].parentclass) end def test_fqfind parser = mkparser table = {} # Define a bunch of things. %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| table[string] = string end check = proc do |namespace, hash| hash.each do |thing, result| assert_equal(result, parser.fqfind(namespace, thing, table), "Could not find %s in %s" % [thing, namespace]) end end # Now let's do some test lookups. # First do something really simple check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", "c::d" => "a::b::c::d", "::c::d" => "c::d" check.call "", "a" => "a", "a::c" => "a::c" end # Setup a module. def mk_module(name, files = {}) mdir = File.join(@dir, name) mandir = File.join(mdir, "manifests") FileUtils.mkdir_p mandir if defs = files[:define] files.delete(:define) end Dir.chdir(mandir) do files.each do |file, classes| File.open("%s.pp" % file, "w") do |f| classes.each { |klass| if defs f.puts "define %s {}" % klass else f.puts "class %s {}" % klass end } end end end end # #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one. def test_module_autoloading @dir = tempfile Puppet[:modulepath] = @dir FileUtils.mkdir_p @dir parser = mkparser # Make sure we fail like normal for actually missing classes assert_nil(parser.findclass("", "nosuchclass"), "Did not return nil on missing classes") # test the simple case -- the module class itself name = "simple" mk_module(name, :init => [name]) # Try to load the module automatically now klass = parser.findclass("", name) assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Try loading the simple module when we're in something other than the base namespace. parser = mkparser klass = parser.findclass("something::else", name) assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Now try it with a definition as the base file name = "simpdef" mk_module(name, :define => true, :init => [name]) klass = parser.finddefine("", name) - assert_instance_of(AST::Component, klass, "Did not autoload class from module init file") + assert_instance_of(AST::Definition, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Now try it with namespace classes where both classes are in the init file parser = mkparser modname = "both" name = "sub" mk_module(modname, :init => %w{both both::sub}) # First try it with a namespace klass = parser.findclass("both", name) assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with a namespace") assert_equal("both::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "both::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with no namespace") assert_equal("both::sub", klass.classname, "Incorrect class was returned") # Now try it with the class in a different file parser = mkparser modname = "separate" name = "sub" mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub}) # First try it with a namespace klass = parser.findclass("separate", name) assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with a namespace") assert_equal("separate::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "separate::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with no namespace") assert_equal("separate::sub", klass.classname, "Incorrect class was returned") # Now make sure we don't get a failure when there's no module file parser = mkparser modname = "alone" name = "sub" mk_module(modname, :sub => %w{alone::sub}) # First try it with a namespace assert_nothing_raised("Could not autoload file when module file is missing") do klass = parser.findclass("alone", name) end assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with a namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "alone::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") end # Make sure class, node, and define methods are case-insensitive def test_structure_case_insensitivity parser = mkparser result = nil assert_nothing_raised do result = parser.newclass "Yayness" end assert_equal(result, parser.findclass("", "yayNess")) assert_nothing_raised do result = parser.newdefine "FunTest" end assert_equal(result, parser.finddefine("", "fUntEst"), "%s was not matched" % "fUntEst") end end # $Id$ diff --git a/test/language/resource.rb b/test/language/resource.rb index 37b0b7e1e..320b958ff 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -1,464 +1,464 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/resourcetesting' class TestResource < PuppetTest::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Parser::AST Resource = Puppet::Parser::Resource Reference = Puppet::Parser::Resource::Reference def setup super Puppet[:trace] = false end def teardown mocha_verify end def test_initialize args = {:type => "resource", :title => "testing", :source => "source", :scope => "scope"} # Check our arg requirements args.each do |name, value| try = args.dup try.delete(name) assert_raise(ArgumentError, "Did not fail when %s was missing" % name) do Parser::Resource.new(try) end end Reference.expects(:new).with(:type => "resource", :title => "testing", :scope => "scope").returns(:ref) res = nil assert_nothing_raised do res = Parser::Resource.new(args) end end def test_merge res = mkresource other = mkresource # First try the case where the resource is not allowed to override res.source = "source1" other.source = "source2" other.source.expects(:child_of?).with("source1").returns(false) assert_raise(Puppet::ParseError, "Allowed unrelated resources to override") do res.merge(other) end # Next try it when the sources are equal. res.source = "source3" other.source = res.source other.source.expects(:child_of?).with("source3").never params = {:a => :b, :c => :d} other.expects(:params).returns(params) res.expects(:override_parameter).with(:b) res.expects(:override_parameter).with(:d) res.merge(other) # And then parentage is involved other = mkresource res.source = "source3" other.source = "source4" other.source.expects(:child_of?).with("source3").returns(true) params = {:a => :b, :c => :d} other.expects(:params).returns(params) res.expects(:override_parameter).with(:b) res.expects(:override_parameter).with(:d) res.merge(other) end # the [] method def test_array_accessors res = mkresource params = res.instance_variable_get("@params") assert_nil(res[:missing], "Found a missing parameter somehow") params[:something] = stub(:value => "yay") assert_equal("yay", res[:something], "Did not correctly call value on the parameter") res.expects(:title).returns(:mytitle) assert_equal(:mytitle, res[:title], "Did not call title when asked for it as a param") end # Make sure any defaults stored in the scope get added to our resource. def test_add_defaults res = mkresource params = res.instance_variable_get("@params") params[:a] = :b res.scope.expects(:lookupdefaults).with(res.type).returns(:a => :replaced, :c => :d) res.expects(:debug) res.send(:add_defaults) assert_equal(:d, params[:c], "Did not set default") assert_equal(:b, params[:a], "Replaced parameter with default") end def test_finish res = mkresource res.expects(:add_overrides) res.expects(:add_defaults) res.expects(:add_metaparams) res.expects(:validate) res.finish end # Make sure we paramcheck our params def test_validate res = mkresource params = res.instance_variable_get("@params") params[:one] = :two params[:three] = :four res.expects(:paramcheck).with(:one) res.expects(:paramcheck).with(:three) res.send(:validate) end def test_override_parameter res = mkresource params = res.instance_variable_get("@params") # There are three cases, with the second having two options: # No existing parameter. param = stub(:name => "myparam") res.send(:override_parameter, param) assert_equal(param, params["myparam"], "Override was not added to param list") # An existing parameter that we can override. source = stub(:child_of? => true) # Start out without addition params["param2"] = stub(:source => :whatever) param = stub(:name => "param2", :source => source, :add => false) res.send(:override_parameter, param) assert_equal(param, params["param2"], "Override was not added to param list") # Try with addition. params["param2"] = stub(:value => :a, :source => :whatever) param = stub(:name => "param2", :source => source, :add => true, :value => :b) param.expects(:value=).with([:a, :b]) res.send(:override_parameter, param) assert_equal(param, params["param2"], "Override was not added to param list") # And finally, make sure we throw an exception when the sources aren't related source = stub(:child_of? => false) params["param2"] = stub(:source => :whatever, :file => :f, :line => :l) old = params["param2"] param = stub(:name => "param2", :source => source, :file => :f, :line => :l) assert_raise(Puppet::ParseError, "Did not fail when params conflicted") do res.send(:override_parameter, param) end assert_equal(old, params["param2"], "Param was replaced irrespective of conflict") end def test_set_parameter res = mkresource params = res.instance_variable_get("@params") # First test the simple case: It's already a parameter param = mock('param') param.expects(:is_a?).with(Resource::Param).returns(true) param.expects(:name).returns("pname") res.send(:set_parameter, param) assert_equal(param, params["pname"], "Parameter was not added to hash") # Now the case where there's no value but it's not a param param = mock('param') param.expects(:is_a?).with(Resource::Param).returns(false) assert_raise(ArgumentError, "Did not fail when a non-param was passed") do res.send(:set_parameter, param) end # and the case where a value is passed in param = stub :name => "pname", :value => "whatever" Resource::Param.expects(:new).with(:name => "pname", :value => "myvalue", :source => res.source).returns(param) res.send(:set_parameter, "pname", "myvalue") assert_equal(param, params["pname"], "Did not put param in hash") end def test_paramcheck # There are three cases here: # It's a valid parameter res = mkresource ref = mock('ref') res.instance_variable_set("@ref", ref) klass = mock("class") ref.expects(:typeclass).returns(klass).times(4) klass.expects(:validattr?).with("good").returns(true) assert(res.send(:paramcheck, :good), "Did not allow valid param") # It's name or title klass.expects(:validattr?).with("name").returns(false) assert(res.send(:paramcheck, :name), "Did not allow name") klass.expects(:validattr?).with("title").returns(false) assert(res.send(:paramcheck, :title), "Did not allow title") # It's not actually allowed klass.expects(:validattr?).with("other").returns(false) res.expects(:fail) ref.expects(:type) res.send(:paramcheck, :other) end def test_to_trans # First try translating a builtin resource. Make sure we use some references # and arrays, to make sure they translate correctly. source = mock("source") scope = mock("scope") scope.expects(:tags).returns([]) refs = [] 4.times { |i| refs << Puppet::Parser::Resource::Reference.new(:title => "file%s" % i, :type => "file") } res = Parser::Resource.new :type => "file", :title => "/tmp", :source => source, :scope => scope, :params => paramify(source, :owner => "nobody", :group => %w{you me}, :require => refs[0], :ignore => %w{svn}, :subscribe => [refs[1], refs[2]], :notify => [refs[3]]) obj = nil assert_nothing_raised do obj = res.to_trans end assert_instance_of(Puppet::TransObject, obj) assert_equal(obj.type, res.type) assert_equal(obj.name, res.title) # TransObjects use strings, resources use symbols assert_equal("nobody", obj["owner"], "Single-value string was not passed correctly") assert_equal(%w{you me}, obj["group"], "Array of strings was not passed correctly") assert_equal("svn", obj["ignore"], "Array with single string was not turned into single value") assert_equal(["file", refs[0].title], obj["require"], "Resource reference was not passed correctly") assert_equal([["file", refs[1].title], ["file", refs[2].title]], obj["subscribe"], "Array of resource references was not passed correctly") assert_equal(["file", refs[3].title], obj["notify"], "Array with single resource reference was not turned into single value") end def test_evaluate # First try the most common case, we're not a builtin type. res = mkresource ref = res.instance_variable_get("@ref") type = mock("type") ref.expects(:definedtype).returns(type) res.expects(:finish) res.scope = mock("scope") config = mock("config") res.scope.expects(:compile).returns(config) config.expects(:delete_resource).with(res) args = {:scope => res.scope, :arguments => res.to_hash} # This is insane; FIXME we need to redesign how classes and components are evaluated. [:type, :title, :virtual, :exported].each do |param| args[param] = res.send(param) end type.expects(:evaluate_resource).with(args) res.evaluate end def test_add_overrides # Try it with nil res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:compile).returns(config) config.expects(:resource_overrides).with(res).returns(nil) res.expects(:merge).never res.send(:add_overrides) # And an empty array res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:compile).returns(config) config.expects(:resource_overrides).with(res).returns([]) res.expects(:merge).never res.send(:add_overrides) # And with some overrides res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:compile).returns(config) returns = %w{a b} config.expects(:resource_overrides).with(res).returns(returns) res.expects(:merge).with("a") res.expects(:merge).with("b") res.send(:add_overrides) assert(returns.empty?, "Did not clear overrides") end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => mock("source"), :scope => mock('scope') assert_equal("evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) end def test_add_metaparams res = mkresource params = res.instance_variable_get("@params") params[:a] = :b Puppet::Type.expects(:eachmetaparam).multiple_yields(:a, :b, :c) res.scope.expects(:lookupvar).with("b", false).returns(:something) res.scope.expects(:lookupvar).with("c", false).returns(:undefined) res.expects(:set_parameter).with(:b, :something) res.send(:add_metaparams) assert_nil(params[:c], "A value was created somehow for an unset metaparam") end def test_reference_conversion # First try it as a normal string ref = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref1") # Now create an obj that uses it res = mkresource :type => "file", :title => "/tmp/resource", :params => {:require => ref} res.scope = stub(:tags => []) trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"]) assert_equal(["file", "/tmp/ref1"], trans["require"]) # Now try it when using an array of references. two = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref2") res = mkresource :type => "file", :title => "/tmp/resource2", :params => {:require => [ref, two]} res.scope = stub(:tags => []) trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"][0]) trans["require"].each do |val| assert_instance_of(Array, val) assert_equal("file", val[0]) assert(val[1] =~ /\/tmp\/ref[0-9]/, "Was %s instead of the file name" % val[1]) end end # This is a bit of a weird one -- the user should not actually know # that components exist, so we want references to act like they're not # builtin def test_components_are_not_builtin ref = Parser::Resource::Reference.new(:type => "component", :title => "yay") - assert_nil(ref.builtintype, "Component was considered builtin") + assert_nil(ref.builtintype, "Definition was considered builtin") end # The second part of #539 - make sure resources pass the arguments # correctly. def test_title_with_definitions parser = mkparser define = parser.newdefine "yayness", :code => resourcedef("file", "/tmp", "owner" => varref("name"), "mode" => varref("title")) klass = parser.findclass("", "") should = {:name => :owner, :title => :mode} [ {:name => "one", :title => "two"}, {:title => "three"}, ].each do |hash| config = mkconfig parser args = {:type => "yayness", :title => hash[:title], :source => klass, :scope => config.topscope} if hash[:name] args[:params] = {:name => hash[:name]} else args[:params] = {} # override the defaults end res = nil assert_nothing_raised("Could not create res with %s" % hash.inspect) do res = mkresource(args) end assert_nothing_raised("Could not eval res with %s" % hash.inspect) do res.evaluate end made = config.topscope.findresource("File[/tmp]") assert(made, "Did not create resource with %s" % hash.inspect) should.each do |orig, param| assert_equal(hash[orig] || hash[:title], made[param], "%s was not set correctly with %s" % [param, hash.inspect]) end end end # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. def test_undef_and_to_hash res = mkresource :type => "file", :title => "/tmp/testing", :source => mock("source"), :scope => mock("scope"), :params => {:owner => :undef, :mode => "755"} hash = nil assert_nothing_raised("Could not convert resource with undef to hash") do hash = res.to_hash end assert_nil(hash[:owner], "got a value for an undef parameter") end # #643 - Make sure virtual defines result in virtual resources def test_virtual_defines parser = mkparser define = parser.newdefine("yayness", :code => resourcedef("file", varref("name"), "mode" => "644")) config = mkconfig(parser) res = mkresource :type => "yayness", :title => "foo", :params => {}, :scope => config.topscope res.virtual = true result = nil assert_nothing_raised("Could not evaluate defined resource") do result = res.evaluate end scope = res.scope newres = scope.findresource("File[foo]") assert(newres, "Could not find resource") assert(newres.virtual?, "Virtual defined resource generated non-virtual resources") # Now try it with exported resources res = mkresource :type => "yayness", :title => "bar", :params => {}, :scope => config.topscope res.exported = true result = nil assert_nothing_raised("Could not evaluate exported resource") do result = res.evaluate end scope = res.scope newres = scope.findresource("File[bar]") assert(newres, "Could not find resource") assert(newres.exported?, "Exported defined resource generated non-exported resources") assert(newres.virtual?, "Exported defined resource generated non-virtual resources") end end # $Id$ diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 5fb11e8cd..c3c60e77f 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -1,512 +1,524 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppet/network/handler' require 'puppettest' class TestSnippets < Test::Unit::TestCase include PuppetTest - include ObjectSpace def setup super @file = Puppet::Type.type(:file) end def self.snippetdir PuppetTest.datadir "snippets" end def assert_file(path, msg = nil) unless file = @file[path] msg ||= "Could not find file %s" % path raise msg end end def assert_mode_equal(mode, path) unless file = @file[path] raise "Could not find file %s" % path end unless mode == file.should(:mode) raise "Mode for %s is incorrect: %o vs %o" % [path, mode, file.should(:mode)] end end def snippet(name) File.join(self.class.snippetdir, name) end def file2ast(file) parser = Puppet::Parser::Parser.new() parser.file = file ast = parser.parse return ast end def snippet2ast(text) parser = Puppet::Parser::Parser.new() parser.string = text ast = parser.parse return ast end def client args = { :Listen => false } Puppet::Network::Client.new(args) end def ast2scope(ast) interp = Puppet::Parser::Interpreter.new( :ast => ast, :client => client() ) scope = Puppet::Parser::Scope.new() ast.evaluate(scope) return scope end def scope2objs(scope) objs = scope.to_trans end def snippet2scope(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) end def snippet2objs(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) objs = scope2objs(scope) end def properties(type) properties = type.validproperties end def metaparams(type) mparams = [] Puppet::Type.eachmetaparam { |param| mparams.push param } mparams end def params(type) params = [] type.parameters.each { |name,property| params.push name } params end def randthing(thing,type) list = self.send(thing,type) list[rand(list.length)] end def randeach(type) [:properties, :metaparams, :params].collect { |thing| randthing(thing,type) } end @@snippets = { true => [ %{File { mode => 755 }} ], } def disabled_test_defaults Puppet::Type.eachtype { |type| next if type.name == :puppet or type.name == :component rands = randeach(type) name = type.name.to_s.capitalize [0..1, 0..2].each { |range| params = rands[range] paramstr = params.collect { |param| "%s => fake" % param }.join(", ") str = "%s { %s }" % [name, paramstr] scope = nil assert_nothing_raised { scope = snippet2scope(str) } defaults = nil assert_nothing_raised { defaults = scope.lookupdefaults(name) } p defaults params.each { |param| puts "%s => '%s'" % [name,param] assert(defaults.include?(param)) } } } end # this is here in case no tests get defined; otherwise we get a warning def test_nothing end def snippet_filecreate %w{a b c d}.each { |letter| path = "/tmp/create%stest" % letter assert_file(path) if %w{a b}.include?(letter) assert_mode_equal(0755, path) end } end def snippet_simpledefaults path = "/tmp/defaulttest" assert_file(path) assert_mode_equal(0755, path) end def snippet_simpleselector files = %w{a b c d}.collect { |letter| path = "/tmp/snippetselect%stest" % letter assert_file(path) assert_mode_equal(0755, path) } end def snippet_classpathtest path = "/tmp/classtest" file = @file[path] assert(file, "did not create file %s" % path) assert_nothing_raised { assert_equal( "//testing/component[componentname]/File[/tmp/classtest]", file.path) } end def snippet_argumentdefaults path1 = "/tmp/argumenttest1" path2 = "/tmp/argumenttest2" file1 = @file[path1] file2 = @file[path2] assert_file(path1) assert_mode_equal(0755, path1) assert_file(path2) assert_mode_equal(0644, path2) end def snippet_casestatement paths = %w{ /tmp/existsfile /tmp/existsfile2 /tmp/existsfile3 /tmp/existsfile4 /tmp/existsfile5 } paths.each { |path| file = @file[path] assert(file, "File %s is missing" % path) assert_mode_equal(0755, path) } end def snippet_implicititeration paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration%stest" % l } paths.each { |path| file = @file[path] assert_file(path) assert_mode_equal(0755, path) } end def snippet_multipleinstances paths = %w{a b c}.collect { |l| "/tmp/multipleinstances%s" % l } paths.each { |path| assert_file(path) assert_mode_equal(0755, path) } end def snippet_namevartest file = "/tmp/testfiletest" dir = "/tmp/testdirtest" assert_file(file) assert_file(dir) assert_equal(:directory, @file[dir].should(:ensure), "Directory is not set to be a directory") end def snippet_scopetest file = "/tmp/scopetest" assert_file(file) assert_mode_equal(0755, file) end def snippet_failmissingexecpath file = "/tmp/exectesting1" execfile = "/tmp/execdisttesting" assert_file(file) assert_nil(Puppet::Type.type(:exec)["exectest"], "invalid exec was created") end def snippet_selectorvalues nums = %w{1 2 3 4 5} files = nums.collect { |n| "/tmp/selectorvalues%s" % n } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_singleselector nums = %w{1 2 3} files = nums.collect { |n| "/tmp/singleselector%s" % n } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_falsevalues file = "/tmp/falsevaluesfalse" assert_file(file) end def disabled_snippet_classargtest [1,2].each { |num| file = "/tmp/classargtest%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_classheirarchy [1,2,3].each { |num| file = "/tmp/classheir%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_singleary [1,2,3,4].each { |num| file = "/tmp/singleary%s" % num assert_file(file) } end def snippet_classincludes [1,2,3].each { |num| file = "/tmp/classincludes%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_componentmetaparams ["/tmp/component1", "/tmp/component2"].each { |file| assert_file(file) } end def snippet_aliastest %w{/tmp/aliastest /tmp/aliastest2 /tmp/aliastest3}.each { |file| assert_file(file) } end def snippet_singlequote { 1 => 'a $quote', 2 => 'some "\yayness\"' }.each { |count, str| path = "/tmp/singlequote%s" % count assert_file(path) assert_equal(str, @file[path].should(:content)) } end # There's no way to actually retrieve the list of classes from the # transaction. def snippet_tag end # Make sure that set tags are correctly in place, yo. def snippet_tagged tags = {"testing" => true, "yayness" => false, "both" => false, "bothtrue" => true, "define" => true} tags.each do |tag, retval| assert_file("/tmp/tagged#{tag}#{retval.to_s}") end end def snippet_defineoverrides file = "/tmp/defineoverrides1" assert_file(file) assert_mode_equal(0755, file) end def snippet_deepclassheirarchy 5.times { |i| i += 1 file = "/tmp/deepclassheir%s" % i assert_file(file) } end def snippet_emptyclass # There's nothing to check other than that it works end def snippet_emptyexec assert(Puppet::Type.type(:exec)["touch /tmp/emptyexectest"], "Did not create exec") end def snippet_multisubs path = "/tmp/multisubtest" assert_file(path) file = @file[path] assert_equal("sub2", file.should(:content), "sub2 did not override content") assert_mode_equal(0755, path) end def snippet_collection assert_file("/tmp/colltest1") assert_nil(@file["/tmp/colltest2"], "Incorrectly collected file") end def snippet_virtualresources %w{1 2 3 4}.each do |num| assert_file("/tmp/virtualtest#{num}") end end def snippet_componentrequire %w{1 2}.each do |num| assert_file("/tmp/testing_component_requires#{num}", "#{num} does not exist") end end def snippet_realize_defined_types assert_file("/tmp/realize_defined_test1") assert_file("/tmp/realize_defined_test2") end def snippet_fqparents assert_file("/tmp/fqparent1", "Did not make file from parent class") assert_file("/tmp/fqparent2", "Did not make file from subclass") end def snippet_fqdefinition assert_file("/tmp/fqdefinition", "Did not make file from fully-qualified definition") end def snippet_subclass_name_duplication assert_file("/tmp/subclass_name_duplication1", "Did not make first file from duplicate subclass names") assert_file("/tmp/subclass_name_duplication2", "Did not make second file from duplicate subclass names") end # Iterate across each of the snippets and create a test. Dir.entries(snippetdir).sort.each { |file| next if file =~ /^\./ mname = "snippet_" + file.sub(/\.pp$/, '') if self.method_defined?(mname) #eval("alias %s %s" % [testname, mname]) testname = ("test_" + mname).intern self.send(:define_method, testname) { + facts = { + "hostname" => "testhost", + "domain" => "domain.com", + "ipaddress" => "127.0.0.1", + "fqdn" => "testhost.domain.com" + } + Facter.stubs(:each) + facts.each do |name, value| + Facter.stubs(:value).with(name).returns(value) + end # first parse the file server = Puppet::Network::Handler.master.new( :Manifest => snippet(file), :Local => true ) + server.send(:fact_handler).stubs(:set) + server.send(:fact_handler).stubs(:get).returns(facts) client = Puppet::Network::Client.master.new( :Master => server, :Cache => false ) + client.class.stubs(:facts).returns(facts) assert(client.local) assert_nothing_raised { client.getconfig() } client = Puppet::Network::Client.master.new( :Master => server, :Cache => false ) assert(client.local) # Now do it again Puppet::Type.allclear assert_nothing_raised { client.getconfig() } #assert_nothing_raised { # trans = client.apply() #} Puppet::Type.eachtype { |type| type.each { |obj| # don't worry about this for now #unless obj.name == "puppet[top]" or # obj.is_a?(Puppet.type(:schedule)) # assert(obj.parent, "%s has no parent" % obj.name) #end assert(obj.name) } } assert_nothing_raised { self.send(mname) } client.clear } mname = mname.intern end } end # $Id$