.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
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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).
-
-
-
-
-----------------
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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``.
-
-
-
-
-----------------
-
-
-
-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).
-
-
-
-
-----------------
-
-
-
-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).
-
-
-
-
-----------------
-
-
-
-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``.
-
-
-
-
-----------------
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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+$)``.
-
-
-
-
-----------------
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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``.
-
-
-
-
-----------------
-
-
-
-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``).
-
-
-
-
-----------------
-
-
-
-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``.
-
-
-
-
-----------------
-
-
-
-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.
-
-
-
-
-----------------
-
-
-
-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]+)``.
-
-
-
-
-----------------
-
-
-
-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$