diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 4f9bb7cf1..1b4e0ccd1 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -1,38 +1,38 @@ --- packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' cows: 'base-lucid-i386.cow base-precise-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-trusty-i386.cow base-wheezy-i386.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs final_mocks: 'pl-el-5-i386 pl-el-6-i386 pl-el-7-x86_64 pl-fedora-19-i386 pl-fedora-20-i386' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE build_dmg: TRUE build_msi: puppet_for_the_win: - ref: '4eb71b5b063f611eb447d561d51481831a66b5dd' + ref: '2476cd007b1b52fb1b9f22202b56218629626b5c' repo: 'git://github.com/puppetlabs/puppet_for_the_win.git' facter: ref: 'refs/tags/2.3.0' repo: 'git://github.com/puppetlabs/facter.git' hiera: ref: 'refs/tags/1.3.4' repo: 'git://github.com/puppetlabs/hiera.git' sys: ref: x86: '8db9d84da9950760144b5dfcd807213eecee4842' x64: '12030f11e9bb2f085c68108bff34be6956b25df9' repo: 'git://github.com/puppetlabs/puppet-win32-ruby.git' apt_host: 'apt.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com' apt_repo_path: '/opt/repository/incoming' ips_repo: '/var/pkgrepo' ips_store: '/opt/repository' ips_host: 'solaris-11-ips-repo.acctest.dc1.puppetlabs.net' tar_host: 'downloads.puppetlabs.com' diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 7443fe663..abbc728fa 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,2107 +1,2115 @@ module Puppet def self.default_diffargs if (Facter.value(:kernel) == "AIX" && Facter.value(:kernelmajversion) == "5300") "" else "-u" end end ############################################################################################ # NOTE: For information about the available values for the ":type" property of settings, # see the docs for Settings.define_settings ############################################################################################ AS_DURATION = %q{This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y).} STORECONFIGS_ONLY = %q{This setting is only used by the ActiveRecord storeconfigs and inventory backends, which are deprecated.} # This is defined first so that the facter implementation is replaced before other setting defaults are evaluated. define_settings(:main, :cfacter => { :default => false, :type => :boolean, :desc => 'Whether or not to use the native facter (cfacter) implementation instead of the Ruby one (facter). Defaults to false.', :hook => proc do |value| return unless value raise ArgumentError, 'facter has already evaluated facts.' if Facter.instance_variable_get(:@collection) raise ArgumentError, 'cfacter version 0.2.0 or later is not installed.' unless Puppet.features.cfacter? CFacter.initialize end } ) define_settings(:main, :confdir => { :default => nil, :type => :directory, :desc => "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, it defaults to being in the user's home directory.", }, :vardir => { :default => nil, :type => :directory, :owner => "service", :group => "service", :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", }, ### NOTE: this setting is usually being set to a symbol value. We don't officially have a ### setting type for that yet, but we might want to consider creating one. :name => { :default => nil, :desc => "The name of the application, if we are running as one. The default is essentially $0 without the path or `.rb`.", } ) define_settings(:main, :logdir => { :default => nil, :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The directory in which to store log files", }, :log_level => { :default => 'notice', :type => :enum, :values => ["debug","info","notice","warning","err","alert","emerg","crit"], :desc => "Default logging level for messages from Puppet. Allowed values are: * debug * info * notice * warning * err * alert * emerg * crit ", }, :disable_warnings => { :default => [], :type => :array, :desc => "A comma-separated list of warning types to suppress. If large numbers of warnings are making Puppet's logs too large or difficult to use, you can temporarily silence them with this setting. If you are preparing to upgrade Puppet to a new major version, you should re-enable all warnings for a while. Valid values for this setting are: * `deprecations` --- disables deprecation warnings.", :hook => proc do |value| values = munge(value) valid = %w[deprecations] invalid = values - (values & valid) if not invalid.empty? raise ArgumentError, "Cannot disable unrecognized warning types #{invalid.inspect}. Valid values are #{valid.inspect}." end end } ) define_settings(:main, :priority => { :default => nil, :type => :priority, :desc => "The scheduling priority of the process. Valid values are 'high', 'normal', 'low', or 'idle', which are mapped to platform-specific values. The priority can also be specified as an integer value and will be passed as is, e.g. -5. Puppet must be running as a privileged user in order to increase scheduling priority.", }, :trace => { :default => false, :type => :boolean, :desc => "Whether to print stack traces on some errors", }, :profile => { :default => false, :type => :boolean, :desc => "Whether to enable experimental performance profiling", }, :autoflush => { :default => true, :type => :boolean, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => { :default => "daemon", :desc => "What syslog facility to use when logging to syslog. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up." }, :statedir => { :default => "$vardir/state", :type => :directory, :mode => "01755", :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => nil, :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "Where Puppet PID files are kept." }, :genconfig => { :default => false, :type => :boolean, :desc => "When true, causes Puppet applications to print an example config file to stdout and exit. The example will include descriptions of each setting, and the current (or default) value of each setting, incorporating any settings overridden on the CLI (with the exception of `genconfig` itself). This setting only makes sense when specified on the command line as `--genconfig`.", }, :genmanifest => { :default => false, :type => :boolean, :desc => "Whether to just print a manifest to stdout and exit. Only makes sense when specified on the command line as `--genmanifest`. Takes into account arguments specified on the CLI.", }, :configprint => { :default => "", :desc => "Print the value of a specific configuration setting. If the name of a setting is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'.", }, :color => { :default => "ansi", :type => :string, :desc => "Whether to use colors when logging to the console. Valid values are `ansi` (equivalent to `true`), `html`, and `false`, which produces no color. Defaults to false on Windows, as its console does not support ansi colors.", }, :mkusers => { :default => false, :type => :boolean, :desc => "Whether to create the necessary user and group that puppet agent will run as.", }, :manage_internal_file_permissions => { :default => true, :type => :boolean, :desc => "Whether Puppet should manage the owner, group, and mode of files it uses internally", }, :onetime => { :default => false, :type => :boolean, :desc => "Perform one configuration run and exit, rather than spawning a long-running daemon. This is useful for interactively running puppet agent, or running puppet agent from cron.", :short => 'o', }, :path => { :default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process.", :call_hook => :on_define_and_write, :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) Puppet::Util::Platform.default_paths.each do |path| ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) end value end }, :libdir => { :type => :directory, :default => "$vardir/lib", :desc => "An extra search path for Puppet. This is only useful for those files that Puppet will load on demand, and is only guaranteed to work for those cases. In fact, the autoload mechanism is responsible for making sure this directory is in Ruby's search path\n", :call_hook => :on_initialize_and_write, :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => { :default => false, :type => :boolean, :desc => "If true, allows the parser to continue without requiring all files referenced with `import` statements to exist. This setting was primarily designed for use with commit hooks for parse-checking.", }, :environment => { :default => "production", :desc => "The environment Puppet is running in. For clients (e.g., `puppet agent`) this determines the environment itself, which is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes we know nothing about." }, :environmentpath => { :default => "", :desc => "A search path for directory environments, as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) This setting must have a value set to enable **directory environments.** The recommended value is `$confdir/environments`. For more details, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :type => :path, }, :always_cache_features => { :type => :boolean, :default => false, :desc => <<-'EOT' Affects how we cache attempts to load Puppet 'features'. If false, then calls to `Puppet.features.?` will always attempt to load the feature (which can be an expensive operation) unless it has already been loaded successfully. This makes it possible for a single agent run to, e.g., install a package that provides the underlying capabilities for a feature, and then later load that feature during the same run (even if the feature had been tested earlier and had not been available). If this setting is set to true, then features will only be checked once, and if they are not available, the negative result is cached and returned for all subsequent attempts to load the feature. This behavior is almost always appropriate for the server, and can result in a significant performance improvement for features that are checked frequently. EOT }, :diff_args => { :default => lambda { default_diffargs }, :desc => "Which arguments to pass to the diff command when printing differences between files. The command to use can be chosen with the `diff` setting.", }, :diff => { :default => (Puppet.features.microsoft_windows? ? "" : "diff"), :desc => "Which diff command to use when printing differences between files. This setting has no default value on Windows, as standard `diff` is not available, but Puppet can use many third-party diff tools.", }, :show_diff => { :type => :boolean, :default => false, :desc => "Whether to log and report a contextual diff when files are being replaced. This causes partial file contents to pass through Puppet's normal logging and reporting system, so this setting should be used with caution if you are sending Puppet's reports to an insecure destination. This feature currently requires the `diff/lcs` Ruby library.", }, :daemonize => { :type => :boolean, :default => (Puppet.features.microsoft_windows? ? false : true), :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, and to false on Windows (where Puppet currently cannot daemonize).", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? raise "Cannot daemonize on Windows" end end }, :maximum_uid => { :default => 4294967290, :desc => "The maximum allowed UID. Some platforms use negative UIDs but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens.", }, :route_file => { :default => "$confdir/routes.yaml", :desc => "The YAML file containing indirector route configuration.", }, :node_terminus => { :type => :terminus, :default => "plain", :desc => "Where to find information about nodes.", }, :node_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached nodes. Valid values are (none), 'json', 'msgpack', 'yaml' or write only yaml ('write_only_yaml'). The master application defaults to 'write_only_yaml', all others to none.", }, :data_binding_terminus => { :type => :terminus, :default => "hiera", :desc => "Where to retrive information about data.", }, :hiera_config => { :default => "$confdir/hiera.yaml", :desc => "The hiera configuration file. Puppet only reads this file on startup, so you must restart the puppet master every time you edit it.", :type => :file, }, :binder => { :default => false, :desc => "Turns the binding system on or off. This includes bindings in modules. The binding system aggregates data from modules and other locations and makes them available for lookup. The binding system is experimental and any or all of it may change.", :type => :boolean, }, :binder_config => { :default => nil, :desc => "The binder configuration file. Puppet reads this file on each request to configure the bindings system. If set to nil (the default), a $confdir/binder_config.yaml is optionally loaded. If it does not exists, a default configuration is used. If the setting :binding_config is specified, it must reference a valid and existing yaml file.", :type => :file, }, :catalog_terminus => { :type => :terminus, :default => "compiler", :desc => "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store.", }, :catalog_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached catalogs. Valid values are 'json', 'msgpack' and 'yaml'. The agent application defaults to 'json'." }, :facts_terminus => { :default => 'facter', :desc => "The node facts terminus.", :call_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node/facts' # Cache to YAML if we're uploading facts away if %w[rest inventory_service].include? value.to_s Puppet.info "configuring the YAML fact cache because a remote terminus is active" Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => { :type => :terminus, :default => "$facts_terminus", :desc => "Should usually be the same as the facts terminus", }, :default_file_terminus => { :type => :terminus, :default => "rest", :desc => "The default source for files if no server is given in a uri, e.g. puppet:///file. The default of `rest` causes the file to be retrieved using the `server` setting. When running `apply` the default is `file_server`, causing requests to be filled locally." }, :httplog => { :default => "$logdir/http.log", :type => :file, :owner => "root", :mode => "0640", :desc => "Where the puppet agent web server logs.", }, :http_proxy_host => { :default => "none", :desc => "The HTTP proxy host to use for outgoing connections. Note: You may need to use a FQDN for the server hostname when using a proxy. Environment variable http_proxy or HTTP_PROXY will override this value", }, :http_proxy_port => { :default => 3128, :desc => "The HTTP proxy port to use for outgoing connections", }, :http_proxy_user => { :default => "none", :desc => "The user name for an authenticated HTTP proxy. Requires the `http_proxy_host` setting.", }, :http_proxy_password =>{ :default => "none", :hook => proc do |value| if Puppet.settings[:http_proxy_password] =~ /[@!# \/]/ raise "Passwords set in the http_proxy_password setting must be valid as part of a URL, and any reserved characters must be URL-encoded. We received: #{value}" end end, :desc => "The password for the user of an authenticated HTTP proxy. Requires the `http_proxy_user` setting. Note that passwords must be valid when used as part of a URL. If a password contains any characters with special meanings in URLs (as specified by RFC 3986 section 2.2), they must be URL-encoded. (For example, `#` would become `%23`.)", }, :http_keepalive_timeout => { :default => "4s", :type => :duration, :desc => "The maximum amount of time a persistent HTTP connection can remain idle in the connection pool, before it is closed. This timeout should be shorter than the keepalive timeout used on the HTTP server, e.g. Apache KeepAliveTimeout directive. #{AS_DURATION}" }, :http_debug => { :default => false, :type => :boolean, :desc => "Whether to write HTTP request and responses to stderr. This should never be used in a production environment." }, :filetimeout => { :default => "15s", :type => :duration, :desc => "The minimum time to wait between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk. #{AS_DURATION}", }, :environment_timeout => { :default => "0", :type => :ttl, :desc => "How long the Puppet master should cache data it loads from an environment. #{AS_DURATION} A value of `0` will disable caching. This setting can also be set to `unlimited`, which will cache environments until the master is restarted or told to refresh the cache. You should change this setting once your Puppet deployment is doing non-trivial work. We chose the default value of `0` because it lets new users update their code without any extra steps, but it lowers the performance of your Puppet master. We recommend setting this to `unlimited` and explicitly refreshing your Puppet master as part of your code deployment process. * With Puppet Server, you should refresh environments by calling the `environment-cache` API endpoint. See the docs for the Puppet Server administrative API. * With a Rack Puppet master, you should restart the web server or the application server. Passenger lets you touch a `restart.txt` file to refresh an application without restarting Apache; see the Passenger docs for details. We don't recommend using any value other than `0` or `unlimited`, since most Puppet masters use a pool of Ruby interpreters which all have their own cache timers. When these timers drift out of sync, agents can be served inconsistent catalogs." }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_source => { :default => "stomp://localhost:61613/", :desc => "Which type of queue to use for asynchronous processing. If your stomp server requires authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1", }, :async_storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppet queue` be running.", :hook => proc do |value| if value # This reconfigures the termini for Node, Facts, and Catalog Puppet.settings.override_default(:storeconfigs, true) # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue Puppet.settings.override_default(:catalog_cache_terminus, :queue) else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => { :default => false, :type => :boolean, :desc => "Boolean; whether Puppet should store only facts and exported resources in the storeconfigs database. This will improve the performance of exported resources with the older `active_record` backend, but will disable external tools that search the storeconfigs database. Thinning catalogs is generally unnecessary when using PuppetDB to store catalogs.", :hook => proc do |value| Puppet.settings.override_default(:storeconfigs, true) if value end }, :config_version => { :default => "", :desc => "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server. Setting a global value for config_version in puppet.conf is deprecated. Please set a per-environment value in environment.conf instead. For more info, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :deprecated => :allowed_on_commandline, }, :zlib => { :default => true, :type => :boolean, :desc => "Boolean; whether to use the zlib library", }, :prerun_command => { :default => "", :desc => "A command to run before every agent run. If this command returns a non-zero return code, the entire Puppet run will fail.", }, :postrun_command => { :default => "", :desc => "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run.", }, :freeze_main => { :default => false, :type => :boolean, :desc => "Freezes the 'main' class, disallowing any code to be added to it. This essentially means that you can't have any code outside of a node, class, or definition other than in the site manifest.", }, :stringify_facts => { :default => true, :type => :boolean, :desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or hashes as fact values. (DEPRECATED) This option will be removed in Puppet 4.0.", }, :trusted_node_data => { :default => false, :type => :boolean, :desc => "Stores trusted node data in a hash called $trusted. When true also prevents $trusted from being overridden in any scope.", }, :immutable_node_data => { :default => '$trusted_node_data', :type => :boolean, :desc => "When true, also prevents $trusted and $facts from being overridden in any scope", + }, + :preview_outputdir => { + :default => '$vardir/preview', + :type => :directory, + :mode => "0750", + :owner => "service", + :group => "service", + :desc => "The directory where catalog previews per node are generated." } ) Puppet.define_settings(:module_tool, :module_repository => { :default => 'https://forgeapi.puppetlabs.com', :desc => "The module repository", }, :module_working_dir => { :default => '$vardir/puppet-module', :desc => "The directory into which module tool data is stored", }, :module_skeleton_dir => { :default => '$module_working_dir/skeleton', :desc => "The directory which the skeleton for module tool generate is stored.", }, :forge_authorization => { :default => nil, :desc => "The authorization key to connect to the Puppet Forge. Leave blank for unauthorized or license based connections", }, :module_groups => { :default => nil, :desc => "Extra module groups to request from the Puppet Forge", } ) Puppet.define_settings( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for # manipulating naming. :certname => { :default => lambda { Puppet::Settings.default_certname.downcase }, :desc => "The name to use when handling certificates. When a node requests a certificate from the CA puppet master, it uses the value of the `certname` setting as its requested Subject CN. This is the name used when managing a node's permissions in [auth.conf](http://docs.puppetlabs.com/puppet/latest/reference/config_file_auth.html). In most cases, it is also used as the node's name when matching [node definitions](http://docs.puppetlabs.com/puppet/latest/reference/lang_node_definitions.html) and requesting data from an ENC. (This can be changed with the `node_name_value` and `node_name_fact` settings, although you should only do so if you have a compelling reason.) A node's certname is available in Puppet manifests as `$trusted['certname']`. (See [Facts and Built-In Variables](http://docs.puppetlabs.com/puppet/latest/reference/lang_facts_and_builtin_vars.html) for more details.) * For best compatibility, you should limit the value of `certname` to only use letters, numbers, periods, underscores, and dashes. (That is, it should match `/\A[a-z0-9._-]+\Z/`.) * The special value `ca` is reserved, and can't be used as the certname for a normal node. Defaults to the node's fully qualified domain name.", :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, :certdnsnames => { :default => '', :hook => proc do |value| unless value.nil? or value == '' then Puppet.warning < < { :default => '', :desc => < { :default => "$confdir/csr_attributes.yaml", :type => :file, :desc => < { :default => "$ssldir/certs", :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :type => :directory, :mode => "0771", :owner => "service", :group => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :type => :file, :mode => "0640", :owner => "service", :group => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :type => :file, :mode => "0640", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where each client stores the CA certificate." }, :ssl_client_ca_auth => { :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Certificate authorities who issue server certificates. SSL servers will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :ssl_server_ca_auth => { :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Certificate authorities who issue client certificates. SSL clients will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :hostcrl => { :default => "$ssldir/crl.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :certificate_revocation => { :default => true, :type => :boolean, :desc => "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) to all clients. If enabled, CA chaining will almost definitely not work.", }, :certificate_expire_warning => { :default => "60d", :type => :duration, :desc => "The window of time leading up to a certificate's expiration that a notification will be logged. This applies to CA, master, and agent certificates. #{AS_DURATION}" }, :digest_algorithm => { :default => 'md5', :type => :enum, :values => ["md5", "sha256"], :desc => 'Which digest algorithm to use for file resources and the filebucket. Valid values are md5, sha256. Default is md5.', } ) define_settings( :ca, :ca_name => { :default => "Puppet CA: $certname", :desc => "The name to use the Certificate Authority certificate.", }, :cadir => { :default => "$ssldir/ca", :type => :directory, :owner => "service", :group => "service", :mode => "0755", :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :type => :file, :owner => "service", :group => "service", :mode => "0640", :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", }, :caprivatedir => { :default => "$cadir/private", :type => :directory, :owner => "service", :group => "service", :mode => "0750", :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :type => :directory, :owner => "service", :group => "service", :mode => "0755", :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :type => :directory, :owner => "service", :group => "service", :mode => "0755", :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :type => :file, :owner => "service", :group => "service", :mode => "0640", :desc => "Where the CA stores the password for the private key." }, :serial => { :default => "$cadir/serial", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", :type => :autosign, :desc => "Whether (and how) to autosign certificate requests. This setting is only relevant on a puppet master acting as a certificate authority (CA). Valid values are true (autosigns all certificate requests; not recommended), false (disables autosigning certificates), or the absolute path to a file. The file specified in this setting may be either a **configuration file** or a **custom policy executable.** Puppet will automatically determine what it is: If the Puppet user (see the `user` setting) can execute the file, it will be treated as a policy executable; otherwise, it will be treated as a config file. If a custom policy executable is configured, the CA puppet master will run it every time it receives a CSR. The executable will be passed the subject CN of the request _as a command line argument,_ and the contents of the CSR in PEM format _on stdin._ It should exit with a status of 0 if the cert should be autosigned and non-zero if the cert should not be autosigned. If a certificate request is not autosigned, it will persist for review. An admin user can use the `puppet cert sign` command to manually sign it, or can delete the request. For info on autosign configuration files, see [the guide to Puppet's config files](http://docs.puppetlabs.com/guides/configuring.html).", }, :allow_duplicate_certs => { :default => false, :type => :boolean, :desc => "Whether to allow a new certificate request to overwrite an existing certificate.", }, :ca_ttl => { :default => "5y", :type => :duration, :desc => "The default TTL for new certificates. #{AS_DURATION}" }, :req_bits => { :default => 4096, :desc => "The bit length of the certificates.", }, :keylength => { :default => 4096, :desc => "The bit length of keys.", }, :cert_inventory => { :default => "$cadir/inventory.txt", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "The inventory file. This is a text file to which the CA writes a complete listing of all certificates." } ) # Define the config default. define_settings(:application, :config_file_name => { :type => :string, :default => Puppet::Settings.default_config_file_name, :desc => "The name of the puppet config file.", }, :config => { :type => :file, :default => "$confdir/${config_file_name}", :desc => "The configuration file for the current puppet application.", }, :pidfile => { :type => :file, :default => "$rundir/${run_mode}.pid", :desc => "The file containing the PID of a running process. This file is intended to be used by service management frameworks and monitoring systems to determine if a puppet process is still in the process table.", }, :bindaddress => { :default => "0.0.0.0", :desc => "The address a listening server should bind to.", } ) define_settings(:master, :user => { :default => "puppet", :desc => "The user puppet master should run as.", }, :group => { :default => "puppet", :desc => "The group puppet master should run as.", }, :manifestdir => { :default => "$confdir/manifests", :type => :directory, :desc => "Used to build the default value of the `manifest` setting. Has no other purpose. This setting is deprecated.", :deprecated => :completely, }, :manifest => { :default => "$manifestdir/site.pp", :type => :file_or_directory, :desc => "The entry-point manifest for puppet master. This can be one file or a directory of manifests to be evaluated in alphabetical order. Puppet manages this path as a directory if one exists or if the path ends with a / or \\. Setting a global value for `manifest` in puppet.conf is deprecated. Please use directory environments instead. If you need to use something other than the environment's `manifests` directory as the main manifest, you can set `manifest` in environment.conf. For more info, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :deprecated => :allowed_on_commandline, }, :default_manifest => { :default => "./manifests", :type => :string, :desc => "The default main manifest for directory environments. Any environment that doesn't set the `manifest` setting in its `environment.conf` file will use this manifest. This setting's value can be an absolute or relative path. An absolute path will make all environments default to the same main manifest; a relative path will allow each environment to use its own manifest, and Puppet will resolve the path relative to each environment's main directory. In either case, the path can point to a single file or to a directory of manifests to be evaluated in alphabetical order.", }, :disable_per_environment_manifest => { :default => false, :type => :boolean, :desc => "Whether to disallow an environment-specific main manifest. When set to `true`, Puppet will use the manifest specified in the `default_manifest` setting for all environments. If an environment specifies a different main manifest in its `environment.conf` file, catalog requests for that environment will fail with an error. This setting requires `default_manifest` to be set to an absolute path.", :hook => proc do |value| if value && !Pathname.new(Puppet[:default_manifest]).absolute? raise(Puppet::Settings::ValidationError, "The 'default_manifest' setting must be set to an absolute path when 'disable_per_environment_manifest' is true") end end, }, :code => { :default => "", :desc => "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet executable.", }, :masterlog => { :default => "$logdir/puppetmaster.log", :type => :file, :owner => "service", :group => "service", :mode => "0660", :desc => "This file is literally never used, although Puppet may create it as an empty file. For more context, see the `puppetdlog` setting and puppet master's `--logdest` command line option. This setting is deprecated and will be removed in a future version of Puppet.", :deprecated => :completely }, :masterhttplog => { :default => "$logdir/masterhttp.log", :type => :file, :owner => "service", :group => "service", :mode => "0660", :create => true, :desc => "Where the puppet master web server saves its access log. This is only used when running a WEBrick puppet master. When puppet master is running under a Rack server like Passenger, that web server will have its own logging behavior." }, :masterport => { :default => 8140, :desc => "The port for puppet master traffic. For puppet master, this is the port to listen on; for puppet agent, this is the port to make requests on. Both applications use this setting to get the port.", }, :node_name => { :default => "cert", :desc => "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)", }, :bucketdir => { :default => "$vardir/bucket", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :rest_authconfig => { :default => "$confdir/auth.conf", :type => :file, :desc => "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained authorization system for `puppet master`.", }, :ca => { :default => true, :type => :boolean, :desc => "Whether the master should function as a certificate authority.", }, :basemodulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :type => :path, :desc => "The search path for **global** modules. Should be specified as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) If you are using directory environments, these are the modules that will be used by _all_ environments. Note that the `modules` directory of the active environment will have priority over any global directories. For more info, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html This setting also provides the default value for the deprecated `modulepath` setting, which is used when directory environments are disabled.", }, :modulepath => { :default => "$basemodulepath", :type => :path, :desc => "The search path for modules, as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) Setting a global value for `modulepath` in puppet.conf is deprecated. Please use directory environments instead. If you need to use something other than the default modulepath of `:$basemodulepath`, you can set `modulepath` in environment.conf. For more info, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :deprecated => :allowed_on_commandline, }, :ssl_client_header => { :default => "HTTP_X_CLIENT_DN", :desc => "The header containing an authenticated client's SSL DN. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). Puppet will parse out the Common Name (CN) from the Distinguished Name (DN) and use the value of the CN field for authorization. Note that the name of the HTTP header gets munged by the web server common gateway inteface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-DN` header, this setting should be `HTTP_X_CLIENT_DN`.", }, :ssl_client_verify_header => { :default => "HTTP_X_CLIENT_VERIFY", :desc => "The header containing the status message of the client verification. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. Note that the name of the HTTP header gets munged by the web server common gateway inteface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-Verify` header, this setting should be `HTTP_X_CLIENT_VERIFY`.", }, # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). :yamldir => { :default => "$vardir/yaml", :type => :directory, :owner => "service", :group => "service", :mode => "0750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, :server_datadir => { :default => "$vardir/server_data", :type => :directory, :owner => "service", :group => "service", :mode => "0750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => { :default => "store", :desc => "The list of report handlers to use. When using multiple report handlers, their names should be comma-separated, with whitespace allowed. (For example, `reports = http, tagmail`.) This setting is relevant to puppet master and puppet apply. The puppet master will call these report handlers with the reports it receives from agent nodes, and puppet apply will call them with its own report. (In all cases, the node applying the catalog must have `report = true`.) See the report reference for information on the built-in report handlers; custom report handlers can also be loaded from modules. (Report handlers are loaded from the lib directory, at `puppet/reports/NAME.rb`.)", }, :reportdir => { :default => "$vardir/reports", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The directory in which to store reports. Each node gets a separate subdirectory in this directory. This setting is only used when the `store` report processor is enabled (see the `reports` setting)."}, :reporturl => { :default => "http://localhost:3000/reports/upload", :desc => "The URL that reports should be forwarded to. This setting is only used when the `http` report processor is enabled (see the `reports` setting).", }, :fileserverconfig => { :default => "$confdir/fileserver.conf", :type => :file, :desc => "Where the fileserver configuration is stored.", }, :strict_hostname_checking => { :default => false, :desc => "Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs.", } ) define_settings(:metrics, :rrddir => { :type => :directory, :default => "$vardir/rrd", :mode => "0750", :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, :rrdinterval => { :default => "$runinterval", :type => :duration, :desc => "How often RRD should expect data. This should match how often the hosts report back to the server. #{AS_DURATION}", } ) define_settings(:device, :devicedir => { :default => "$vardir/devices", :type => :directory, :mode => "0750", :desc => "The root directory of devices' $vardir.", }, :deviceconfig => { :default => "$confdir/device.conf", :desc => "Path to the device config file for puppet device.", } ) define_settings(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :localconfig => { :default => "$statedir/localconfig", :type => :file, :owner => "root", :mode => "0660", :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, :statefile => { :default => "$statedir/state.yaml", :type => :file, :mode => "0660", :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, :clientyamldir => { :default => "$vardir/client_yaml", :type => :directory, :mode => "0750", :desc => "The directory in which client-side YAML data is stored." }, :client_datadir => { :default => "$vardir/client_data", :type => :directory, :mode => "0750", :desc => "The directory in which serialized data is stored on the client." }, :classfile => { :default => "$statedir/classes.txt", :type => :file, :owner => "root", :mode => "0640", :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, :resourcefile => { :default => "$statedir/resources.txt", :type => :file, :owner => "root", :mode => "0640", :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :puppetdlog => { :default => "$logdir/puppetd.log", :type => :file, :owner => "root", :mode => "0640", :desc => "The fallback log file. This is only used when the `--logdest` option is not specified AND Puppet is running on an operating system where both the POSIX syslog service and the Windows Event Log are unavailable. (Currently, no supported operating systems match that description.) Despite the name, both puppet agent and puppet master will use this file as the fallback logging destination. For control over logging destinations, see the `--logdest` command line option in the manual pages for puppet master, puppet agent, and puppet apply. You can see man pages by running `puppet --help`, or read them online at http://docs.puppetlabs.com/references/latest/man/." }, :server => { :default => "puppet", :desc => "The puppet master server to which the puppet agent should connect." }, :use_srv_records => { :default => false, :type => :boolean, :desc => "Whether the server will search for SRV records in DNS for the current domain.", }, :srv_domain => { :default => lambda { Puppet::Settings.domain_fact }, :desc => "The domain which will be queried to find the SRV records of servers to use.", }, :ignoreschedules => { :default => false, :type => :boolean, :desc => "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs.", }, :default_schedules => { :default => true, :type => :boolean, :desc => "Boolean; whether to generate the default schedule resources. Setting this to false is useful for keeping external report processors clean of skipped schedule resources.", }, :puppetport => { :default => 8139, :desc => "Which port puppet agent listens on.", }, :noop => { :default => false, :type => :boolean, :desc => "Whether to apply catalogs in noop mode, which allows Puppet to partially simulate a normal run. This setting affects puppet agent and puppet apply. When running in noop mode, Puppet will check whether each resource is in sync, like it does when running normally. However, if a resource attribute is not in the desired state (as declared in the catalog), Puppet will take no action, and will instead report the changes it _would_ have made. These simulated changes will appear in the report sent to the puppet master, or be shown on the console if running puppet agent or puppet apply in the foreground. The simulated changes will not send refresh events to any subscribing or notified resources, although Puppet will log that a refresh event _would_ have been sent. **Important note:** [The `noop` metaparameter](http://docs.puppetlabs.com/references/latest/metaparameter.html#noop) allows you to apply individual resources in noop mode, and will override the global value of the `noop` setting. This means a resource with `noop => false` _will_ be changed if necessary, even when running puppet agent with `noop = true` or `--noop`. (Conversely, a resource with `noop => true` will only be simulated, even when noop mode is globally disabled.)", }, :runinterval => { :default => "30m", :type => :duration, :desc => "How often puppet agent applies the catalog. Note that a runinterval of 0 means \"run continuously\" rather than \"never run.\" If you want puppet agent to never run, you should start it with the `--no-client` option. #{AS_DURATION}", }, :listen => { :default => false, :type => :boolean, :desc => "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to requests on the `run`, `facts`, `certificate`, and `resource` endpoints.", }, :ca_server => { :default => "$server", :desc => "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale.", }, :ca_port => { :default => "$masterport", :desc => "The port to use for the certificate authority.", }, :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.deprecation_warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings.override_default(:preferred_serialization_format, value) end } }, :preferred_serialization_format => { :default => "pson", :desc => "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it.", }, :report_serialization_format => { :default => "pson", :type => :enum, :values => ["pson", "yaml"], :desc => "The serialization format to use when sending reports to the `report_server`. Possible values are `pson` and `yaml`. This setting affects puppet agent, but not puppet apply (which processes its own reports). This should almost always be set to `pson`. It can be temporarily set to `yaml` to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to 'yaml' automatically if the agent detects an older master, so should never need to be set explicitly." }, :legacy_query_parameter_serialization => { :default => false, :type => :boolean, :desc => "The serialization format to use when sending file_metadata query parameters. Older versions of puppet master expect certain query parameters to be serialized as yaml, which is deprecated. This should almost always be false. It can be temporarily set to true to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to true automatically if the agent detects an older master, so should never need to be set explicitly." }, :agent_catalog_run_lockfile => { :default => "$statedir/agent_catalog_run.lock", :type => :string, # (#2888) Ensure this file is not added to the settings catalog. :desc => "A lock file to indicate that a puppet agent catalog run is currently in progress. The file contains the pid of the process that holds the lock on the catalog run.", }, :agent_disabled_lockfile => { :default => "$statedir/agent_disabled.lock", :type => :file, :desc => "A lock file to indicate that puppet agent runs have been administratively disabled. File contains a JSON object with state information.", }, :usecacheonfailure => { :default => true, :type => :boolean, :desc => "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.", }, :use_cached_catalog => { :default => false, :type => :boolean, :desc => "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired.", }, :ignoremissingtypes => { :default => false, :type => :boolean, :desc => "Skip searching for classes and definitions that were missing during a prior compilation. The list of missing objects is maintained per-environment and persists until the environment is cleared or the master is restarted.", }, :ignorecache => { :default => false, :type => :boolean, :desc => "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts change or if the server changes.", }, :dynamicfacts => { :default => "memorysize,memoryfree,swapsize,swapfree", :desc => "(Deprecated) Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be comma-separated.", :hook => proc { |value| if value Puppet.deprecation_warning "The dynamicfacts setting is deprecated and will be ignored." end } }, :splaylimit => { :default => "$runinterval", :type => :duration, :desc => "The maximum time to delay before runs. Defaults to being the same as the run interval. #{AS_DURATION}", }, :splay => { :default => false, :type => :boolean, :desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before a run.", }, :clientbucketdir => { :default => "$vardir/clientbucket", :type => :directory, :mode => "0750", :desc => "Where FileBucket files are stored locally." }, :configtimeout => { :default => "2m", :type => :duration, :desc => "How long the client should wait for the configuration to be retrieved before considering it a failure. This can help reduce flapping if too many clients contact the server at one time. #{AS_DURATION}", }, :report_server => { :default => "$server", :desc => "The server to send transaction reports to.", }, :report_port => { :default => "$masterport", :desc => "The port to communicate with the report_server.", }, :inventory_server => { :default => "$server", :desc => "The server to send facts to.", }, :inventory_port => { :default => "$masterport", :desc => "The port to communicate with the inventory_server.", }, :report => { :default => true, :type => :boolean, :desc => "Whether to send reports after every transaction.", }, :lastrunfile => { :default => "$statedir/last_run_summary.yaml", :type => :file, :mode => "0644", :desc => "Where puppet agent stores the last run report summary in yaml format." }, :lastrunreport => { :default => "$statedir/last_run_report.yaml", :type => :file, :mode => "0640", :desc => "Where puppet agent stores the last run report in yaml format." }, :graph => { :default => false, :type => :boolean, :desc => "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick).", }, :graphdir => { :default => "$statedir/graphs", :type => :directory, :desc => "Where to store dot-outputted graphs.", }, :http_compression => { :default => false, :type => :boolean, :desc => "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs.", }, :waitforcert => { :default => "2m", :type => :duration, :desc => "How frequently puppet agent should ask for a signed certificate. When starting for the first time, puppet agent will submit a certificate signing request (CSR) to the server named in the `ca_server` setting (usually the puppet master); this may be autosigned, or may need to be approved by a human, depending on the CA server's configuration. Puppet agent cannot apply configurations until its approved certificate is available. Since the certificate may or may not be available immediately, puppet agent will repeatedly try to fetch it at this interval. You can turn off waiting for certificates by specifying a time of 0, in which case puppet agent will exit if it cannot get a cert. #{AS_DURATION}", }, :ordering => { :type => :enum, :values => ["manifest", "title-hash", "random"], :default => "title-hash", :desc => "How unrelated resources should be ordered when applying a catalog. Allowed values are `title-hash`, `manifest`, and `random`. This setting affects puppet agent and puppet apply, but not puppet master. * `title-hash` (the default) will order resources randomly, but will use the same order across runs and across nodes. * `manifest` will use the order in which the resources were declared in their manifest files. * `random` will order resources randomly and change their order with each run. This can work like a fuzzer for shaking out undeclared dependencies. Regardless of this setting's value, Puppet will always obey explicit dependencies set with the before/require/notify/subscribe metaparameters and the `->`/`~>` chaining arrows; this setting only affects the relative ordering of _unrelated_ resources." } ) define_settings(:inspect, :archive_files => { :type => :boolean, :default => false, :desc => "During an inspect run, whether to archive files whose contents are audited to a file bucket.", }, :archive_file_server => { :default => "$server", :desc => "During an inspect run, the file bucket server to archive files to if archive_files is set.", } ) # Plugin information. define_settings( :main, :plugindest => { :type => :directory, :default => "$libdir", :desc => "Where Puppet should store plugins that it pulls down from the central server.", }, :pluginsource => { :default => "puppet://$server/plugins", :desc => "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here.", }, :pluginfactdest => { :type => :directory, :default => "$vardir/facts.d", :desc => "Where Puppet should store external facts that are being handled by pluginsync", }, :pluginfactsource => { :default => "puppet://$server/pluginfacts", :desc => "Where to retrieve external facts for pluginsync", }, :pluginsync => { :default => true, :type => :boolean, :desc => "Whether plugins should be synced with the central server.", }, :pluginsignore => { :default => ".svn CVS .git", :desc => "What files to ignore when pulling down plugins.", } ) # Central fact information. define_settings( :main, :factpath => { :type => :path, :default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should be separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", :call_hook => :on_initialize_and_write, # Call our hook with the default value, so we always get the value added to facter. :hook => proc do |value| paths = value.split(File::PATH_SEPARATOR) Facter.search(*paths) end } ) define_settings( :tagmail, :tagmap => { :default => "$confdir/tagmail.conf", :desc => "The mapping between reporting tags and email addresses.", }, :sendmail => { :default => which('sendmail') || '', :desc => "Where to find the sendmail binary with which to send email.", }, :reportfrom => { :default => lambda { "report@#{Puppet::Settings.default_certname.downcase}" }, :desc => "The 'from' email address for the reports.", }, :smtpserver => { :default => "none", :desc => "The server through which to send email reports.", }, :smtpport => { :default => 25, :desc => "The TCP port through which to send email reports.", }, :smtphelo => { :default => lambda { Facter.value 'fqdn' }, :desc => "The name by which we identify ourselves in SMTP HELO for reports. If you send to a smtpserver which does strict HELO checking (as with Postfix's `smtpd_helo_restrictions` access controls), you may need to ensure this resolves.", } ) define_settings( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :type => :file, :mode => "0660", :owner => "service", :group => "service", :desc => "The sqlite database file. #{STORECONFIGS_ONLY}" }, :dbadapter => { :default => "sqlite3", :desc => "The type of database to use. #{STORECONFIGS_ONLY}", }, :dbmigrate => { :default => false, :type => :boolean, :desc => "Whether to automatically migrate the database. #{STORECONFIGS_ONLY}", }, :dbname => { :default => "puppet", :desc => "The name of the database to use. #{STORECONFIGS_ONLY}", }, :dbserver => { :default => "localhost", :desc => "The database server for caching. Only used when networked databases are used.", }, :dbport => { :default => "", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbuser => { :default => "puppet", :desc => "The database user for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbpassword => { :default => "puppet", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbconnections => { :default => '', :desc => "The number of database connections for networked databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}", }, :dbsocket => { :default => "", :desc => "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}", }, :railslog => { :default => "$logdir/rails.log", :type => :file, :mode => "0600", :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent. #{STORECONFIGS_ONLY}" }, :rails_loglevel => { :default => "info", :desc => "The log level for Rails connections. The value must be a valid log level within Rails. Production environments normally use `info` and other environments normally use `debug`. #{STORECONFIGS_ONLY}", } ) define_settings( :couchdb, :couchdb_url => { :default => "http://127.0.0.1:5984/puppet", :desc => "The url where the puppet couchdb database will be created. Only used when `facts_terminus` is set to `couch`.", } ) define_settings( :transaction, :tags => { :default => "", :desc => "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. Values must be comma-separated.", }, :evaltrace => { :default => false, :type => :boolean, :desc => "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done.", }, :summarize => { :default => false, :type => :boolean, :desc => "Whether to print a transaction summary.", } ) define_settings( :main, :external_nodes => { :default => "none", :desc => "An external command that can produce node information. The command's output must be a YAML dump of a hash, and that hash must have a `classes` key and/or a `parameters` key, where `classes` is an array or hash and `parameters` is a hash. For unknown nodes, the command should exit with a non-zero exit code. This command makes it straightforward to store your node mapping information in other data sources like databases.", } ) define_settings( :ldap, :ldapssl => { :default => false, :type => :boolean, :desc => "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side.", }, :ldaptls => { :default => false, :type => :boolean, :desc => "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side.", }, :ldapserver => { :default => "ldap", :desc => "The LDAP server. Only used if `node_terminus` is set to `ldap`.", }, :ldapport => { :default => 389, :desc => "The LDAP port. Only used if `node_terminus` is set to `ldap`.", }, :ldapstring => { :default => "(&(objectclass=puppetClient)(cn=%s))", :desc => "The search string used to find an LDAP node.", }, :ldapclassattrs => { :default => "puppetclass", :desc => "The LDAP attributes to use to define Puppet classes. Values should be comma-separated.", }, :ldapstackedattrs => { :default => "puppetvar", :desc => "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values should be comma-separated.", }, :ldapattrs => { :default => "all", :desc => "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns all attributes.", }, :ldapparentattr => { :default => "parentnode", :desc => "The attribute to use to define the parent node.", }, :ldapuser => { :default => "", :desc => "The user to use to connect to LDAP. Must be specified as a full DN.", }, :ldappassword => { :default => "", :desc => "The password to use to connect to LDAP.", }, :ldapbase => { :default => "", :desc => "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory.", } ) define_settings(:master, :storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. :call_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value if not Puppet.settings[:async_storeconfigs] Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet.settings.override_default(:catalog_cache_terminus, :store_configs) end Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :type => :terminus, :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) define_settings(:parser, :templatedir => { :default => "$vardir/templates", :type => :directory, :desc => "Where Puppet looks for template files. Can be a list of colon-separated directories. This setting is deprecated. Please put your templates in modules instead.", :deprecated => :completely, }, :allow_variables_with_dashes => { :default => false, :desc => <<-'EOT' Permit hyphens (`-`) in variable names and issue deprecation warnings about them. This setting **should always be `false`;** setting it to `true` will cause subtle and wide-ranging bugs. It will be removed in a future version. Hyphenated variables caused major problems in the language, but were allowed between Puppet 2.7.3 and 2.7.14. If you used them during this window, we apologize for the inconvenience --- you can temporarily set this to `true` in order to upgrade, and can rename your variables at your leisure. Please revert it to `false` after you have renamed all affected variables. EOT }, :parser => { :default => "current", :desc => <<-'EOT' Selects the parser to use for parsing puppet manifests (in puppet DSL language/'.pp' files). Available choices are `current` (the default) and `future`. The `current` parser means that the released version of the parser should be used. The `future` parser is a "time travel to the future" allowing early exposure to new language features. What these features are will vary from release to release and they may be invididually configurable. Available Since Puppet 3.2. EOT }, :max_errors => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation errors in case multiple errors have been detected. A value of 0 is the same as a value of 1; a minimum of one error is always raised. The count is per manifest. EOT }, :max_warnings => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation warnings in case multiple warnings have been detected. A value of 0 blocks logging of warnings. The count is per manifest. EOT }, :max_deprecations => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation deprecation warnings in case multiple deprecation warnings have been detected. A value of 0 blocks the logging of deprecation warnings. The count is per manifest. EOT }, :strict_variables => { :default => false, :type => :boolean, :desc => <<-'EOT' Makes the parser raise errors when referencing unknown variables. (This does not affect referencing variables that are explicitly set to undef). EOT } ) define_settings(:puppetdoc, :document_all => { :default => false, :type => :boolean, :desc => "Whether to document all resources when using `puppet doc` to generate manifest documentation.", } ) end diff --git a/lib/puppet/error.rb b/lib/puppet/error.rb index be59d3db6..a9b247c20 100644 --- a/lib/puppet/error.rb +++ b/lib/puppet/error.rb @@ -1,66 +1,92 @@ module Puppet # The base class for all Puppet errors. It can wrap another exception class Error < RuntimeError attr_accessor :original def initialize(message, original=nil) super(message) @original = original end end module ExternalFileError # This module implements logging with a filename and line number. Use this # for errors that need to report a location in a non-ruby file that we # parse. attr_accessor :line, :file, :pos # May be called with 3 arguments for message, file, line, and exception, or # 4 args including the position on the line. # def initialize(message, file=nil, line=nil, pos=nil, original=nil) if pos.kind_of? Exception original = pos pos = nil end super(message, original) @file = file unless (file.is_a?(String) && file.empty?) @line = line @pos = pos end def to_s msg = super @file = nil if (@file.is_a?(String) && @file.empty?) if @file and @line and @pos "#{msg} at #{@file}:#{@line}:#{@pos}" elsif @file and @line "#{msg} at #{@file}:#{@line}" elsif @line and @pos "#{msg} at line #{@line}:#{@pos}" elsif @line "#{msg} at line #{@line}" elsif @file "#{msg} in #{@file}" else msg end end end class ParseError < Puppet::Error include ExternalFileError end class ResourceError < Puppet::Error include ExternalFileError end + # Contains an issue code and can be annotated with an environment and a node + class ParseErrorWithIssue < Puppet::ParseError + attr_reader :issue_code, :basic_message + attr_accessor :environment, :node + + # @param message [String] The error message + # @param file [String] The path to the file where the error was found + # @param line [Integer] The line in the file + # @param pos [Integer] The position on the line + # @param original [Exception] Original exception + # @param issue_code [Symbol] The issue code + # + def initialize(message, file=nil, line=nil, pos=nil, original=nil, issue_code= nil) + super(message, file, line, pos, original) + @issue_code = issue_code + @basic_message = message + end + + def to_s + msg = super + msg = "Could not parse for environment #{environment}: #{msg}" if environment + msg = "#{msg} on node #{node}" if node + msg + end + end + # An error that already contains location information in the message text - class PreformattedError < Puppet::ParseError + class PreformattedError < Puppet::ParseErrorWithIssue end # An error class for when I don't know what happened. Automatically # prints a stack trace when in debug mode. class DevError < Puppet::Error include ExternalFileError end end diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index fa0841cf0..876242923 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,595 +1,599 @@ require 'puppet/util' require 'puppet/util/cacher' require 'monitor' require 'puppet/parser/parser_factory' # Just define it, so this class has fewer load dependencies. class Puppet::Node end # Puppet::Node::Environment acts as a container for all configuration # that is expected to vary between environments. # # ## The root environment # # In addition to normal environments that are defined by the user,there is a # special 'root' environment. It is defined as an instance variable on the # Puppet::Node::Environment metaclass. The environment name is `*root*` and can # be accessed by looking up the `:root_environment` using {Puppet.lookup}. # # The primary purpose of the root environment is to contain parser functions # that are not bound to a specific environment. The main case for this is for # logging functions. Logging functions are attached to the 'root' environment # when {Puppet::Parser::Functions.reset} is called. class Puppet::Node::Environment include Puppet::Util::Cacher NO_MANIFEST = :no_manifest # @api private def self.seen @seen ||= {} end # Create a new environment with the given name, or return an existing one # # The environment class memoizes instances so that attempts to instantiate an # environment with the same name with an existing environment will return the # existing environment. # # @overload self.new(environment) # @param environment [Puppet::Node::Environment] # @return [Puppet::Node::Environment] the environment passed as the param, # this is implemented so that a calling class can use strings or # environments interchangeably. # # @overload self.new(string) # @param string [String, Symbol] # @return [Puppet::Node::Environment] An existing environment if it exists, # else a new environment with that name # # @overload self.new() # @return [Puppet::Node::Environment] The environment as set by # Puppet.settings[:environment] # # @api public def self.new(name = nil) return name if name.is_a?(self) name ||= Puppet.settings.value(:environment) raise ArgumentError, "Environment name must be specified" unless name symbol = name.to_sym return seen[symbol] if seen[symbol] obj = self.create(symbol, split_path(Puppet.settings.value(:modulepath, symbol)), Puppet.settings.value(:manifest, symbol), Puppet.settings.value(:config_version, symbol)) seen[symbol] = obj end # Create a new environment with the given name # # @param name [Symbol] the name of the # @param modulepath [Array] the list of paths from which to load modules # @param manifest [String] the path to the manifest for the environment or # the constant Puppet::Node::Environment::NO_MANIFEST if there is none. # @param config_version [String] path to a script whose output will be added # to report logs (optional) # @return [Puppet::Node::Environment] # # @api public def self.create(name, modulepath, manifest = NO_MANIFEST, config_version = nil) obj = self.allocate obj.send(:initialize, name, expand_dirs(extralibs() + modulepath), manifest == NO_MANIFEST ? manifest : File.expand_path(manifest), config_version) obj end # A "reference" to a remote environment. The created environment instance # isn't expected to exist on the local system, but is instead a reference to # environment information on a remote system. For instance when a catalog is # being applied, this will be used on the agent. # # @note This does not provide access to the information of the remote # environment's modules, manifest, or anything else. It is simply a value # object to pass around and use as an environment. # # @param name [Symbol] The name of the remote environment # def self.remote(name) create(name, [], NO_MANIFEST) end # Instantiate a new environment # # @note {Puppet::Node::Environment.new} is overridden to return memoized # objects, so this will not be invoked with the normal Ruby initialization # semantics. # # @param name [Symbol] The environment name def initialize(name, modulepath, manifest, config_version) @name = name @modulepath = modulepath @manifest = manifest @config_version = config_version # set watching to true for legacy environments - the directory based environment loaders will set this to # false for directory based environments after the environment has been created. @watching = true end # Returns if files are being watched or not. # @api private # def watching? @watching end # Turns watching of files on or off # @param flag [TrueClass, FalseClass] if files should be watched or not # @ api private def watching=(flag) @watching = flag end # Creates a new Puppet::Node::Environment instance, overriding any of the passed # parameters. # # @param env_params [Hash<{Symbol => String,Array}>] new environment # parameters (:modulepath, :manifest, :config_version) # @return [Puppet::Node::Environment] def override_with(env_params) return self.class.create(name, env_params[:modulepath] || modulepath, env_params[:manifest] || manifest, env_params[:config_version] || config_version) end # Creates a new Puppet::Node::Environment instance, overriding manfiest # modulepath, or :config_version from the passed settings if they were # originally set from the commandline, or returns self if there is nothing to # override. # # @param settings [Puppet::Settings] an initialized puppet settings instance # @return [Puppet::Node::Environment] new overridden environment or self if # there are no commandline changes from settings. def override_from_commandline(settings) overrides = {} if settings.set_by_cli?(:modulepath) overrides[:modulepath] = self.class.split_path(settings.value(:modulepath)) end if settings.set_by_cli?(:config_version) overrides[:config_version] = settings.value(:config_version) end if settings.set_by_cli?(:manifest) || (settings.set_by_cli?(:manifestdir) && settings.value(:manifest).start_with?(settings.value(:manifestdir))) overrides[:manifest] = settings.value(:manifest) end overrides.empty? ? self : self.override_with(overrides) end # Retrieve the environment for the current process. # # @note This should only used when a catalog is being compiled. # # @api private # # @return [Puppet::Node::Environment] the currently set environment if one # has been explicitly set, else it will return the '*root*' environment def self.current Puppet.deprecation_warning("Puppet::Node::Environment.current has been replaced by Puppet.lookup(:current_environment), see http://links.puppetlabs.com/current-env-deprecation") Puppet.lookup(:current_environment) end # @param [String] name Environment name to check for valid syntax. # @return [Boolean] true if name is valid # @api public def self.valid_name?(name) !!name.match(/\A\w+\Z/) end # Clear all memoized environments and the 'current' environment # # @api private def self.clear seen.clear end # @!attribute [r] name # @api public # @return [Symbol] the human readable environment name that serves as the # environment identifier attr_reader :name # @api public # @return [Array] All directories present on disk in the modulepath def modulepath @modulepath.find_all do |p| Puppet::FileSystem.directory?(p) end end # @api public # @return [Array] All directories in the modulepath (even if they are not present on disk) def full_modulepath @modulepath end # @!attribute [r] manifest # @api public # @return [String] path to the manifest file or directory. attr_reader :manifest # @!attribute [r] config_version # @api public # @return [String] path to a script whose output will be added to report logs # (optional) attr_reader :config_version # Checks to make sure that this environment did not have a manifest set in # its original environment.conf if Puppet is configured with # +disable_per_environment_manifest+ set true. If it did, the environment's # modules may not function as intended by the original authors, and we may # seek to halt a puppet compilation for a node in this environment. # # The only exception to this would be if the environment.conf manifest is an exact, # uninterpolated match for the current +default_manifest+ setting. # # @return [Boolean] true if using directory environments, and # Puppet[:disable_per_environment_manifest] is true, and this environment's # original environment.conf had a manifest setting that is not the # Puppet[:default_manifest]. # @api public def conflicting_manifest_settings? return false if Puppet[:environmentpath].empty? || !Puppet[:disable_per_environment_manifest] environment_conf = Puppet.lookup(:environments).get_conf(name) original_manifest = environment_conf.raw_setting(:manifest) !original_manifest.nil? && !original_manifest.empty? && original_manifest != Puppet[:default_manifest] end # Return an environment-specific Puppet setting. # # @api public # # @param param [String, Symbol] The environment setting to look up # @return [Object] The resolved setting value def [](param) Puppet.settings.value(param, self.name) end # @api public # @return [Puppet::Resource::TypeCollection] The current global TypeCollection def known_resource_types if @known_resource_types.nil? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import(), '') end @known_resource_types end # Yields each modules' plugin directory if the plugin directory (modulename/lib) # is present on the filesystem. # # @yield [String] Yields the plugin directory from each module to the block. # @api public def each_plugin_directory(&block) modules.map(&:plugin_directory).each do |lib| lib = Puppet::Util::Autoload.cleanpath(lib) yield lib if File.directory?(lib) end end # Locate a module instance by the module name alone. # # @api public # # @param name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module(name) modules.find {|mod| mod.name == name} end # Locate a module instance by the full forge name (EG authorname/module) # # @api public # # @param forge_name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module_by_forge_name(forge_name) author, modname = forge_name.split('/') found_mod = self.module(modname) found_mod and found_mod.forge_name == forge_name ? found_mod : nil end # @!attribute [r] modules # Return all modules for this environment in the order they appear in the # modulepath. # @note If multiple modules with the same name are present they will # both be added, but methods like {#module} and {#module_by_forge_name} # will return the first matching entry in this list. # @note This value is cached so that the filesystem doesn't have to be # re-enumerated every time this method is invoked, since that # enumeration could be a costly operation and this method is called # frequently. The cache expiry is determined by `Puppet[:filetimeout]`. # @see Puppet::Util::Cacher.cached_attr # @api public # @return [Array] All modules for this environment cached_attr(:modules, Puppet[:filetimeout]) do module_references = [] seen_modules = {} modulepath.each do |path| Dir.entries(path).each do |name| warn_about_mistaken_path(path, name) next if module_references.include?(name) if not seen_modules[name] module_references << {:name => name, :path => File.join(path, name)} seen_modules[name] = true end end end module_references.collect do |reference| begin Puppet::Module.new(reference[:name], reference[:path], self) rescue Puppet::Module::Error nil end end.compact end # Generate a warning if the given directory in a module path entry is named `lib`. # # @api private # # @param path [String] The module directory containing the given directory # @param name [String] The directory name def warn_about_mistaken_path(path, name) if name == "lib" Puppet.debug("Warning: Found directory named 'lib' in module path ('#{path}/lib'); unless " + "you are expecting to load a module named 'lib', your module path may be set " + "incorrectly.") end end # Modules broken out by directory in the modulepath # # @note This method _changes_ the current working directory while enumerating # the modules. This seems rather dangerous. # # @api public # # @return [Hash>] A hash whose keys are file # paths, and whose values is an array of Puppet Modules for that path def modules_by_path modules_by_path = {} modulepath.each do |path| Dir.chdir(path) do module_names = Dir.glob('*').select do |d| FileTest.directory?(d) && (File.basename(d) =~ /\A\w+(-\w+)*\Z/) end modules_by_path[path] = module_names.sort.map do |name| Puppet::Module.new(name, File.join(path, name), self) end end end modules_by_path end # All module requirements for all modules in the environment modulepath # # @api public # # @comment This has nothing to do with an environment. It seems like it was # stuffed into the first convenient class that vaguely involved modules. # # @example # environment.module_requirements # # => { # # 'username/amodule' => [ # # { # # 'name' => 'username/moduledep', # # 'version' => '1.2.3', # # 'version_requirement' => '>= 1.0.0', # # }, # # { # # 'name' => 'username/anotherdep', # # 'version' => '4.5.6', # # 'version_requirement' => '>= 3.0.0', # # } # # ] # # } # # # # @return [Hash>>] See the method example # for an explanation of the return value. def module_requirements deps = {} modules.each do |mod| next unless mod.forge_name deps[mod.forge_name] ||= [] mod.dependencies and mod.dependencies.each do |mod_dep| dep_name = mod_dep['name'].tr('-', '/') (deps[dep_name] ||= []) << { 'name' => mod.forge_name, 'version' => mod.version, 'version_requirement' => mod_dep['version_requirement'] } end end deps.each do |mod, mod_deps| deps[mod] = mod_deps.sort_by { |d| d['name'] } end deps end # Set a periodic watcher on the file, so we can tell if it has changed. # If watching has been turned off, this call has no effect. # @param file[File,String] File instance or filename # @api private def watch_file(file) if watching? known_resource_types.watch_file(file.to_s) end end # Checks if a reparse is required (cache of files is stale). # This call does nothing unless files are being watched. # def check_for_reparse if (Puppet[:code] != @parsed_code) || (watching? && @known_resource_types && @known_resource_types.require_reparse?) @parsed_code = nil @known_resource_types = nil end end # @return [String] The stringified value of the `name` instance variable # @api public def to_s name.to_s end # @return [Symbol] The `name` value, cast to a string, then cast to a symbol. # # @api public # # @note the `name` instance variable is a Symbol, but this casts the value # to a String and then converts it back into a Symbol which will needlessly # create an object that needs to be garbage collected def to_sym to_s.to_sym end # Return only the environment name when serializing. # # The only thing we care about when serializing an environment is its # identity; everything else is ephemeral and should not be stored or # transmitted. # # @api public def to_zaml(z) self.to_s.to_zaml(z) end def self.split_path(path_string) path_string.split(File::PATH_SEPARATOR) end def ==(other) return true if other.kind_of?(Puppet::Node::Environment) && self.name == other.name && self.full_modulepath == other.full_modulepath && self.manifest == other.manifest end alias eql? == def hash [self.class, name, full_modulepath, manifest].hash end private def self.extralibs() if ENV["PUPPETLIB"] split_path(ENV["PUPPETLIB"]) else [] end end def self.expand_dirs(dirs) dirs.collect do |dir| File.expand_path(dir) end end # Reparse the manifests for the given environment # # There are two sources that can be used for the initial parse: # # 1. The value of `Puppet.settings[:code]`: Puppet can take a string from # its settings and parse that as a manifest. This is used by various # Puppet applications to read in a manifest and pass it to the # environment as a side effect. This is attempted first. # 2. The contents of `Puppet.settings[:manifest]`: Puppet will try to load # the environment manifest. By default this is `$manifestdir/site.pp` # # @note This method will return an empty hostclass if # `Puppet.settings[:ignoreimport]` is set to true. # # @return [Puppet::Parser::AST::Hostclass] The AST hostclass object # representing the 'main' hostclass def perform_initial_import return empty_parse_result if Puppet[:ignoreimport] parser = Puppet::Parser::ParserFactory.parser(self) @parsed_code = Puppet[:code] if @parsed_code != "" parser.string = @parsed_code parser.parse else file = self.manifest # if the manifest file is a reference to a directory, parse and combine all .pp files in that # directory if file == NO_MANIFEST Puppet::Parser::AST::Hostclass.new('') elsif File.directory?(file) if Puppet.future_parser? parse_results = Puppet::FileSystem::PathPattern.absolute(File.join(file, '**/*.pp')).glob.sort.map do | file_to_parse | parser.file = file_to_parse parser.parse end else parse_results = Dir.entries(file).find_all { |f| f =~ /\.pp$/ }.sort.map do |file_to_parse| parser.file = File.join(file, file_to_parse) parser.parse end end # Use a parser type specific merger to concatenate the results Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results)) else parser.file = file parser.parse end end + rescue Puppet::ParseErrorWithIssue => detail + @known_resource_types.parse_failed = true + detail.environment = self.name + raise rescue => detail @known_resource_types.parse_failed = true msg = "Could not parse for environment #{self}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end # Return an empty toplevel hostclass to indicate that no file was loaded # # This is used as the return value of {#perform_initial_import} when # `Puppet.settings[:ignoreimport]` is true. # # @return [Puppet::Parser::AST::Hostclass] def empty_parse_result return Puppet::Parser::AST::Hostclass.new('') end # A special "null" environment # # This environment should be used when there is no specific environment in # effect. NONE = create(:none, []) end diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 1fabeb8d0..b927e7dfc 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -1,633 +1,637 @@ require 'forwardable' require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/util/errors' require 'puppet/resource/type_collection_helper' # Maintain a graph of scopes, along with a bunch of data # about the individual catalog we're compiling. class Puppet::Parser::Compiler extend Forwardable include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Resource::TypeCollectionHelper def self.compile(node) $env_module_directories = nil node.environment.check_for_reparse if node.environment.conflicting_manifest_settings? errmsg = [ "The 'disable_per_environment_manifest' setting is true, and this '#{node.environment}'", "has an environment.conf manifest that conflicts with the 'default_manifest' setting.", "Compilation has been halted in order to avoid running a catalog which may be using", "unexpected manifests. For more information, see", "http://docs.puppetlabs.com/puppet/latest/reference/environments.html", ] raise(Puppet::Error, errmsg.join(' ')) end new(node).compile {|resulting_catalog| resulting_catalog.to_resource } + rescue Puppet::ParseErrorWithIssue => detail + detail.node = node.name + Puppet.log_exception(detail) + raise rescue => detail message = "#{detail} on node #{node.name}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end attr_reader :node, :facts, :collections, :catalog, :resources, :relationships, :topscope # The injector that provides lookup services, or nil if accessed before the compiler has started compiling and # bootstrapped. The injector is initialized and available before any manifests are evaluated. # # @return [Puppet::Pops::Binder::Injector, nil] The injector that provides lookup services for this compiler/environment # @api public # attr_accessor :injector # Access to the configured loaders for 4x # @return [Puppet::Pops::Loader::Loaders] the configured loaders # @api private attr_reader :loaders # The injector that provides lookup services during the creation of the {#injector}. # @return [Puppet::Pops::Binder::Injector, nil] The injector that provides lookup services during injector creation # for this compiler/environment # # @api private # attr_accessor :boot_injector # Add a collection to the global list. def_delegator :@collections, :<<, :add_collection def_delegator :@relationships, :<<, :add_relationship # Store a resource override. def add_override(override) # If possible, merge the override in immediately. if resource = @catalog.resource(override.ref) resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end def add_resource(scope, resource) @resources << resource # Note that this will fail if the resource is not unique. @catalog.add_resource(resource) if not resource.class? and resource[:stage] raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" end # Stages should not be inside of classes. They are always a # top-level container, regardless of where they appear in the # manifest. return if resource.stage? # This adds a resource to the class it lexically appears in in the # manifest. unless resource.class? return @catalog.add_edge(scope.resource, resource) end end # Do we use nodes found in the code, vs. the external node sources? def_delegator :known_resource_types, :nodes?, :ast_nodes? # Store the fact that we've evaluated a class def add_class(name) @catalog.add_class(name) unless name == "" end # Return a list of all of the defined classes. def_delegator :@catalog, :classes, :classlist # Compiler our catalog. This mostly revolves around finding and evaluating classes. # This is the main entry into our catalog. def compile Puppet.override( @context_overrides , "For compiling #{node.name}") do @catalog.environment_instance = environment # Set the client's parameters into the top scope. Puppet::Util::Profiler.profile("Compile: Set node parameters", [:compiler, :set_node_params]) { set_node_parameters } Puppet::Util::Profiler.profile("Compile: Created settings scope", [:compiler, :create_settings_scope]) { create_settings_scope } if is_binder_active? # create injector, if not already created - this is for 3x that does not trigger # lazy loading of injector via context Puppet::Util::Profiler.profile("Compile: Created injector", [:compiler, :create_injector]) { injector } end Puppet::Util::Profiler.profile("Compile: Evaluated main", [:compiler, :evaluate_main]) { evaluate_main } Puppet::Util::Profiler.profile("Compile: Evaluated AST node", [:compiler, :evaluate_ast_node]) { evaluate_ast_node } Puppet::Util::Profiler.profile("Compile: Evaluated node classes", [:compiler, :evaluate_node_classes]) { evaluate_node_classes } Puppet::Util::Profiler.profile("Compile: Evaluated generators", [:compiler, :evaluate_generators]) { evaluate_generators } Puppet::Util::Profiler.profile("Compile: Finished catalog", [:compiler, :finish_catalog]) { finish } fail_on_unevaluated if block_given? yield @catalog else @catalog end end end # Constructs the overrides for the context def context_overrides() if Puppet.future_parser? require 'puppet/loaders' { :current_environment => environment, :global_scope => @topscope, # 4x placeholder for new global scope :loaders => lambda {|| loaders() }, # 4x loaders :injector => lambda {|| injector() } # 4x API - via context instead of via compiler } else { :current_environment => environment, } end end def_delegator :@collections, :delete, :delete_collection # Return the node's environment. def environment node.environment end # Evaluate all of the classes specified by the node. # Classes with parameters are evaluated as if they were declared. # Classes without parameters or with an empty set of parameters are evaluated # as if they were included. This means classes with an empty set of # parameters won't conflict even if the class has already been included. def evaluate_node_classes if @node.classes.is_a? Hash classes_with_params, classes_without_params = @node.classes.partition {|name,params| params and !params.empty?} # The results from Hash#partition are arrays of pairs rather than hashes, # so we have to convert to the forms evaluate_classes expects (Hash, and # Array of class names) classes_with_params = Hash[classes_with_params] classes_without_params.map!(&:first) else classes_with_params = {} classes_without_params = @node.classes end evaluate_classes(classes_with_params, @node_scope || topscope) evaluate_classes(classes_without_params, @node_scope || topscope) end # Evaluate each specified class in turn. If there are any classes we can't # find, raise an error. This method really just creates resource objects # that point back to the classes, and then the resources are themselves # evaluated later in the process. # # Sometimes we evaluate classes with a fully qualified name already, in which # case, we tell scope.find_hostclass we've pre-qualified the name so it # doesn't need to search its namespaces again. This gets around a weird # edge case of duplicate class names, one at top scope and one nested in our # namespace and the wrong one (or both!) getting selected. See ticket #13349 # for more detail. --jeffweiss 26 apr 2012 def evaluate_classes(classes, scope, lazy_evaluate = true, fqname = false) raise Puppet::DevError, "No source for scope passed to evaluate_classes" unless scope.source class_parameters = nil # if we are a param class, save the classes hash # and transform classes to be the keys if classes.class == Hash class_parameters = classes classes = classes.keys end hostclasses = classes.collect do |name| scope.find_hostclass(name, :assume_fqname => fqname) or raise Puppet::Error, "Could not find class #{name} for #{node.name}" end if class_parameters resources = ensure_classes_with_parameters(scope, hostclasses, class_parameters) if !lazy_evaluate resources.each(&:evaluate) end resources else already_included, newly_included = ensure_classes_without_parameters(scope, hostclasses) if !lazy_evaluate newly_included.each(&:evaluate) end already_included + newly_included end end def evaluate_relationships @relationships.each { |rel| rel.evaluate(catalog) } end # Return a resource by either its ref or its type and title. def_delegator :@catalog, :resource, :findresource def initialize(node, options = {}) @node = node set_options(options) initvars end # Create a new scope, with either a specified parent scope or # using the top scope. def newscope(parent, options = {}) parent ||= topscope scope = Puppet::Parser::Scope.new(self, options) scope.parent = parent scope end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end def injector create_injector if @injector.nil? @injector end def loaders @loaders ||= Puppet::Pops::Loaders.new(environment) end def boot_injector create_boot_injector(nil) if @boot_injector.nil? @boot_injector end # Creates the boot injector from registered system, default, and injector config. # @return [Puppet::Pops::Binder::Injector] the created boot injector # @api private Cannot be 'private' since it is called from the BindingsComposer. # def create_boot_injector(env_boot_bindings) assert_binder_active() pb = Puppet::Pops::Binder boot_contribution = pb::SystemBindings.injector_boot_contribution(env_boot_bindings) final_contribution = pb::SystemBindings.final_contribution binder = pb::Binder.new(pb::BindingsFactory.layered_bindings(final_contribution, boot_contribution)) @boot_injector = pb::Injector.new(binder) end # Answers if Puppet Binder should be active or not, and if it should and is not active, then it is activated. # @return [Boolean] true if the Puppet Binder should be activated def is_binder_active? should_be_active = Puppet[:binder] || Puppet.future_parser? if should_be_active # TODO: this should be in a central place, not just for ParserFactory anymore... Puppet::Parser::ParserFactory.assert_rgen_installed() @@binder_loaded ||= false unless @@binder_loaded require 'puppet/pops' require 'puppetx' @@binder_loaded = true end end should_be_active end private def ensure_classes_with_parameters(scope, hostclasses, parameters) hostclasses.collect do |klass| klass.ensure_in_catalog(scope, parameters[klass.name] || {}) end end def ensure_classes_without_parameters(scope, hostclasses) already_included = [] newly_included = [] hostclasses.each do |klass| class_scope = scope.class_scope(klass) if class_scope already_included << class_scope.resource else newly_included << klass.ensure_in_catalog(scope) end end [already_included, newly_included] end # 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 = known_resource_types.node(name.to_s.downcase) end unless (astnode ||= known_resource_types.node("default")) raise Puppet::ParseError, "Could not find default node or by name with '#{node.names.join(", ")}'" end # Create a resource to model this node, and then add it to the list # of resources. resource = astnode.ensure_in_catalog(topscope) resource.evaluate @node_scope = topscope.class_scope(astnode) end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? exceptwrap do # We have to iterate over a dup of the array because # collections can delete themselves from the list, which # changes its length and causes some collections to get missed. Puppet::Util::Profiler.profile("Evaluated collections", [:compiler, :evaluate_collections]) do found_something = false @collections.dup.each do |collection| found_something = true if collection.evaluate end found_something end end 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 Puppet::Util::Profiler.profile("Evaluated definitions", [:compiler, :evaluate_definitions]) do !unevaluated_resources.each do |resource| Puppet::Util::Profiler.profile("Evaluated resource #{resource}", [:compiler, :evaluate_resource, resource]) do resource.evaluate end end.empty? 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 Puppet::Util::Profiler.profile("Iterated (#{count + 1}) on generators", [:compiler, :iterate_on_generators]) do # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions end break if done count += 1 if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host catalog" end end end # Find and evaluate our main object, if possible. def evaluate_main @main = known_resource_types.find_hostclass([""], "") || known_resource_types.add(Puppet::Resource::Type.new(:hostclass, "")) @topscope.source = @main @main_resource = Puppet::Parser::Resource.new("class", :main, :scope => @topscope, :source => @main) @topscope.resource = @main_resource add_resource(@topscope, @main_resource) @main_resource.evaluate end # Make sure the entire catalog is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = @resource_overrides.values.flatten.collect(&:ref) if !remaining.empty? fail Puppet::ParseError, "Could not find resource(s) #{remaining.join(', ')} for overriding" 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 if Puppet.future_parser? remaining = @collections.collect(&:unresolved_resources).flatten.compact else remaining = @collections.collect(&:resources).flatten.compact end if !remaining.empty? raise Puppet::ParseError, "Failed to realize virtual resources #{remaining.join(', ')}" end end # Make sure all of our resources and such have done any last work # necessary. def finish evaluate_relationships resources.each do |resource| # Add in any resource overrides. if overrides = resource_overrides(resource) overrides.each do |over| resource.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end resource.finish if resource.respond_to?(:finish) end add_resource_metaparams end def add_resource_metaparams unless main = catalog.resource(:class, :main) raise "Couldn't find main" end names = Puppet::Type.metaparams.select do |name| !Puppet::Parser::Resource.relationship_parameter?(name) end data = {} catalog.walk(main, :out) do |source, target| if source_data = data[source] || metaparams_as_data(source, names) # only store anything in the data hash if we've actually got # data data[source] ||= source_data source_data.each do |param, value| target[param] = value if target[param].nil? end data[target] = source_data.merge(metaparams_as_data(target, names)) end target.tag(*(source.tags)) end end def metaparams_as_data(resource, params) data = nil params.each do |param| unless resource[param].nil? # Because we could be creating a hash for every resource, # and we actually probably don't often have any data here at all, # we're optimizing a bit by only creating a hash if there's # any data to put in it. data ||= {} data[param] = resource[param] end end data end # Set up all of our internal variables. def initvars # 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 = [] # The list of relationships to evaluate. @relationships = [] # For maintaining the relationship between scopes and their resources. @catalog = Puppet::Resource::Catalog.new(@node.name, @node.environment) # MOVED HERE - SCOPE IS NEEDED (MOVE-SCOPE) # Create the initial scope, it is needed early @topscope = Puppet::Parser::Scope.new(self) # Need to compute overrides here, and remember them, because we are about to # enter the magic zone of known_resource_types and intial import. # Expensive entries in the context are bound lazily. @context_overrides = context_overrides() # This construct ensures that initial import (triggered by instantiating # the structure 'known_resource_types') has a configured context # It cannot survive the initvars method, and is later reinstated # as part of compiling... # Puppet.override( @context_overrides , "For initializing compiler") do # THE MAGIC STARTS HERE ! This triggers parsing, loading etc. @catalog.version = known_resource_types.version end @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @topscope)) # local resource array to maintain resource ordering @resources = [] # Make sure any external node classes are in our class list if @node.classes.class == Hash @catalog.add_class(*@node.classes.keys) else @catalog.add_class(*@node.classes) end end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope[param.to_s] = value end # These might be nil. catalog.client_version = node.parameters["clientversion"] catalog.server_version = node.parameters["serverversion"] if Puppet[:trusted_node_data] @topscope.set_trusted(node.trusted_data) end if(Puppet[:immutable_node_data]) facts_hash = node.facts.nil? ? {} : node.facts.values @topscope.set_facts(facts_hash) end end def create_settings_scope settings_type = Puppet::Resource::Type.new :hostclass, "settings" environment.known_resource_types.add(settings_type) settings_resource = Puppet::Parser::Resource.new("class", "settings", :scope => @topscope) @catalog.add_resource(settings_resource) settings_type.evaluate_code(settings_resource) scope = @topscope.class_scope(settings_type) env = environment Puppet.settings.each do |name, setting| next if name == :name scope[name.to_s] = env[name] end end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources # The order of these is significant for speed due to short-circuting resources.reject { |resource| resource.evaluated? or resource.virtual? or resource.builtin_type? } end # Creates the injector from bindings found in the current environment. # @return [void] # @api private # def create_injector assert_binder_active() composer = Puppet::Pops::Binder::BindingsComposer.new() layered_bindings = composer.compose(topscope) @injector = Puppet::Pops::Binder::Injector.new(Puppet::Pops::Binder::Binder.new(layered_bindings)) end def assert_binder_active unless is_binder_active? raise ArgumentError, "The Puppet Binder is only available when either '--binder true' or '--parser future' is used" end end end diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb index 692a4e025..a2a7428b0 100644 --- a/lib/puppet/pops.rb +++ b/lib/puppet/pops.rb @@ -1,127 +1,131 @@ module Puppet # The Pops language system. This includes the parser, evaluator, AST model, and # Binder. # # @todo Explain how a user should use this to parse and evaluate the puppet # language. # # @note Warning: Pops is still considered experimental, as such the API may # change at any time. # # @api public module Pops require 'puppet/pops/patterns' require 'puppet/pops/utils' require 'puppet/pops/adaptable' require 'puppet/pops/adapters' require 'puppet/pops/visitable' require 'puppet/pops/visitor' require 'puppet/pops/containment' require 'puppet/pops/issues' require 'puppet/pops/semantic_error' require 'puppet/pops/label_provider' require 'puppet/pops/validation' require 'puppet/pops/issue_reporter' require 'puppet/pops/model/model' # (the Types module initializes itself) require 'puppet/pops/types/types' require 'puppet/pops/types/type_calculator' require 'puppet/pops/types/type_factory' require 'puppet/pops/types/type_parser' require 'puppet/pops/types/class_loader' require 'puppet/pops/types/enumeration' module Model require 'puppet/pops/model/tree_dumper' require 'puppet/pops/model/ast_transformer' require 'puppet/pops/model/ast_tree_dumper' require 'puppet/pops/model/factory' require 'puppet/pops/model/model_tree_dumper' require 'puppet/pops/model/model_label_provider' end module Binder module SchemeHandler # the handlers are auto loaded via bindings end module Producers require 'puppet/pops/binder/producers' end require 'puppet/pops/binder/binder' require 'puppet/pops/binder/bindings_model' require 'puppet/pops/binder/binder_issues' require 'puppet/pops/binder/bindings_checker' require 'puppet/pops/binder/bindings_factory' require 'puppet/pops/binder/bindings_label_provider' require 'puppet/pops/binder/bindings_validator_factory' require 'puppet/pops/binder/injector_entry' require 'puppet/pops/binder/key_factory' require 'puppet/pops/binder/injector' require 'puppet/pops/binder/bindings_composer' require 'puppet/pops/binder/bindings_model_dumper' require 'puppet/pops/binder/system_bindings' require 'puppet/pops/binder/bindings_loader' require 'puppet/pops/binder/lookup' module Config require 'puppet/pops/binder/config/binder_config' require 'puppet/pops/binder/config/binder_config_checker' require 'puppet/pops/binder/config/issues' require 'puppet/pops/binder/config/diagnostic_producer' end end module Parser require 'puppet/pops/parser/eparser' require 'puppet/pops/parser/parser_support' require 'puppet/pops/parser/locator' require 'puppet/pops/parser/locatable' require 'puppet/pops/parser/lexer2' require 'puppet/pops/parser/evaluating_parser' require 'puppet/pops/parser/epp_parser' end module Validation require 'puppet/pops/validation/checker4_0' require 'puppet/pops/validation/validator_factory_4_0' end module Evaluator require 'puppet/pops/evaluator/callable_signature' require 'puppet/pops/evaluator/runtime3_converter' require 'puppet/pops/evaluator/runtime3_support' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/pops/evaluator/epp_evaluator' require 'puppet/pops/evaluator/callable_mismatch_describer' require 'puppet/pops/evaluator/collector_transformer' require 'puppet/pops/evaluator/puppet_proc' module Collectors require 'puppet/pops/evaluator/collectors/abstract_collector' require 'puppet/pops/evaluator/collectors/fixed_set_collector' require 'puppet/pops/evaluator/collectors/catalog_collector' require 'puppet/pops/evaluator/collectors/exported_collector' end end # Subsystem for puppet functions defined in ruby. # # @api public module Functions require 'puppet/pops/functions/function' require 'puppet/pops/functions/dispatch' require 'puppet/pops/functions/dispatcher' end + + module Migration + require 'puppet/pops/migration/migration_checker' + end end require 'puppet/parser/ast/pops_bridge' require 'puppet/bindings' require 'puppet/functions' end diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb index a31899052..275e93264 100644 --- a/lib/puppet/pops/evaluator/evaluator_impl.rb +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -1,1193 +1,1214 @@ require 'rgen/ecore/ecore' require 'puppet/pops/evaluator/compare_operator' require 'puppet/pops/evaluator/relationship_operator' require 'puppet/pops/evaluator/access_operator' require 'puppet/pops/evaluator/closure' require 'puppet/pops/evaluator/external_syntax_support' # This implementation of {Puppet::Pops::Evaluator} performs evaluation using the puppet 3.x runtime system # in a manner largely compatible with Puppet 3.x, but adds new features and introduces constraints. # # The evaluation uses _polymorphic dispatch_ which works by dispatching to the first found method named after # the class or one of its super-classes. The EvaluatorImpl itself mainly deals with evaluation (it currently # also handles assignment), and it uses a delegation pattern to more specialized handlers of some operators # that in turn use polymorphic dispatch; this to not clutter EvaluatorImpl with too much responsibility). # # Since a pattern is used, only the main entry points are fully documented. The parameters _o_ and _scope_ are # the same in all the polymorphic methods, (the type of the parameter _o_ is reflected in the method's name; # either the actual class, or one of its super classes). The _scope_ parameter is always the scope in which # the evaluation takes place. If nothing else is mentioned, the return is always the result of evaluation. # # See {Puppet::Pops::Visitable} and {Puppet::Pops::Visitor} for more information about # polymorphic calling. # class Puppet::Pops::Evaluator::EvaluatorImpl include Puppet::Pops::Utils # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Puppet::Pops::Evaluator::Runtime3Support include Puppet::Pops::Evaluator::ExternalSyntaxSupport # This constant is not defined as Float::INFINITY in Ruby 1.8.7 (but is available in later version # Refactor when support is dropped for Ruby 1.8.7. # INFINITY = 1.0 / 0.0 EMPTY_STRING = ''.freeze COMMA_SEPARATOR = ', '.freeze # Reference to Issues name space makes it easier to refer to issues # (Issues are shared with the validator). # Issues = Puppet::Pops::Issues def initialize @@eval_visitor ||= Puppet::Pops::Visitor.new(self, "eval", 1, 1) @@lvalue_visitor ||= Puppet::Pops::Visitor.new(self, "lvalue", 1, 1) @@assign_visitor ||= Puppet::Pops::Visitor.new(self, "assign", 3, 3) @@string_visitor ||= Puppet::Pops::Visitor.new(self, "string", 1, 1) @@type_calculator ||= Puppet::Pops::Types::TypeCalculator.new() @@type_parser ||= Puppet::Pops::Types::TypeParser.new() @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new() @@relationship_operator ||= Puppet::Pops::Evaluator::RelationshipOperator.new() + + # Use null migration checker unless given in context + @migration_checker = (Puppet.lookup(:migration_checker) { Puppet::Pops::Migration::MigrationChecker.new() }) end # @api private def type_calculator @@type_calculator end # Evaluates the given _target_ object in the given scope. # # @overload evaluate(target, scope) # @param target [Object] evaluation target - see methods on the pattern assign_TYPE for actual supported types. # @param scope [Object] the runtime specific scope class where evaluation should take place # @return [Object] the result of the evaluation # # @api public # def evaluate(target, scope) begin @@eval_visitor.visit_this_1(self, target, scope) rescue Puppet::Pops::SemanticError => e # A raised issue may not know the semantic target, use errors call stack, but fill in the # rest from a supplied semantic object, or the target instruction if there is not semantic # object. # fail(e.issue, e.semantic || target, e.options, e) rescue Puppet::PreformattedError => e # Already formatted with location information, and with the wanted call stack. # Note this is currently a specialized ParseError, so rescue-order is important # raise e rescue Puppet::ParseError => e # ParseError may be raised in ruby code without knowing the location # in puppet code. # Accept a ParseError that has file or line information available # as an error that should be used verbatim. (Tests typically run without # setting a file name). # ParseError can supply an original - it is impossible to determine which # call stack that should be propagated, using the ParseError's backtrace. # if e.file || e.line raise e else # Since it had no location information, treat it as user intended a general purpose # error. Pass on its call stack. fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end rescue Puppet::Error => e # PuppetError has the ability to wrap an exception, if so, use the wrapped exception's # call stack instead fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e.original || e) rescue StandardError => e # All other errors, use its message and call stack fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end end # Assigns the given _value_ to the given _target_. The additional argument _o_ is the instruction that # produced the target/value tuple and it is used to set the origin of the result. # # @param target [Object] assignment target - see methods on the pattern assign_TYPE for actual supported types. # @param value [Object] the value to assign to `target` # @param o [Puppet::Pops::Model::PopsObject] originating instruction # @param scope [Object] the runtime specific scope where evaluation should take place # # @api private # def assign(target, value, o, scope) @@assign_visitor.visit_this_3(self, target, value, o, scope) end # Computes a value that can be used as the LHS in an assignment. # @param o [Object] the expression to evaluate as a left (assignable) entity # @param scope [Object] the runtime specific scope where evaluation should take place # # @api private # def lvalue(o, scope) @@lvalue_visitor.visit_this_1(self, o, scope) end # Produces a String representation of the given object _o_ as used in interpolation. # @param o [Object] the expression of which a string representation is wanted # @param scope [Object] the runtime specific scope where evaluation should take place # # @api public # def string(o, scope) @@string_visitor.visit_this_1(self, o, scope) end # Evaluate a BlockExpression in a new scope with variables bound to the # given values. # # @param scope [Puppet::Parser::Scope] the parent scope # @param variable_bindings [Hash{String => Object}] the variable names and values to bind (names are keys, bound values are values) # @param block [Puppet::Pops::Model::BlockExpression] the sequence of expressions to evaluate in the new scope # # @api private # def evaluate_block_with_bindings(scope, variable_bindings, block_expr) with_guarded_scope(scope) do # change to create local scope_from - cannot give it file and line - # that is the place of the call, not "here" create_local_scope_from(variable_bindings, scope) evaluate(block_expr, scope) end end protected def lvalue_VariableExpression(o, scope) # evaluate the name evaluate(o.expr, scope) end # Catches all illegal lvalues # def lvalue_Object(o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end # Assign value to named variable. # The '$' sign is never part of the name. # @example In Puppet DSL # $name = value # @param name [String] name of variable without $ # @param value [Object] value to assign to the variable # @param o [Puppet::Pops::Model::PopsObject] originating instruction # @param scope [Object] the runtime specific scope where evaluation should take place # @return [value] # def assign_String(name, value, o, scope) if name =~ /::/ fail(Issues::CROSS_SCOPE_ASSIGNMENT, o.left_expr, {:name => name}) end set_variable(name, value, o, scope) value end def assign_Numeric(n, value, o, scope) fail(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o.left_expr, {:varname => n.to_s}) end # Catches all illegal assignment (e.g. 1 = 2, {'a'=>1} = 2, etc) # def assign_Object(name, value, o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end def eval_Factory(o, scope) evaluate(o.current, scope) end # Evaluates any object not evaluated to something else to itself. def eval_Object o, scope o end # Allows nil to be used as a Nop, Evaluates to nil def eval_NilClass(o, scope) nil end # Evaluates Nop to nil. def eval_Nop(o, scope) nil end # Captures all LiteralValues not handled elsewhere. # def eval_LiteralValue(o, scope) o.value end # Reserved Words fail to evaluate # def eval_ReservedWord(o, scope) fail(Puppet::Pops::Issues::RESERVED_WORD, o, {:word => o.word}) end def eval_LiteralDefault(o, scope) :default end def eval_LiteralUndef(o, scope) nil end # A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType # def eval_QualifiedReference(o, scope) @@type_parser.interpret(o) end def eval_NotExpression(o, scope) - ! is_true?(evaluate(o.expr, scope)) + ! is_true?(evaluate(o.expr, scope), o.expr) end def eval_UnaryMinusExpression(o, scope) - coerce_numeric(evaluate(o.expr, scope), o, scope) end def eval_UnfoldExpression(o, scope) candidate = evaluate(o.expr, scope) case candidate when nil [] when Array candidate when Hash candidate.to_a else # turns anything else into an array (so result can be unfolded) [candidate] end end # Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and # right_expr # @return > array with result of evaluating left and right expressions # def eval_BinaryExpression o, scope [ evaluate(o.left_expr, scope), evaluate(o.right_expr, scope) ] end # Evaluates assignment with operators =, +=, -= and # # @example Puppet DSL # $a = 1 # $a += 1 # $a -= 1 # def eval_AssignmentExpression(o, scope) name = lvalue(o.left_expr, scope) value = evaluate(o.right_expr, scope) if o.operator == :'=' assign(name, value, o, scope) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end value end ARITHMETIC_OPERATORS = [:'+', :'-', :'*', :'/', :'%', :'<<', :'>>'] COLLECTION_OPERATORS = [:'+', :'-', :'<<'] # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> # def eval_ArithmeticExpression(o, scope) left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) begin result = calculate(left, right, o.operator, o.left_expr, o.right_expr, scope) rescue ArgumentError => e fail(Issues::RUNTIME_ERROR, o, {:detail => e.message}, e) end result end # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> # def calculate(left, right, operator, left_o, right_o, scope) unless ARITHMETIC_OPERATORS.include?(operator) fail(Issues::UNSUPPORTED_OPERATOR, left_o.eContainer, {:operator => o.operator}) end if (left.is_a?(Array) || left.is_a?(Hash)) && COLLECTION_OPERATORS.include?(operator) # Handle operation on collections case operator when :'+' concatenate(left, right) when :'-' delete(left, right) when :'<<' unless left.is_a?(Array) fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) end left + [right] end else # Handle operation on numeric left = coerce_numeric(left, left_o, scope) right = coerce_numeric(right, right_o, scope) begin if operator == :'%' && (left.is_a?(Float) || right.is_a?(Float)) # Deny users the fun of seeing severe rounding errors and confusing results fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) end result = left.send(operator, right) rescue NoMethodError => e fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) rescue ZeroDivisionError => e fail(Issues::DIV_BY_ZERO, right_o) end if result == INFINITY || result == -INFINITY fail(Issues::RESULT_IS_INFINITY, left_o, {:operator => operator}) end result end end def eval_EppExpression(o, scope) scope["@epp"] = [] evaluate(o.body, scope) result = scope["@epp"].join result end def eval_RenderStringExpression(o, scope) scope["@epp"] << o.value.dup nil end def eval_RenderExpression(o, scope) scope["@epp"] << string(evaluate(o.expr, scope), scope) nil end # Evaluates Puppet DSL ->, ~>, <-, and <~ def eval_RelationshipExpression(o, scope) # First level evaluation, reduction to basic data types or puppet types, the relationship operator then translates this # to the final set of references (turning strings into references, which can not naturally be done by the main evaluator since # all strings should not be turned into references. # real = eval_BinaryExpression(o, scope) @@relationship_operator.evaluate(real, o, scope) end # Evaluates x[key, key, ...] # def eval_AccessExpression(o, scope) left = evaluate(o.left_expr, scope) keys = o.keys.nil? ? [] : o.keys.collect {|key| evaluate(key, scope) } Puppet::Pops::Evaluator::AccessOperator.new(o).access(left, scope, *keys) end # Evaluates <, <=, >, >=, and == # def eval_ComparisonExpression o, scope left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) + @migration_checker.report_uc_bareword_type(left, o.left_expr) + @migration_checker.report_uc_bareword_type(right, o.right_expr) + begin # Left is a type if left.is_a?(Puppet::Pops::Types::PAnyType) case o.operator when :'==' + @migration_checker.report_equality_type_mismatch(left, right, o) @@type_calculator.equals(left,right) when :'!=' + @migration_checker.report_equality_type_mismatch(left, right, o) !@@type_calculator.equals(left,right) when :'<' # left can be assigned to right, but they are not equal @@type_calculator.assignable?(right, left) && ! @@type_calculator.equals(left,right) when :'<=' # left can be assigned to right @@type_calculator.assignable?(right, left) when :'>' # right can be assigned to left, but they are not equal @@type_calculator.assignable?(left,right) && ! @@type_calculator.equals(left,right) when :'>=' # right can be assigned to left @@type_calculator.assignable?(left, right) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end else case o.operator when :'==' + @migration_checker.report_equality_type_mismatch(left, right, o) @@compare_operator.equals(left,right) when :'!=' + @migration_checker.report_equality_type_mismatch(left, right, o) ! @@compare_operator.equals(left,right) when :'<' @@compare_operator.compare(left,right) < 0 when :'<=' @@compare_operator.compare(left,right) <= 0 when :'>' @@compare_operator.compare(left,right) > 0 when :'>=' @@compare_operator.compare(left,right) >= 0 else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end end rescue ArgumentError => e fail(Issues::COMPARISON_NOT_POSSIBLE, o, { :operator => o.operator, :left_value => left, :right_value => right, :detail => e.message}, e) end end # Evaluates matching expressions with type, string or regexp rhs expression. # If RHS is a type, the =~ matches compatible (instance? of) type. # # @example # x =~ /abc.*/ # @example # x =~ "abc.*/" # @example # y = "abc" # x =~ "${y}.*" # @example # [1,2,3] =~ Array[Integer[1,10]] # # Note that a string is not instance? of Regexp, only Regular expressions are. # The Pattern type should instead be used as it is specified as subtype of String. # # @return [Boolean] if a match was made or not. Also sets $0..$n to matchdata in current scope. # def eval_MatchExpression o, scope left = evaluate(o.left_expr, scope) pattern = evaluate(o.right_expr, scope) + @migration_checker.report_uc_bareword_type(left, o.left_expr) + # matches RHS types as instance of for all types except a parameterized Regexp[R] if pattern.is_a?(Puppet::Pops::Types::PAnyType) # evaluate as instance? of type check matched = @@type_calculator.instance?(pattern, left) # convert match result to Boolean true, or false return o.operator == :'=~' ? !!matched : !matched end begin pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp) rescue StandardError => e fail(Issues::MATCH_NOT_REGEXP, o.right_expr, {:detail => e.message}, e) end unless left.is_a?(String) fail(Issues::MATCH_NOT_STRING, o.left_expr, {:left_value => left}) end matched = pattern.match(left) # nil, or MatchData set_match_data(matched,scope) # creates ephemeral # convert match result to Boolean true, or false o.operator == :'=~' ? !!matched : !matched end # Evaluates Puppet DSL `in` expression # def eval_InExpression o, scope left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) + @migration_checker.report_uc_bareword_type(left, o.left_expr) + @migration_checker.report_in_expression(o) @@compare_operator.include?(right, left, scope) end # @example # $a and $b # b is only evaluated if a is true # def eval_AndExpression o, scope - is_true?(evaluate(o.left_expr, scope)) ? is_true?(evaluate(o.right_expr, scope)) : false + is_true?(evaluate(o.left_expr, scope), o.left_expr) ? is_true?(evaluate(o.right_expr, scope), o.right_expr) : false end # @example # a or b # b is only evaluated if a is false # def eval_OrExpression o, scope - is_true?(evaluate(o.left_expr, scope)) ? true : is_true?(evaluate(o.right_expr, scope)) + is_true?(evaluate(o.left_expr, scope), o.left_expr) ? true : is_true?(evaluate(o.right_expr, scope), o.right_expr) end # Evaluates each entry of the literal list and creates a new Array # Supports unfolding of entries # @return [Array] with the evaluated content # def eval_LiteralList o, scope unfold([], o.values, scope) end # Evaluates each entry of the literal hash and creates a new Hash. # @return [Hash] with the evaluated content # def eval_LiteralHash o, scope # optimized o.entries.reduce({}) {|h,entry| h[evaluate(entry.key, scope)] = evaluate(entry.value, scope); h } end # Evaluates all statements and produces the last evaluated value # def eval_BlockExpression o, scope r = nil o.statements.each {|s| r = evaluate(s, scope)} r end # Performs optimized search over case option values, lazily evaluating each # until there is a match. If no match is found, the case expression's default expression # is evaluated (it may be nil or Nop if there is no default, thus producing nil). # If an option matches, the result of evaluating that option is returned. # @return [Object, nil] what a matched option returns, or nil if nothing matched. # def eval_CaseExpression(o, scope) # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the case expression. # with_guarded_scope(scope) do test = evaluate(o.test, scope) + @migration_checker.report_uc_bareword_type(test, o.test) + result = nil the_default = nil if o.options.find do |co| # the first case option that matches if co.values.find do |c| case c when Puppet::Pops::Model::LiteralDefault the_default = co.then_expr - is_match?(test, evaluate(c, scope), c, scope) + is_match?(test, evaluate(c, scope), c, co, scope) when Puppet::Pops::Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) - evaluate(c, scope).any? {|v| is_match?(test, v, c, scope) } + evaluate(c, scope).any? {|v| is_match?(test, v, c, co, scope) } else - is_match?(test, evaluate(c, scope), c, scope) + is_match?(test, evaluate(c, scope), c, co, scope) end end result = evaluate(co.then_expr, scope) true # the option was picked end end result # an option was picked, and produced a result else evaluate(the_default, scope) # evaluate the default (should be a nop/nil) if there is no default). end end end # Evaluates a CollectExpression by creating a collector transformer. The transformer # will evaulate the collection, create the appropriate collector, and hand it off # to the compiler to collect the resources specified by the query. # def eval_CollectExpression o, scope Puppet::Pops::Evaluator::CollectorTransformer.new().transform(o,scope) end def eval_ParenthesizedExpression(o, scope) evaluate(o.expr, scope) end # This evaluates classes, nodes and resource type definitions to nil, since 3x: # instantiates them, and evaluates their parameters and body. This is achieved by # providing bridge AST classes in Puppet::Parser::AST::PopsBridge that bridges a # Pops Program and a Pops Expression. # # Since all Definitions are handled "out of band", they are treated as a no-op when # evaluated. # def eval_Definition(o, scope) nil end def eval_Program(o, scope) evaluate(o.body, scope) end # Produces Array[PAnyType], an array of resource references # def eval_ResourceExpression(o, scope) exported = o.exported virtual = o.virtual # Get the type name type_name = if (tmp_name = o.type_name).is_a?(Puppet::Pops::Model::QualifiedName) tmp_name.value # already validated as a name else type_name_acceptable = case o.type_name when Puppet::Pops::Model::QualifiedReference true when Puppet::Pops::Model::AccessExpression o.type_name.left_expr.is_a?(Puppet::Pops::Model::QualifiedReference) end evaluated_name = evaluate(tmp_name, scope) unless type_name_acceptable actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual => actual}) end # must be a CatalogEntry subtype case evaluated_name when Puppet::Pops::Types::PHostClassType unless evaluated_name.class_name.nil? fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end 'class' when Puppet::Pops::Types::PResourceType unless evaluated_name.title().nil? fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end evaluated_name.type_name # assume validated else actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=>actual}) end end # This is a runtime check - the model is valid, but will have runtime issues when evaluated # and storeconfigs is not set. if(o.exported) optionally_fail(Puppet::Pops::Issues::RT_NO_STORECONFIGS_EXPORT, o); end titles_to_body = {} body_to_titles = {} body_to_params = {} # titles are evaluated before attribute operations o.bodies.map do | body | titles = evaluate(body.title, scope) # Title may not be nil # Titles may be given as an array, it is ok if it is empty, but not if it contains nil entries # Titles may not be an empty String # Titles must be unique in the same resource expression # There may be a :default entry, its entries apply with lower precedence # if titles.nil? fail(Puppet::Pops::Issues::MISSING_TITLE, body.title) end titles = [titles].flatten # Check types of evaluated titles and duplicate entries titles.each_with_index do |title, index| if title.nil? fail(Puppet::Pops::Issues::MISSING_TITLE_AT, body.title, {:index => index}) elsif !title.is_a?(String) && title != :default actual = type_calculator.generalize!(type_calculator.infer(title)).to_s fail(Puppet::Pops::Issues::ILLEGAL_TITLE_TYPE_AT, body.title, {:index => index, :actual => actual}) elsif title == EMPTY_STRING fail(Puppet::Pops::Issues::EMPTY_STRING_TITLE_AT, body.title, {:index => index}) elsif titles_to_body[title] fail(Puppet::Pops::Issues::DUPLICATE_TITLE, o, {:title => title}) end titles_to_body[title] = body end # Do not create a real instance from the :default case titles.delete(:default) body_to_titles[body] = titles # Store evaluated parameters in a hash associated with the body, but do not yet create resource # since the entry containing :defaults may appear later body_to_params[body] = body.operations.reduce({}) do |param_memo, op| params = evaluate(op, scope) params = [params] unless params.is_a?(Array) params.each do |p| if param_memo.include? p.name fail(Puppet::Pops::Issues::DUPLICATE_ATTRIBUTE, o, {:attribute => p.name}) end param_memo[p.name] = p end param_memo end end # Titles and Operations have now been evaluated and resources can be created # Each production is a PResource, and an array of all is produced as the result of # evaluating the ResourceExpression. # defaults_hash = body_to_params[titles_to_body[:default]] || {} o.bodies.map do | body | titles = body_to_titles[body] params = defaults_hash.merge(body_to_params[body] || {}) create_resources(o, scope, virtual, exported, type_name, titles, params.values) end.flatten.compact end def eval_ResourceOverrideExpression(o, scope) evaluated_resources = evaluate(o.resources, scope) evaluated_parameters = o.operations.map { |op| evaluate(op, scope) } create_resource_overrides(o, scope, [evaluated_resources].flatten, evaluated_parameters) evaluated_resources end # Produces 3x parameter def eval_AttributeOperation(o, scope) create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator) end def eval_AttributesOperation(o, scope) hashed_params = evaluate(o.expr, scope) unless hashed_params.is_a?(Hash) actual = type_calculator.generalize!(type_calculator.infer(hashed_params)).to_s fail(Puppet::Pops::Issues::TYPE_MISMATCH, o.expr, {:expected => 'Hash', :actual => actual}) end hashed_params.map { |k,v| create_resource_parameter(o, scope, k, v, :'=>') } end # Sets default parameter values for a type, produces the type # def eval_ResourceDefaultsExpression(o, scope) type = evaluate(o.type_ref, scope) type_name = if type.is_a?(Puppet::Pops::Types::PResourceType) && !type.type_name.nil? && type.title.nil? type.type_name # assume it is a valid name else actual = type_calculator.generalize!(type_calculator.infer(type)) fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_ref, {:actual => actual}) end evaluated_parameters = o.operations.map {|op| evaluate(op, scope) } create_resource_defaults(o, scope, type_name, evaluated_parameters) # Produce the type type end # Evaluates function call by name. # def eval_CallNamedFunctionExpression(o, scope) # The functor expression is not evaluated, it is not possible to select the function to call # via an expression like $a() case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok when Puppet::Pops::Model::RenderStringExpression # helpful to point out this easy to make Epp error fail(Issues::ILLEGAL_EPP_PARAMETERS, o) else fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = o.functor_expr.value call_function_with_block(name, unfold([], o.arguments, scope), o, scope) end # Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name) # def eval_CallMethodExpression(o, scope) unless o.functor_expr.is_a? Puppet::Pops::Model::NamedAccessExpression fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function accessor', :container => o}) end receiver = evaluate(o.functor_expr.left_expr, scope) name = o.functor_expr.right_expr unless name.is_a? Puppet::Pops::Model::QualifiedName fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = name.value # the string function name call_function_with_block(name, unfold([receiver], o.arguments || [], scope), o, scope) end def call_function_with_block(name, evaluated_arguments, o, scope) if o.lambda.nil? call_function(name, evaluated_arguments, o, scope) else closure = Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) call_function(name, evaluated_arguments, o, scope, &proc_from_closure(closure)) end end private :call_function_with_block # Creates a Proc with an arity count that matches the parameters of the given closure. The arity will # be correct up to 10 parameters and then default to varargs (-1) # def proc_from_closure(closure) return Puppet::Pops::Evaluator::PuppetProc.new(closure) { |*args| closure.call(*args) } unless RUBY_VERSION[0,3] == '1.8' # This code is required since a Proc isn't propagated by reference in Ruby 1.8.x. It produces a standard # Proc that has correct arity as a replacement for the otherwise used PuppetProc # TODO: Remove when Ruby 1.8.x support is dropped arity = closure.parameters.reduce(0) do |memo, param| count = memo + 1 break -count if param.captures_rest || !param.value.nil? count end case arity when 0 proc { || closure.call } when 1 proc { |a| closure.call(a) } when 2 proc { |a, b| closure.call(a, b) } when 3 proc { |a, b, c| closure.call(a, b, c) } when 4 proc { |a, b, c, d| closure.call(a, b, c, d) } when 5 proc { |a, b, c, d, e| closure.call(a, b, c, d, e) } when 6 proc { |a, b, c, d, e, f| closure.call(a, b, c, d, e, f) } when 7 proc { |a, b, c, d, e, f, g| closure.call(a, b, c, d, e, f, g) } when 8 proc { |a, b, c, d, e, f, g, h| closure.call(a, b, c, d, e, f, g, h) } when 9 proc { |a, b, c, d, e, f, g, h, i| closure.call(a, b, c, d, e, f, g, h, i) } when 10 proc { |a, b, c, d, e, f, g, h, i, j| closure.call(a, b, c, d, e, f, g, h, i, j) } when -1 proc { |*v| closure.call(*v) } when -2 proc { |a, *v| closure.call(a, *v) } when -3 proc { |a, b, *v| closure.call(a, b, *v) } when -4 proc { |a, b, c, *v| closure.call(a, b, c, *v) } when -5 proc { |a, b, c, d, *v| closure.call(a, b, c, d, *v) } when -6 proc { |a, b, c, d, e, *v| closure.call(a, b, c, d, e, *v) } when -7 proc { |a, b, c, d, e, f, *v| closure.call(a, b, c, d, e, f, *v) } when -8 proc { |a, b, c, d, e, f, g, *v| closure.call(a, b, c, d, e, f, g, *v) } when -9 proc { |a, b, c, d, e, f, g, h, *v| closure.call(a, b, c, d, e, f,g, h, *v) } when -10 proc { |a, b, c, d, e, f, g, h, i, *v| closure.call(a, b, c, d, e, f,g, h, i, *v) } else proc { |*a| closure.call(*a) } end end private :proc_from_closure # @example # $x ? { 10 => true, 20 => false, default => 0 } # def eval_SelectorExpression o, scope # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the selector expression. # with_guarded_scope(scope) do test = evaluate(o.left_expr, scope) + @migration_checker.report_uc_bareword_type(test, o.left_expr) + the_default = nil selected = o.selectors.find do |s| me = s.matching_expr case me when Puppet::Pops::Model::LiteralDefault the_default = s.value_expr false when Puppet::Pops::Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) - evaluate(me, scope).any? {|v| is_match?(test, v, me, scope) } + evaluate(me, scope).any? {|v| is_match?(test, v, me, s, scope) } else - is_match?(test, evaluate(me, scope), me, scope) + is_match?(test, evaluate(me, scope), me, s, scope) end end if selected evaluate(selected.value_expr, scope) elsif the_default evaluate(the_default, scope) else fail(Issues::UNMATCHED_SELECTOR, o.left_expr, :param_value => test) end end end # SubLocatable is simply an expression that holds location information def eval_SubLocatedExpression o, scope evaluate(o.expr, scope) end # Evaluates Puppet DSL Heredoc def eval_HeredocExpression o, scope result = evaluate(o.text_expr, scope) assert_external_syntax(scope, result, o.syntax, o.text_expr) result end # Evaluates Puppet DSL `if` def eval_IfExpression o, scope with_guarded_scope(scope) do - if is_true?(evaluate(o.test, scope)) + if is_true?(evaluate(o.test, scope), o.test) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates Puppet DSL `unless` def eval_UnlessExpression o, scope with_guarded_scope(scope) do - unless is_true?(evaluate(o.test, scope)) + unless is_true?(evaluate(o.test, scope), o.test) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates a variable (getting its value) # The evaluator is lenient; any expression producing a String is used as a name # of a variable. # def eval_VariableExpression o, scope # Evaluator is not too fussy about what constitutes a name as long as the result # is a String and a valid variable name # name = evaluate(o.expr, scope) # Should be caught by validation, but make this explicit here as well, or mysterious evaluation issues # may occur for some evaluation use cases. case name when String when Numeric else fail(Issues::ILLEGAL_VARIABLE_EXPRESSION, o.expr) end get_variable_value(name, o, scope) end # Evaluates double quoted strings that may contain interpolation # def eval_ConcatenatedString o, scope o.segments.collect {|expr| string(evaluate(expr, scope), scope)}.join end # If the wrapped expression is a QualifiedName, it is taken as the name of a variable in scope. # Note that this is different from the 3.x implementation, where an initial qualified name # is accepted. (e.g. `"---${var + 1}---"` is legal. This implementation requires such concrete # syntax to be expressed in a model as `(TextExpression (+ (Variable var) 1)` - i.e. moving the decision to # the parser. # # Semantics; the result of an expression is turned into a string, nil is silently transformed to empty # string. # @return [String] the interpolated result # def eval_TextExpression o, scope if o.expr.is_a?(Puppet::Pops::Model::QualifiedName) string(get_variable_value(o.expr.value, o, scope), scope) else string(evaluate(o.expr, scope), scope) end end def string_Object(o, scope) o.to_s end def string_Symbol(o, scope) if :undef == o # optimized comparison 1.44 vs 1.95 EMPTY_STRING else o.to_s end end def string_Array(o, scope) "[#{o.map {|e| string(e, scope)}.join(COMMA_SEPARATOR)}]" end def string_Hash(o, scope) "{#{o.map {|k,v| "#{string(k, scope)} => #{string(v, scope)}"}.join(COMMA_SEPARATOR)}}" end def string_Regexp(o, scope) "/#{o.source}/" end def string_PAnyType(o, scope) @@type_calculator.string(o) end # Produces concatenation / merge of x and y. # # When x is an Array, y of type produces: # # * Array => concatenation `[1,2], [3,4] => [1,2,3,4]` # * Hash => concatenation of hash as array `[key, value, key, value, ...]` # * any other => concatenation of single value # # When x is a Hash, y of type produces: # # * Array => merge of array interpreted as `[key, value, key, value,...]` # * Hash => a merge, where entries in `y` overrides # * any other => error # # When x is something else, wrap it in an array first. # # When x is nil, an empty array is used instead. # # @note to concatenate an Array, nest the array - i.e. `[1,2], [[2,3]]` # # @overload concatenate(obj_x, obj_y) # @param obj_x [Object] object to wrap in an array and concatenate to; see other overloaded methods for return type # @param ary_y [Object] array to concatenate at end of `ary_x` # @return [Object] wraps obj_x in array before using other overloaded option based on type of obj_y # @overload concatenate(ary_x, ary_y) # @param ary_x [Array] array to concatenate to # @param ary_y [Array] array to concatenate at end of `ary_x` # @return [Array] new array with `ary_x` + `ary_y` # @overload concatenate(ary_x, hsh_y) # @param ary_x [Array] array to concatenate to # @param hsh_y [Hash] converted to array form, and concatenated to array # @return [Array] new array with `ary_x` + `hsh_y` converted to array # @overload concatenate (ary_x, obj_y) # @param ary_x [Array] array to concatenate to # @param obj_y [Object] non array or hash object to add to array # @return [Array] new array with `ary_x` + `obj_y` added as last entry # @overload concatenate(hsh_x, ary_y) # @param hsh_x [Hash] the hash to merge with # @param ary_y [Array] array interpreted as even numbered sequence of key, value merged with `hsh_x` # @return [Hash] new hash with `hsh_x` merged with `ary_y` interpreted as hash in array form # @overload concatenate(hsh_x, hsh_y) # @param hsh_x [Hash] the hash to merge to # @param hsh_y [Hash] hash merged with `hsh_x` # @return [Hash] new hash with `hsh_x` merged with `hsh_y` # @raise [ArgumentError] when `xxx_x` is neither an Array nor a Hash # @raise [ArgumentError] when `xxx_x` is a Hash, and `xxx_y` is neither Array nor Hash. # def concatenate(x, y) x = [x] unless x.is_a?(Array) || x.is_a?(Hash) case x when Array y = case y when Array then y when Hash then y.to_a else [y] end x + y # new array with concatenation when Hash y = case y when Hash then y when Array # Hash[[a, 1, b, 2]] => {} # Hash[a,1,b,2] => {a => 1, b => 2} # Hash[[a,1], [b,2]] => {[a,1] => [b,2]} # Hash[[[a,1], [b,2]]] => {a => 1, b => 2} # Use type calcultor to determine if array is Array[Array[?]], and if so use second form # of call t = @@type_calculator.infer(y) if t.element_type.is_a? Puppet::Pops::Types::PArrayType Hash[y] else Hash[*y] end else raise ArgumentError.new("Can only append Array or Hash to a Hash") end x.merge y # new hash with overwrite else raise ArgumentError.new("Can only append to an Array or a Hash.") end end # Produces the result x \ y (set difference) # When `x` is an Array, `y` is transformed to an array and then all matching elements removed from x. # When `x` is a Hash, all contained keys are removed from x as listed in `y` if it is an Array, or all its keys if it is a Hash. # The difference is returned. The given `x` and `y` are not modified by this operation. # @raise [ArgumentError] when `x` is neither an Array nor a Hash # def delete(x, y) result = x.dup case x when Array y = case y when Array then y when Hash then y.to_a else [y] end y.each {|e| result.delete(e) } when Hash y = case y when Array then y when Hash then y.keys else [y] end y.each {|e| result.delete(e) } else raise ArgumentError.new("Can only delete from an Array or Hash.") end result end # Implementation of case option matching. # # This is the type of matching performed in a case option, using == for every type # of value except regular expression where a match is performed. # - def is_match? left, right, o, scope + def is_match?(left, right, o, option_expr, scope) + @migration_checker.report_option_type_mismatch(left, right, option_expr, o) if right.is_a?(Regexp) return false unless left.is_a? String matched = right.match(left) set_match_data(matched, scope) # creates or clears ephemeral !!matched # convert to boolean elsif right.is_a?(Puppet::Pops::Types::PAnyType) + @migration_checker.report_uc_bareword_type(right, o) + # right is a type and left is not - check if left is an instance of the given type # (The reverse is not terribly meaningful - computing which of the case options that first produces # an instance of a given type). # @@type_calculator.instance?(right, left) else # Handle equality the same way as the language '==' operator (case insensitive etc.) @@compare_operator.equals(left,right) end end def with_guarded_scope(scope) scope_memo = get_scope_nesting_level(scope) begin yield ensure set_scope_nesting_level(scope, scope_memo) end end # Maps the expression in the given array to their product except for UnfoldExpressions which are first unfolded. # The result is added to the given result Array. # @param result [Array] Where to add the result (may contain information to add to) # @param array [Array[Puppet::Pops::Model::Expression] the expressions to map # @param scope [Puppet::Parser::Scope] the scope to evaluate in # @return [Array] the given result array with content added from the operation # def unfold(result, array, scope) array.each do |x| if x.is_a?(Puppet::Pops::Model::UnfoldExpression) result.concat(evaluate(x, scope)) else result << evaluate(x, scope) end end result end private :unfold end diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb index c9250e954..48ca19280 100644 --- a/lib/puppet/pops/evaluator/runtime3_support.rb +++ b/lib/puppet/pops/evaluator/runtime3_support.rb @@ -1,486 +1,489 @@ # A module with bindings between the new evaluator and the 3x runtime. # The intention is to separate all calls into scope, compiler, resource, etc. in this module # to make it easier to later refactor the evaluator for better implementations of the 3x classes. # # @api private module Puppet::Pops::Evaluator::Runtime3Support NAME_SPACE_SEPARATOR = '::'.freeze # Fails the evaluation of _semantic_ with a given issue. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def fail(issue, semantic, options={}, except=nil) optionally_fail(issue, semantic, options, except) # an error should have been raised since fail always fails raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end # Optionally (based on severity) Fails the evaluation of _semantic_ with a given issue # If the given issue is configured to be of severity < :error it is only reported, and the function returns. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def optionally_fail(issue, semantic, options={}, except=nil) if except.nil? # Want a stacktrace, and it must be passed as an exception begin raise EvaluationError.new() rescue EvaluationError => e except = e end end diagnostic_producer.accept(issue, semantic, options, except) end # Binds the given variable name to the given value in the given scope. # The reference object `o` is intended to be used for origin information - the 3x scope implementation # only makes use of location when there is an error. This is now handled by other mechanisms; first a check # is made if a variable exists and an error is raised if attempting to change an immutable value. Errors # in name, numeric variable assignment etc. have also been validated prior to this call. In the event the # scope.setvar still raises an error, the general exception handling for evaluation of the assignment # expression knows about its location. Because of this, there is no need to extract the location for each # setting (extraction is somewhat expensive since 3x requires line instead of offset). # def set_variable(name, value, o, scope) # Scope also checks this but requires that location information are passed as options. # Those are expensive to calculate and a test is instead made here to enable failing with better information. # The error is not specific enough to allow catching it - need to check the actual message text. # TODO: Improve the messy implementation in Scope. # if scope.bound?(name) if Puppet::Parser::Scope::RESERVED_VARIABLE_NAMES.include?(name) fail(Puppet::Pops::Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) else fail(Puppet::Pops::Issues::ILLEGAL_REASSIGNMENT, o, {:name => name} ) end end scope.setvar(name, value) end # Returns the value of the variable (nil is returned if variable has no value, or if variable does not exist) # def get_variable_value(name, o, scope) # Puppet 3x stores all variables as strings (then converts them back to numeric with a regexp... to see if it is a match variable) # Not ideal, scope should support numeric lookup directly instead. # TODO: consider fixing scope catch(:undefined_variable) { x = scope.lookupvar(name.to_s) # Must convert :undef back to nil - this can happen when an undefined variable is used in a # parameter's default value expression - there nil must be :undef to work with the rest of 3x. # Now that the value comes back to 4x it is changed to nil. return (x == :undef) ? nil : x } # It is always ok to reference numeric variables even if they are not assigned. They are always undef # if not set by a match expression. # unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME fail(Puppet::Pops::Issues::UNKNOWN_VARIABLE, o, {:name => name}) end end # Returns true if the variable of the given name is set in the given most nested scope. True is returned even if # variable is bound to nil. # def variable_bound?(name, scope) scope.bound?(name.to_s) end # Returns true if the variable is bound to a value or nil, in the scope or it's parent scopes. # def variable_exists?(name, scope) scope.exist?(name.to_s) end def set_match_data(match_data, scope) # See set_variable for rationale for not passing file and line to ephemeral_from. # NOTE: The 3x scope adds one ephemeral(match) to its internal stack per match that succeeds ! It never # clears anything. Thus a context that performs many matches will get very deep (there simply is no way to # clear the match variables without rolling back the ephemeral stack.) # This implementation does not attempt to fix this, it behaves the same bad way. unless match_data.nil? scope.ephemeral_from(match_data) end end # Creates a local scope with vairalbes set from a hash of variable name to value # def create_local_scope_from(hash, scope) # two dummy values are needed since the scope tries to give an error message (can not happen in this # case - it is just wrong, the error should be reported by the caller who knows in more detail where it # is in the source. # raise ArgumentError, "Internal error - attempt to create a local scope without a hash" unless hash.is_a?(Hash) scope.ephemeral_from(hash) end # Creates a nested match scope def create_match_scope_from(scope) # Create a transparent match scope (for future matches) scope.new_match_scope(nil) end def get_scope_nesting_level(scope) scope.ephemeral_level end def set_scope_nesting_level(scope, level) # Yup, 3x uses this method to reset the level, it also supports passing :all to destroy all # ephemeral/local scopes - which is a sure way to create havoc. # scope.unset_ephemeral_var(level) end # Adds a relationship between the given `source` and `target` of the given `relationship_type` # @param source [Puppet:Pops::Types::PCatalogEntryType] the source end of the relationship (from) # @param target [Puppet:Pops::Types::PCatalogEntryType] the target end of the relationship (to) # @param relationship_type [:relationship, :subscription] the type of the relationship # def add_relationship(source, target, relationship_type, scope) # The 3x way is to record a Puppet::Parser::Relationship that is evaluated at the end of the compilation. # This means it is not possible to detect any duplicates at this point (and signal where an attempt is made to # add a duplicate. There is also no location information to signal the original place in the logic. The user will have # to go fish. # The 3.x implementation is based on Strings :-o, so the source and target must be transformed. The resolution is # done by Catalog#resource(type, title). To do that, it creates a Puppet::Resource since it is responsible for # translating the name/type/title and create index-keys used by the catalog. The Puppet::Resource has bizarre parsing of # the type and title (scan for [] that is interpreted as type/title (but it gets it wrong). # Moreover if the type is "" or "component", the type is Class, and if the type is :main, it is :main, all other cases # undergo capitalization of name-segments (foo::bar becomes Foo::Bar). (This was earlier done in the reverse by the parser). # Further, the title undergoes the same munging !!! # # That bug infested nest of messy logic needs serious Exorcism! # # Unfortunately it is not easy to simply call more intelligent methods at a lower level as the compiler evaluates the recorded # Relationship object at a much later point, and it is responsible for invoking all the messy logic. # # TODO: Revisit the below logic when there is a sane implementation of the catalog, compiler and resource. For now # concentrate on transforming the type references to what is expected by the wacky logic. # # HOWEVER, the Compiler only records the Relationships, and the only method it calls is @relationships.each{|x| x.evaluate(catalog) } # Which means a smarter Relationship class could do this right. Instead of obtaining the resource from the catalog using # the borked resource(type, title) which creates a resource for the purpose of looking it up, it needs to instead # scan the catalog's resources # # GAAAH, it is even worse! # It starts in the parser, which parses "File['foo']" into an AST::ResourceReference with type = File, and title = foo # This AST is evaluated by looking up the type/title in the scope - causing it to be loaded if it exists, and if not, the given # type name/title is used. It does not search for resource instances, only classes and types. It returns symbolic information # [type, [title, title]]. From this, instances of Puppet::Resource are created and returned. These only have type/title information # filled out. One or an array of resources are returned. # This set of evaluated (empty reference) Resource instances are then passed to the relationship operator. It creates a # Puppet::Parser::Relationship giving it a source and a target that are (empty reference) Resource instances. These are then remembered # until the relationship is evaluated by the compiler (at the end). When evaluation takes place, the (empty reference) Resource instances # are converted to String (!?! WTF) on the simple format "#{type}[#{title}]", and the catalog is told to find a resource, by giving # it this string. If it cannot find the resource it fails, else the before/notify parameter is appended with the target. # The search for the resource begin with (you guessed it) again creating an (empty reference) resource from type and title (WTF?!?!). # The catalog now uses the reference resource to compute a key [r.type, r.title.to_s] and also gets a uniqueness key from the # resource (This is only a reference type created from title and type). If it cannot find it with the first key, it uses the # uniqueness key to lookup. # # This is probably done to allow a resource type to munge/translate the title in some way (but it is quite unclear from the long # and convoluted path of evaluation. # In order to do this in a way that is similar to 3.x two resources are created to be used as keys. # # And if that is not enough, a source/target may be a Collector (a baked query that will be evaluated by the # compiler - it is simply passed through here for processing by the compiler at the right time). # if source.is_a?(Puppet::Parser::Collector) || source.is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) # use verbatim - behavior defined by 3x source_resource = source else # transform into the wonderful String representation in 3x type, title = Puppet::Pops::Evaluator::Runtime3Converter.instance.catalog_type_to_split_type_title(source) source_resource = Puppet::Resource.new(type, title) end if target.is_a?(Puppet::Parser::Collector) || target.is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) # use verbatim - behavior defined by 3x target_resource = target else # transform into the wonderful String representation in 3x type, title = Puppet::Pops::Evaluator::Runtime3Converter.instance.catalog_type_to_split_type_title(target) target_resource = Puppet::Resource.new(type, title) end # Add the relationship to the compiler for later evaluation. scope.compiler.add_relationship(Puppet::Parser::Relationship.new(source_resource, target_resource, relationship_type)) end # Coerce value `v` to numeric or fails. # The given value `v` is coerced to Numeric, and if that fails the operation # calls {#fail}. # @param v [Object] the value to convert # @param o [Object] originating instruction # @param scope [Object] the (runtime specific) scope where evaluation of o takes place # @return [Numeric] value `v` converted to Numeric. # def coerce_numeric(v, o, scope) unless n = Puppet::Pops::Utils.to_n(v) fail(Puppet::Pops::Issues::NOT_NUMERIC, o, {:value => v}) end n end def call_function(name, args, o, scope, &block) # Call via 4x API if the function exists there loaders = scope.compiler.loaders # find the loader that loaded the code, or use the private_environment_loader (sees env + all modules) adapter = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::LoaderAdapter) loader = adapter.nil? ? loaders.private_environment_loader : adapter.loader if loader && func = loader.load(:function, name) return func.call(scope, *args, &block) end # Call via 3x API if function exists there fail(Puppet::Pops::Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name) # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. # NOTE: Passing an empty string last converts nil/:undef to empty string mapped_args = Puppet::Pops::Evaluator::Runtime3Converter.map_args(args, scope, '') result = scope.send("function_#{name}", mapped_args, &block) # Prevent non r-value functions from leaking their result (they are not written to care about this) Puppet::Parser::Functions.rvalue?(name) ? result : nil end # The o is used for source reference def create_resource_parameter(o, scope, name, value, operator) file, line = extract_file_line(o) Puppet::Parser::Resource::Param.new( :name => name, :value => convert(value, scope, nil), # converted to 3x since 4x supports additional objects / types :source => scope.source, :line => line, :file => file, :add => operator == :'+>' ) end def convert(value, scope, undef_value) Puppet::Pops::Evaluator::Runtime3Converter.convert(value, scope, undef_value) end CLASS_STRING = 'class'.freeze def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) # TODO: Unknown resource causes creation of Resource to fail with ArgumentError, should give # a proper Issue. Now the result is "Error while evaluating a Resource Statement" with the message # from the raised exception. (It may be good enough). # resolve in scope. fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type_name, resource_titles) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work). # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) # Build a resource for each title resource_titles.map do |resource_title| resource = Puppet::Parser::Resource.new( fully_qualified_type, resource_title, :parameters => evaluated_parameters, :file => file, :line => line, :exported => exported, :virtual => virtual, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope, :strict => true ) if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) scope.compiler.evaluate_classes([resource_title], scope, false, true) if fully_qualified_type == CLASS_STRING # Turn the resource into a PType (a reference to a resource type) # weed out nil's resource_to_ptype(resource) end end # Defines default parameters for a type with the given name. # def create_resource_defaults(o, scope, type_name, evaluated_parameters) # Note that name must be capitalized in this 3x call # The 3x impl creates a Resource instance with a bogus title and then asks the created resource # for the type of the name. # Note, locations are available per parameter. # scope.define_settings(capitalize_qualified_name(type_name), evaluated_parameters) end # Capitalizes each segment of a qualified name # def capitalize_qualified_name(name) name.split(/::/).map(&:capitalize).join(NAME_SPACE_SEPARATOR) end # Creates resource overrides for all resource type objects in evaluated_resources. The same set of # evaluated parameters are applied to all. # def create_resource_overrides(o, scope, evaluated_resources, evaluated_parameters) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work. # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) evaluated_resources.each do |r| unless r.is_a?(Puppet::Pops::Types::PResourceType) && r.type_name != 'class' fail(Puppet::Pops::Issues::ILLEGAL_OVERRIDEN_TYPE, o, {:actual => r} ) end resource = Puppet::Parser::Resource.new( r.type_name, r.title, :parameters => evaluated_parameters, :file => file, :line => line, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope ) scope.compiler.add_override(resource) end end # Finds a resource given a type and a title. # def find_resource(scope, type_name, title) scope.compiler.findresource(type_name, title) end # Returns the value of a resource's parameter by first looking up the parameter in the resource # and then in the defaults for the resource. Since the resource exists (it must in order to look up its # parameters, any overrides have already been applied). Defaults are not applied to a resource until it # has been finished (which typically has not taken place when this is evaluated; hence the dual lookup). # def get_resource_parameter_value(scope, resource, parameter_name) # This gets the parameter value, or nil (for both valid parameters and parameters that do not exist). val = resource[parameter_name] # Sometimes the resource is a Puppet::Parser::Resource and sometimes it is # a Puppet::Resource. The Puppet::Resource case occurs when puppet language # is evaluated against an already completed catalog (where all instances of # Puppet::Parser::Resource are converted to Puppet::Resource instances). # Evaluating against an already completed catalog is really only found in # the language specification tests, where the puppet language is used to # test itself. if resource.is_a?(Puppet::Parser::Resource) # The defaults must be looked up in the scope where the resource was created (not in the given # scope where the lookup takes place. resource_scope = resource.scope if val.nil? && resource_scope && defaults = resource_scope.lookupdefaults(resource.type) # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds # name and value. # NOTE: meta parameters that are unset ends up here, and there are no defaults for those encoded # in the defaults, they may receive hardcoded defaults later (e.g. 'tag'). param = defaults[parameter_name.to_sym] # Some parameters (meta parameters like 'tag') does not return a param from which the value can be obtained # at all times. Instead, they return a nil param until a value has been set. val = param.nil? ? nil : param.value end end val end # Returns true, if the given name is the name of a resource parameter. # def is_parameter_of_resource?(scope, resource, name) return false unless name.is_a?(String) resource.valid_parameter?(name) end def resource_to_ptype(resource) nil if resource.nil? # inference returns the meta type since the 3x Resource is an alternate way to describe a type type_calculator.infer(resource).type end # This is the same type of "truth" as used in the current Puppet DSL. # - def is_true? o + def is_true?(value, o) # Is the value true? This allows us to control the definition of truth # in one place. - case o + case value # Support :undef since it may come from a 3x structure when :undef false + when String + @migration_checker.report_empty_string_true(value, o) + true else - !!o + !!value end end # Utility method for TrueClass || FalseClass # @param x [Object] the object to test if it is instance of TrueClass or FalseClass def is_boolean? x x.is_a?(TrueClass) || x.is_a?(FalseClass) end def extract_file_line(o) source_pos = Puppet::Pops::Utils.find_closest_positioned(o) return [nil, -1] unless source_pos [source_pos.locator.file, source_pos.line] end def find_closest_positioned(o) return nil if o.nil? || o.is_a?(Puppet::Pops::Model::Program) o.offset.nil? ? find_closest_positioned(o.eContainer) : Puppet::Pops::Adapters::SourcePosAdapter.adapt(o) end # Creates a diagnostic producer def diagnostic_producer Puppet::Pops::Validation::DiagnosticProducer.new( ExceptionRaisingAcceptor.new(), # Raises exception on all issues SeverityProducer.new(), # All issues are errors Puppet::Pops::Model::ModelLabelProvider.new()) end # Configure the severity of failures class SeverityProducer < Puppet::Pops::Validation::SeverityProducer Issues = Puppet::Pops::Issues def initialize super p = self # Issues triggering warning only if --debug is on if Puppet[:debug] p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :warning else p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore end # Store config issues, ignore or warning p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning end end # An acceptor of diagnostics that immediately raises an exception. class ExceptionRaisingAcceptor < Puppet::Pops::Validation::Acceptor def accept(diagnostic) super Puppet::Pops::IssueReporter.assert_and_report(self, { :message => "Evaluation Error:", :emit_warnings => true, # log warnings :exception_class => Puppet::PreformattedError }) if errors? raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end end end class EvaluationError < StandardError end end diff --git a/lib/puppet/pops/issue_reporter.rb b/lib/puppet/pops/issue_reporter.rb index 02b173c14..895160d31 100644 --- a/lib/puppet/pops/issue_reporter.rb +++ b/lib/puppet/pops/issue_reporter.rb @@ -1,87 +1,113 @@ class Puppet::Pops::IssueReporter # @param acceptor [Puppet::Pops::Validation::Acceptor] the acceptor containing reported issues # @option options [String] :message (nil) A message text to use as prefix in # a single Error message # @option options [Boolean] :emit_warnings (false) whether warnings should be emitted # @option options [Boolean] :emit_errors (true) whether errors should be # emitted or only the given message # @option options [Exception] :exception_class (Puppet::ParseError) The exception to raise # def self.assert_and_report(acceptor, options) return unless acceptor - max_errors = Puppet[:max_errors] - max_warnings = Puppet[:max_warnings] - max_deprecations = - if Puppet[:disable_warnings].include?('deprecations') - 0 - else - Puppet[:max_deprecations] - end + max_errors = options[:max_errors] || Puppet[:max_errors] + max_warnings = options[:max_warnings] || Puppet[:max_warnings] + max_deprecations = options[:max_deprecations] || (Puppet[:disable_warnings].include?('deprecations') ? 0 : Puppet[:max_deprecations]) - emit_warnings = options[:emit_warnings] || false - emit_errors = options[:emit_errors].nil? ? true : !!options[:emit_errors] - emit_message = options[:message] - emit_exception = options[:exception_class] || Puppet::ParseError + emit_warnings = options[:emit_warnings] || false + emit_errors = options[:emit_errors].nil? ? true : !!options[:emit_errors] + emit_message = options[:message] + emit_exception = options[:exception_class] || Puppet::ParseErrorWithIssue # If there are warnings output them warnings = acceptor.warnings if emit_warnings && warnings.size > 0 formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new emitted_w = 0 emitted_dw = 0 acceptor.warnings.each do |w| if w.severity == :deprecation # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not # deprecation of constructs in manifests! (It is not designed for that purpose even if # used throughout the code base). # - Puppet.warning(formatter.format(w)) if emitted_dw < max_deprecations + log_message(:warning, formatter, w) if emitted_dw < max_deprecations emitted_dw += 1 else - Puppet.warning(formatter.format(w)) if emitted_w < max_warnings + log_message(:warning, formatter, w) if emitted_w < max_warnings emitted_w += 1 end break if emitted_w >= max_warnings && emitted_dw >= max_deprecations # but only then end end # If there were errors, report the first found. Use a puppet style formatter. errors = acceptor.errors if errors.size > 0 unless emit_errors raise emit_exception.new(emit_message) end formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new if errors.size == 1 || max_errors <= 1 # raise immediately - exception = emit_exception.new(format_with_prefix(emit_message, formatter.format(errors[0]))) + exception = create_exception(emit_exception, emit_message, formatter, errors[0]) # if an exception was given as cause, use it's backtrace instead of the one indicating "here" if errors[0].exception exception.set_backtrace(errors[0].exception.backtrace) end raise exception end emitted = 0 if emit_message Puppet.err(emit_message) end errors.each do |e| - Puppet.err(formatter.format(e)) + log_message(:err, formatter, e) emitted += 1 break if emitted >= max_errors end warnings_message = (emit_warnings && warnings.size > 0) ? ", and #{warnings.size} warnings" : "" giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up" exception = emit_exception.new(giving_up_message) exception.file = errors[0].file raise exception end end def self.format_with_prefix(prefix, message) return message unless prefix [prefix, message].join(' ') end + + def self.create_exception(exception_class, emit_message, formatter, diagnostic) + file = diagnostic.file + file = (file.is_a?(String) && file.empty?) ? nil : file + line = pos = nil + if diagnostic.source_pos + line = diagnostic.source_pos.line + pos = diagnostic.source_pos.pos + end + exception_class.new(format_with_prefix(emit_message, formatter.format_message(diagnostic)), file, line, pos, nil, diagnostic.issue.issue_code) + end + private_class_method :create_exception + + def self.log_message(severity, formatter, diagnostic) + file = diagnostic.file + file = (file.is_a?(String) && file.empty?) ? nil : file + line = pos = nil + if diagnostic.source_pos + line = diagnostic.source_pos.line + pos = diagnostic.source_pos.pos + end + Puppet::Util::Log.create({ + :level => severity, + :message => formatter.format_message(diagnostic), + :issue_code => diagnostic.issue.issue_code, + :file => file, + :line => line, + :pos => pos, + }) + end + private_class_method :log_message end diff --git a/lib/puppet/pops/migration/migration_checker.rb b/lib/puppet/pops/migration/migration_checker.rb new file mode 100644 index 000000000..efd756cec --- /dev/null +++ b/lib/puppet/pops/migration/migration_checker.rb @@ -0,0 +1,32 @@ +# This class defines the private API of the MigrationChecker support. +# @api private +# +class Puppet::Pops::Migration::MigrationChecker + + def initialize() + end + + def report_ambiguous_integer(o) + end + + def report_ambiguous_float(o) + end + + def report_empty_string_true(value, o) + end + + def report_uc_bareword_type(value, o) + end + + def report_equality_type_mismatch(left, right, o) + end + + def report_option_type_mismatch(test_value, option_value, option_expr, matching_expr) + end + + def report_in_expression(o) + end + + def report_array_last_in_block(o) + end +end diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra index e2e150141..8b386c4c0 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -1,759 +1,759 @@ # vim: syntax=ruby # Parser using the Pops model, expression based class Puppet::Pops::Parser::Parser token STRING DQPRE DQMID DQPOST token WORD token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS DELETES LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE token DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT ATAT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS PIPE token LAMBDA SELBRACE token NUMBER token HEREDOC SUBLOCATE token RENDER_STRING RENDER_EXPR EPP_START EPP_END EPP_END_TRIM token FUNCTION token PRIVATE ATTR TYPE token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN left RPAREN left DOT nonassoc EPP_START left LBRACK LISTSTART left RBRACK left QMARK left LCOLLECT LLCOLLECT right NOT nonassoc SPLAT nonassoc UMINUS left IN left MATCH NOMATCH left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left AND left OR left LBRACE left SELBRACE left RBRACE right AT ATAT right APPENDS DELETES EQUALS left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB left FARROW left COMMA nonassoc RENDER_EXPR nonassoc RENDER_STRING left LOW preclow rule # Produces [Model::Program] with a body containing what was parsed program : statements { result = create_program(Factory.block_or_expression(*val[0])) } | epp_expression { result = create_program(Factory.block_or_expression(*val[0])) } | { result = create_empty_program() } # Produces a semantic model (non validated, but semantically adjusted). statements : syntactic_statements { result = transform_calls(val[0]) } # Collects sequence of elements into a list that the statements rule can transform # (Needed because language supports function calls without parentheses around arguments). # Produces Array # syntactic_statements : syntactic_statement { result = [val[0]]} | syntactic_statements SEMIC syntactic_statement { result = val[0].push val[2] } | syntactic_statements syntactic_statement { result = val[0].push val[1] } # Produce a single expression or Array of expression # This exists to handle multiple arguments to non parenthesized function call. If e is expression, # the a program can consists of e [e,e,e] where the first may be a name of a function to call. # syntactic_statement : assignment =LOW { result = val[0] } | syntactic_statement COMMA assignment =LOW { result = aryfy(val[0]).push(val[1]).push(val[2]) } # Assignment (is right recursive since assignment is right associative) assignment : relationship =LOW | relationship EQUALS assignment { result = val[0].set(val[2]) ; loc result, val[1] } | relationship APPENDS assignment { result = val[0].plus_set(val[2]) ; loc result, val[1] } | relationship DELETES assignment { result = val[0].minus_set(val[2]); loc result, val[1] } assignments : assignment { result = [val[0]] } | assignments COMMA assignment { result = val[0].push(val[2]) } relationship : resource =LOW | relationship IN_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship IN_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship OUT_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship OUT_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } #-- RESOURCE # resource : expression = LOW #---VIRTUAL | AT resource { result = val[1] unless Factory.set_resource_form(result, :virtual) # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Virtual (@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] } #---EXPORTED | ATAT resource { result = val[1] unless Factory.set_resource_form(result, :exported) # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Exported (@@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] } #---RESOURCE TITLED 3x and 4x | resource LBRACE expression COLON attribute_operations additional_resource_bodies RBRACE { bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5] result = Factory.RESOURCE(val[0], bodies) loc result, val[0], val[6] } #---CLASS RESOURCE | CLASS LBRACE resource_bodies endsemi RBRACE { result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] } # --RESOURCE 3X Expression # Handles both 3x overrides and defaults (i.e. single resource_body without title colon) # Slated for possible deprecation since it requires transformation and mix static/evaluation check # | resource LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO should it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } resource_body : expression COLON attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } resource_bodies : resource_body =HIGH { result = [val[0]] } | resource_bodies SEMIC resource_body =HIGH { result = val[0].push val[2] } # This is a rule for the intermediate state where RACC has seen enough tokens to understand that # what is expressed is a Resource Expression, it now has to get to the finishing line # additional_resource_bodies : endcomma { result = [] } | endcomma SEMIC { result = [] } | endcomma SEMIC resource_bodies endsemi { result = val[2] } #-- EXPRESSION # expression : primary_expression | call_function_expression | expression LBRACK expressions RBRACK =LBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } | expression MATCH expression { result = val[0] =~ val[2] ; loc result, val[1] } | expression NOMATCH expression { result = val[0].mne val[2] ; loc result, val[1] } | expression PLUS expression { result = val[0] + val[2] ; loc result, val[1] } | expression MINUS expression { result = val[0] - val[2] ; loc result, val[1] } | expression DIV expression { result = val[0] / val[2] ; loc result, val[1] } | expression TIMES expression { result = val[0] * val[2] ; loc result, val[1] } | expression MODULO expression { result = val[0] % val[2] ; loc result, val[1] } | expression LSHIFT expression { result = val[0] << val[2] ; loc result, val[1] } | expression RSHIFT expression { result = val[0] >> val[2] ; loc result, val[1] } | MINUS expression =UMINUS { result = val[1].minus() ; loc result, val[0] } | TIMES expression =SPLAT { result = val[1].unfold() ; loc result, val[0] } | expression NOTEQUAL expression { result = val[0].ne val[2] ; loc result, val[1] } | expression ISEQUAL expression { result = val[0] == val[2] ; loc result, val[1] } | expression GREATERTHAN expression { result = val[0] > val[2] ; loc result, val[1] } | expression GREATEREQUAL expression { result = val[0] >= val[2] ; loc result, val[1] } | expression LESSTHAN expression { result = val[0] < val[2] ; loc result, val[1] } | expression LESSEQUAL expression { result = val[0] <= val[2] ; loc result, val[1] } | NOT expression { result = val[1].not ; loc result, val[0] } | expression AND expression { result = val[0].and val[2] ; loc result, val[1] } | expression OR expression { result = val[0].or val[2] ; loc result, val[1] } | expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] } | LPAREN assignment RPAREN { result = val[1].paren() ; loc result, val[0] } #---EXPRESSIONS # (i.e. "argument list") # # This expression list can not contain function calls without parentheses around arguments # Produces Array # expressions : expression { result = [val[0]] } | expressions COMMA expression { result = val[0].push(val[2]) } primary_expression : variable | call_method_with_lambda_expression | collection_expression | case_expression | if_expression | unless_expression | definition_expression | hostclass_expression | node_definition_expression | epp_render_expression | reserved_word | array | hash | regex | quotedtext | type | NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] } | BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } | DEFAULT { result = Factory.literal(:default) ; loc result, val[0] } | UNDEF { result = Factory.literal(:undef) ; loc result, val[0] } | NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } #---CALL FUNCTION # # Produces Model::CallNamedFunction call_function_expression : expression LPAREN assignments endcomma RPAREN { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] } | expression LPAREN RPAREN { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] } | expression LPAREN assignments endcomma RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] } | expression LPAREN RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] } #---CALL METHOD # call_method_with_lambda_expression : call_method_expression =LOW { result = val[0] } | call_method_expression lambda { result = val[0]; val[0].lambda = val[1] } call_method_expression : named_access LPAREN assignments RPAREN { result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] } | named_access LPAREN RPAREN { result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] } | named_access =LOW { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] } named_access : expression DOT NAME { result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] } #---LAMBDA # lambda : lambda_parameter_list lambda_rest { result = Factory.LAMBDA(val[0][:value], val[1][:value]) loc result, val[0][:start], val[1][:end] } lambda_rest : LBRACE statements RBRACE { result = {:end => val[2], :value =>val[1] } } | LBRACE RBRACE { result = {:end => val[1], :value => nil } } lambda_parameter_list : PIPE PIPE { result = {:start => val[0], :value => [] } } | PIPE parameters endcomma PIPE { result = {:start => val[0], :value => val[1] } } #---CONDITIONALS #--IF # if_expression : IF if_part { result = val[1] loc(result, val[0], val[1]) } # Produces Model::IfExpression if_part : expression LBRACE statements RBRACE else { result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) } | expression LBRACE RBRACE else { result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) } # Produces [Model::Expression, nil] - nil if there is no else or elsif part else : # nothing | ELSIF if_part { result = val[1] loc(result, val[0], val[1]) } | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--UNLESS # unless_expression : UNLESS expression LBRACE statements RBRACE unless_else { result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] } | UNLESS expression LBRACE RBRACE unless_else { - result = Factory.UNLESS(val[1], nil, nil) + result = Factory.UNLESS(val[1], nil, val[4]) loc result, val[0], val[4] } # Different from else part of if, since "elsif" is not supported, but 'else' is # # Produces [Model::Expression, nil] - nil if there is no else or elsif part unless_else : # nothing | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--- CASE EXPRESSION # case_expression : CASE expression LBRACE case_options RBRACE { result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] } # Produces Array case_options : case_option { result = [val[0]] } | case_options case_option { result = val[0].push val[1] } # Produced Model::CaseOption (aka When) case_option : expressions COLON LBRACE options_statements RBRACE { result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] } options_statements : nil | statements # This special construct is required or racc will produce the wrong result when the selector entry # LHS is generalized to any expression (LBRACE looks like a hash). Thus it is not possible to write # a selector with a single entry where the entry LHS is a hash. # The SELBRACE token is a LBRACE that follows a QMARK, and this is produced by the lexer with a lookback # Produces Array # selector_entries : selector_entry | SELBRACE selector_entry_list endcomma RBRACE { result = val[1] } # Produces Array selector_entry_list : selector_entry { result = [val[0]] } | selector_entry_list COMMA selector_entry { result = val[0].push val[2] } # Produces a Model::SelectorEntry # This FARROW wins over FARROW in Hash selector_entry : expression FARROW expression { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } #---COLLECTION # # A Collection is a predicate applied to a set of objects with an implied context (used variables are # attributes of the object. # i.e. this is equivalent to source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) # collection_expression : expression collect_query LBRACE attribute_operations endcomma RBRACE { result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] } | expression collect_query =LOW { result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] } collect_query : LCOLLECT optional_query RCOLLECT { result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] } | LLCOLLECT optional_query RRCOLLECT { result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] } optional_query : nil | expression #---ATTRIBUTE OPERATIONS (Not an expression) # attribute_operations : { result = [] } | attribute_operation { result = [val[0]] } | attribute_operations COMMA attribute_operation { result = val[0].push(val[2]) } # Produces String # QUESTION: Why is BOOLEAN valid as an attribute name? # attribute_name : NAME | keyword # | BOOLEAN # In this version, illegal combinations are validated instead of producing syntax errors # (Can give nicer error message "+> is not applicable to...") # Produces Model::AttributeOperation # attribute_operation : attribute_name FARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] } | attribute_name PARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] } | TIMES FARROW expression { result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2] } #---DEFINE # # Produces Model::Definition # definition_expression : DEFINE classname parameter_list LBRACE opt_statements RBRACE { result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end } #---HOSTCLASS # # Produces Model::HostClassDefinition # hostclass_expression : CLASS stacked_classname parameter_list classparent LBRACE opt_statements RBRACE { # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] } # Record the classname so nested classes gets a fully qualified name at parse-time # This is a separate rule since racc does not support intermediate actions. # stacked_classname : classname { namestack(val[0][:value]) ; result = val[0] } opt_statements : statements | nil # Produces String, name or nil result classparent : nil | INHERITS classnameordefault { result = val[1] } # Produces String (this construct allows a class to be named "default" and to be referenced as # the parent class. # TODO: Investigate the validity # Produces a String (classname), or a token (DEFAULT). # classnameordefault : classname | DEFAULT #---NODE # # Produces Model::NodeDefinition # node_definition_expression : NODE hostnames endcomma nodeparent LBRACE statements RBRACE { result = add_definition(Factory.NODE(val[1], val[3], val[5])) loc result, val[0], val[6] } | NODE hostnames endcomma nodeparent LBRACE RBRACE { result = add_definition(Factory.NODE(val[1], val[3], nil)) loc result, val[0], val[5] } # Hostnames is not a list of names, it is a list of name matchers (including a Regexp). # (The old implementation had a special "Hostname" object with some minimal validation) # # Produces Array # hostnames : hostname { result = [result] } | hostnames COMMA hostname { result = val[0].push(val[2]) } # Produces a LiteralExpression (string, :default, or regexp) # String with interpolation is validated for better error message hostname : dotted_name | quotedtext | DEFAULT { result = Factory.literal(:default); loc result, val[0] } | regex dotted_name : name_or_number { result = Factory.literal(val[0][:value]); loc result, val[0] } | dotted_name DOT name_or_number { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } name_or_number : NAME | NUMBER # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } #---FUNCTION DEFINITION # #function_definition # For now the function word will just be reserved, in the future it will # produce a function definition # FUNCTION classname parameter_list LBRACE opt_statements RBRACE { # result = add_definition(Factory.FUNCTION(val[1][:value], val[2], val[4])) # loc result, val[0], val[5] # } #---NAMES AND PARAMETERS COMMON TO SEVERAL RULES # Produces String # TODO: The error that "class" is not a valid classname is bad - classname rule is also used for other things classname : NAME | WORD | CLASSREF | CLASS { error val[0], "'class' is not a valid classname" } # Produces Array parameter_list : nil { result = [] } | LPAREN RPAREN { result = [] } | LPAREN parameters endcomma RPAREN { result = val[1] } # Produces Array parameters : parameter { result = [val[0]] } | parameters COMMA parameter { result = val[0].push(val[2]) } # Produces Model::Parameter parameter : untyped_parameter | typed_parameter untyped_parameter : regular_parameter | splat_parameter regular_parameter : VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] } | VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] } splat_parameter : TIMES regular_parameter { result = val[1]; val[1].captures_rest() } typed_parameter : parameter_type untyped_parameter { val[1].type_expr(val[0]) ; result = val[1] } parameter_type : type { result = val[0] } | type LBRACK expressions RBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } #--VARIABLE # variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } #---RESERVED WORDS # reserved_word : FUNCTION { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | PRIVATE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | TYPE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | ATTR { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } #---LITERALS (dynamic and static) # array : LISTSTART assignments endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0], val[1] } | LBRACK assignments endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0], val[1] } hash : LBRACE hashpairs RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[2] } | LBRACE hashpairs COMMA RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[3] } | LBRACE RBRACE { result = Factory.literal({}) ; loc result, val[0], val[1] } hashpairs : hashpair { result = [val[0]] } | hashpairs COMMA hashpair { result = val[0].push val[2] } hashpair : assignment FARROW assignment { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } quotedtext : string | dq_string | heredoc string : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } | WORD { result = Factory.literal(val[0][:value]) ; loc result, val[0] } dq_string : dqpre dqrval { result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] } dqpre : DQPRE { result = Factory.literal(val[0][:value]); loc result, val[0] } dqpost : DQPOST { result = Factory.literal(val[0][:value]); loc result, val[0] } dqmid : DQMID { result = Factory.literal(val[0][:value]); loc result, val[0] } dqrval : text_expression dqtail { result = [val[0]] + val[1] } text_expression : assignment { result = Factory.TEXT(val[0]) } dqtail : dqpost { result = [val[0]] } | dqmid dqrval { result = [val[0]] + val[1] } heredoc : HEREDOC sublocated_text { result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] } sublocated_text : SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } | SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } epp_expression : EPP_START epp_parameters_list optional_statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] } optional_statements : | statements epp_parameters_list : =LOW{ result = nil } | PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } epp_render_expression : RENDER_STRING { result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] } | RENDER_EXPR expression epp_end { result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] } | RENDER_EXPR LBRACE statements RBRACE epp_end { result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] } epp_end : EPP_END | EPP_END_TRIM type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } regex : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } #---MARKERS, SPECIAL TOKENS, SYNTACTIC SUGAR, etc. endcomma : # | COMMA { result = nil } endsemi : # | SEMIC keyword : AND | CASE | CLASS | DEFAULT | DEFINE | ELSE | ELSIF | IF | IN | INHERITS | NODE | OR | UNDEF | UNLESS | TYPE | ATTR | FUNCTION | PRIVATE nil : { result = nil} end ---- header ---- require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end ---- inner ---- # Make emacs happy # Local Variables: # mode: ruby # End: diff --git a/lib/puppet/pops/parser/eparser.rb b/lib/puppet/pops/parser/eparser.rb index aadd1b1b5..c35b31617 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1,2663 +1,2663 @@ # # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.9 # from Racc grammer file "". # require 'racc/parser.rb' require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end module Puppet module Pops module Parser class Parser < Racc::Parser module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 755) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ '58,61,319,-238,59,53,155,54,238,80,-239,-227,335,280,-129,111,320,277', '371,336,370,-241,375,101,18,104,394,99,100,239,42,280,45,135,47,12,-240', '46,36,39,251,44,37,10,11,-238,113,66,17,103,110,38,-239,-227,15,16,-129', '301,112,278,58,61,67,-241,59,53,136,54,43,111,275,81,35,62,111,64,65', '63,-240,314,48,49,51,50,18,256,52,-236,111,342,42,107,45,254,47,115', '255,46,36,39,113,44,37,79,110,113,344,66,17,110,79,38,112,279,15,16', '79,112,280,113,58,61,67,110,59,53,238,54,43,-236,79,112,35,62,111,64', '65,269,270,66,48,49,51,50,18,71,52,239,331,371,42,370,45,328,47,12,352', '46,36,39,69,44,37,10,11,113,353,66,17,110,275,38,58,61,15,16,59,112', '258,257,58,61,67,276,59,53,355,54,43,72,73,74,35,62,111,64,65,256,302', '358,48,49,51,50,18,251,52,314,363,364,42,304,45,251,47,12,250,46,36', '39,249,44,37,10,11,113,368,66,17,110,308,38,361,372,15,16,374,112,75', '77,76,78,67,318,227,230,238,381,43,228,383,238,35,62,275,64,65,238,227', '66,48,49,51,50,58,61,52,239,59,53,66,54,324,79,79,217,216,387,239,71', '121,231,390,275,121,393,152,315,18,58,61,309,267,59,42,314,45,150,47', '12,238,46,36,39,397,44,37,10,11,374,399,66,17,66,400,38,401,122,15,16', '404,239,405,406,121,79,67,71,135,414,68,132,43,416,417,418,35,62,304', '64,65,,,,48,49,51,50,58,61,52,67,59,53,,54,410,80,,,,136,62,,,,,,,,', '101,18,104,,99,100,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,103', ',38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,81,35,62,,64,65,,,,48,49,51', '50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16', ',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,', '42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61', '67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47', '12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,', '54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36', '39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35', '62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,', ',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,', '48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38', ',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', ',52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', '61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', ',47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43', ',,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,408,,,,,,,,,,,,,', ',,18,58,61,,,59,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,', '15,16,,,,,,,67,,135,,,132,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52', '67,59,53,,54,403,80,,,,136,62,,,,,,,,,101,18,104,,99,100,,42,,45,,47', '12,,46,36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53', ',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36', '39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,', '66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,', '15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53', ',54,337,80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46,36,39', ',44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35', '62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,', ',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,139,54,43,,,,35,62,,64,65', ',,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17', ',,38,,,15,16,,,,,58,61,67,,59,53,141,54,43,,,,35,62,,64,65,,,,48,49', '51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,', '15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53', ',54,143,80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46,36,39', ',44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35', '62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10', '11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,', ',,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,', '38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50', '18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16', ',,,,58,61,67,,59,53,,154,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,', ',42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67', ',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12', ',46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', '43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39', ',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,', '66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38', ',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', ',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,', ',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42', ',45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67', ',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12', ',46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', '43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39', ',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11', ',,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38', ',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', ',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,', ',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,171', '185,177,186,47,178,188,179,36,170,,173,168,,,,,66,17,189,184,169,,,15', '167,,,,,,,67,,,,,187,172,,,,35,62,,64,65,,,,180,181,183,182,58,61,52', '80,59,53,,54,,,,,,,,,,101,,104,,99,100,,18,,,,,,42,,45,,47,115,,46,36', '39,,44,37,103,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35', '62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,', ',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,', '48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38', ',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', ',52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', '61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', ',47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', ',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36', '39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,', '66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,', '15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,', '52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', '61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', ',47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', ',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36', '39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,', '66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,', '15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,', '52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', '61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', ',47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', ',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36', '39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,', '66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,', '15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,', '52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', '61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', ',47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', ',54,43,,,213,35,62,,64,65,,,,48,49,51,50,18,215,52,,,,42,,45,,47,12', ',46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', '43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39', ',44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64', '65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17', ',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51', '50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16', ',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,', '42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67', ',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115', ',46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,', '276,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39', ',44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64', '65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17', ',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51', '50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15', '16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52', ',,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61', '67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47', '115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', '43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39', ',44,37,,,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48', '49,51,50,58,61,52,,59,53,,54,143,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12', ',46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', '43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39', ',44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64', '65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66', '17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61', '52,,59,53,,54,312,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44', '37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64', '65,,,,48,49,51,50,18,243,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11', ',,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', '49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38', ',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', ',52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', '61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', ',47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,', '35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,376,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,115,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61', '67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47', '12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,', '54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36', '39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', ',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,115,,46,36,39,,44,37,,,,', '66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58', '61,52,,59,53,,54,322,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39', ',44,37,10,11,80,,66,17,,,38,,,15,16,,,91,101,,104,67,99,100,,92,,43', ',,,35,62,,64,65,,,,48,49,51,50,,103,52,,,,80,,,84,85,87,86,89,90,,82', '83,96,91,101,305,104,81,99,100,,92,94,93,95,,58,61,,,59,,,,,,,,88,,103', ',,,98,97,,,84,85,87,86,89,90,,82,83,80,,246,,,81,,,135,,,132,96,91,101', ',104,,99,100,,92,94,93,95,,88,,,,,67,,,,,,,,,103,136,62,,98,97,,,84', '85,87,86,89,90,,82,83,80,,245,,,81,,,,,,,96,91,101,,104,,99,100,,92', '94,93,95,,88,,,,,,,,,,,,,,103,,,,98,97,,,84,85,87,86,89,90,,82,83,80', ',244,,,81,,,,,,,96,91,101,,104,80,99,100,,92,94,93,95,,88,,,,,101,,104', ',99,100,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82,83,103,,96,91,101', '81,104,,99,100,,92,94,93,95,82,83,,,,,,81,,,,88,,,,103,,,,98,97,,80', '84,85,87,86,89,90,,82,83,,,96,91,101,81,104,,99,100,,92,94,93,95,,,', ',,,,,,,,88,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82,83,,,96,91,101', '81,104,,99,100,,92,94,93,95,,269,270,,,,,,,,,88,,,,103,,58,61,98,97', '59,80,84,85,87,86,89,90,,82,83,,,96,91,101,81,104,,99,100,,92,94,93', '95,,,,,,,,,,135,,88,132,,,103,,,,98,97,,80,84,85,87,86,89,90,,82,83', '67,,,,101,81,104,,99,100,136,62,,,,80,,,,,281,,,,,,88,96,91,101,103', '104,,99,100,,92,94,93,95,87,86,,,,82,83,,,,,,81,,,103,,,,98,97,,80,84', '85,87,86,89,90,,82,83,,88,96,91,101,81,104,,99,100,,92,94,93,95,,,,', ',,,,,,,88,,,,103,,,,,97,,80,84,85,87,86,89,90,,82,83,,,96,91,101,81', '104,,99,100,,92,94,93,95,80,,,,,,,,,,,88,,,101,103,104,,99,100,80,,', '84,85,87,86,89,90,,82,83,96,91,101,273,104,81,99,100,103,92,94,93,95', ',,,,,,,,,,82,83,,88,,103,,81,,98,97,,80,84,85,87,86,89,90,,82,83,,,96', '91,101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98,97,,80', '84,85,87,86,89,90,,82,83,,,,,101,81,104,,99,100,,,,,,80,,,,,,,,,,,88', ',91,101,103,104,,99,100,80,92,,84,85,87,86,,,,82,83,,91,101,,104,81', '99,100,103,92,,,,80,,,84,85,87,86,89,90,,82,83,,88,101,103,104,81,99', '100,,,,84,85,87,86,89,90,80,82,83,,,,,,81,88,,103,96,91,101,,104,,99', '100,,92,94,93,95,,82,83,,88,,,,81,,,,,,,103,,,,98,97,,80,84,85,87,86', '89,90,,82,83,,,96,91,101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,', ',,103,,,,98,97,,80,84,85,87,86,89,90,,82,83,,,96,91,101,81,104,,99,100', ',92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82', '83,,,96,91,101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98', '97,,80,84,85,87,86,89,90,,82,83,,,,91,101,81,104,,99,100,,92,,80,,,', ',,,,,,,,,88,101,,104,103,99,100,,,,,,84,85,87,86,89,90,,82,83,,,,,,81', '103,,,,,80,,,84,85,87,86,89,90,,82,83,,,101,88,104,81,99,100,,,,80,', ',,,,,,,,,,,,101,88,104,103,99,100,,,,,,84,85,87,86,89,90,,82,83,,,,', ',81,103,,,,,80,,,,,87,86,,,,82,83,,,101,88,104,81,99,100,,,,80,,,,,', ',,,,,,96,91,101,88,104,103,99,100,,92,94,93,95,84,85,87,86,,,,82,83', ',,,,,81,103,,,,98,97,,,84,85,87,86,89,90,,82,83,,,,88,,81,,,293,185', '292,186,,290,188,294,,287,,289,291,,,,,,88,189,184,295,,,,288,,,,,,', ',,,,,187,296,,,,,,,,,,,,299,300,298,297,293,185,292,186,,290,188,294', ',287,,289,291,,,,,,,189,184,295,,,,288,,,,,,,,,,,,187,296,,,,,,,,,,', ',299,300,298,297,293,185,292,186,,290,188,294,,287,,289,291,,,,,,,189', '184,295,,,,288,,,,,,,,,,,,187,296,,,,,,,,,,,,299,300,298,297,293,185', '292,186,,290,188,294,,287,,289,291,,,,,,,189,184,295,,,,288,,,,,,,,', ',,,187,296,,,,,,,,,,,,299,300,298,297' ] racc_action_table = arr = ::Array.new(6785, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end clist = [ '0,0,242,182,0,0,68,0,240,193,183,168,252,325,170,45,242,176,368,253', '368,181,325,193,0,193,367,193,193,240,0,367,0,250,0,0,180,0,0,0,214', '0,0,0,0,182,45,0,0,193,45,0,183,168,0,0,170,219,45,176,405,405,0,181', '405,405,250,405,0,178,166,193,0,0,115,0,0,0,180,266,0,0,0,0,405,271', '0,169,12,272,405,12,405,142,405,405,142,405,405,405,178,405,405,164', '178,115,274,405,405,115,163,405,178,191,405,405,162,115,191,12,4,4,405', '12,4,4,314,4,405,169,161,12,405,405,177,405,405,342,342,314,405,405', '405,405,4,156,405,314,248,322,4,322,4,247,4,4,282,4,4,4,4,4,4,4,4,177', '284,4,4,177,286,4,150,150,4,4,150,177,147,147,400,400,4,288,400,400', '303,400,4,7,7,7,4,4,308,4,4,145,221,306,4,4,4,4,400,140,4,310,311,313', '400,222,400,138,400,400,130,400,400,400,128,400,400,400,400,308,321', '400,400,308,225,400,308,323,400,400,324,308,7,7,7,7,400,241,127,120', '121,329,400,118,341,239,400,400,343,400,400,152,108,121,400,400,400', '400,245,245,400,121,245,245,152,245,245,106,105,102,101,351,152,70,353', '121,354,357,215,362,63,238,245,179,179,227,152,179,245,232,245,62,245', '245,227,245,245,245,371,245,245,245,245,372,374,245,245,227,375,245', '378,41,245,245,385,227,386,392,40,8,245,5,179,402,1,179,245,407,409', '411,245,245,415,245,245,,,,245,245,245,245,399,399,245,179,399,399,', '399,399,194,,,,179,179,,,,,,,,,194,399,194,,194,194,,399,,399,,399,399', ',399,399,399,,399,399,399,399,,,399,399,194,,399,,,399,399,,,,,246,246', '399,,246,246,,246,399,,,194,399,399,,399,399,,,,399,399,399,399,246', ',399,,,,246,,246,,246,246,,246,246,246,,246,246,,,,,246,246,,,246,,', '246,246,,,,,10,10,246,,10,10,,10,246,,,,246,246,,246,246,,,,246,246', '246,246,10,,246,,,,10,,10,,10,10,,10,10,10,,10,10,10,10,,,10,10,,,10', ',,10,10,,,,,11,11,10,,11,11,,11,10,,,,10,10,,10,10,,,,10,10,10,10,11', ',10,,,,11,,11,,11,11,,11,11,11,,11,11,11,11,,,11,11,,,11,,,11,11,,,', ',251,251,11,,251,251,,251,11,,,,11,11,,11,11,,,,11,11,11,11,251,,11', ',,,251,,251,,251,251,,251,251,251,,251,251,251,251,,,251,251,,,251,', ',251,251,,,,,15,15,251,,15,15,,15,251,,,,251,251,,251,251,,,,251,251', '251,251,15,,251,,,,15,,15,,15,15,,15,15,15,,15,15,,,,,15,15,,,15,,,15', '15,,,,,16,16,15,,16,16,,16,15,,,,15,15,,15,15,,,,15,15,15,15,16,,15', ',,,16,,16,,16,16,,16,16,16,,16,16,,,,,16,16,,,16,,,16,16,,,,,17,17,16', ',17,17,,17,16,,,,16,16,,16,16,,,,16,16,16,16,17,,16,,,,17,,17,,17,17', ',17,17,17,,17,17,,,,,17,17,,,17,,,17,17,,,,,18,18,17,,18,18,,18,17,', ',,17,17,,17,17,,,,17,17,17,17,18,,17,,,,18,,18,,18,18,,18,18,18,,18', '18,18,18,,,18,18,,,18,,,18,18,,,,,,,18,,,,,,18,,,,18,18,,18,18,,,,18', '18,18,18,397,397,18,,397,397,,397,397,,,,,,,,,,,,,,,,397,46,46,,,46', '397,,397,,397,397,,397,397,397,,397,397,397,397,,,397,397,,,397,,,397', '397,,,,,,,397,,46,,,46,397,,,,397,397,,397,397,,,,397,397,397,397,381', '381,397,46,381,381,,381,381,117,,,,46,46,,,,,,,,,117,381,117,,117,117', ',381,,381,,381,381,,381,381,381,,381,381,381,381,,,381,381,117,,381', ',,381,381,,,,,42,42,381,,42,42,,42,381,,,,381,381,,381,381,,,,381,381', '381,381,42,,381,,,,42,,42,,42,42,,42,42,42,,42,42,,,,,42,42,,,42,,,42', '42,,,,,43,43,42,,43,43,,43,42,,,,42,42,,42,42,,,,42,42,42,42,43,,42', ',,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43,,,43,,,43,43,,,,,44,44,43', ',44,44,,44,43,,,,43,43,,43,43,,,,43,43,43,43,44,,43,,,,44,,44,,44,44', ',44,44,44,,44,44,,,,,44,44,,,44,,,44,44,,,,,,,44,,,,,,44,,,,44,44,,44', '44,,,,44,44,44,44,255,255,44,,255,255,,255,255,192,,,,,,,,,,,,,,192', '255,192,,192,192,,255,,255,,255,255,,255,255,255,,255,255,255,255,,', '255,255,192,,255,,,255,255,,,,,213,213,255,,213,213,,213,255,,,,255', '255,,255,255,,,,255,255,255,255,213,,255,,,,213,,213,,213,213,,213,213', '213,,213,213,,,,,213,213,,,213,,,213,213,,,,,52,52,213,,52,52,52,52', '213,,,,213,213,,213,213,,,,213,213,213,213,52,,213,,,,52,,52,,52,52', ',52,52,52,,52,52,52,52,,,52,52,,,52,,,52,52,,,,,53,53,52,,53,53,53,53', '52,,,,52,52,,52,52,,,,52,52,52,52,53,,52,,,,53,,53,,53,53,,53,53,53', ',53,53,53,53,,,53,53,,,53,,,53,53,,,,,,,53,,,,,,53,,,,53,53,,53,53,', ',,53,53,53,53,54,54,53,,54,54,,54,54,114,,,,,,,,,,,,,,114,54,114,,114', '114,,54,,54,,54,54,,54,54,54,,54,54,54,54,,,54,54,114,,54,,,54,54,,', ',,60,60,54,,60,60,,60,54,,,,54,54,,54,54,,,,54,54,54,54,60,,54,,,,60', ',60,,60,60,,60,60,60,,60,60,60,60,,,60,60,,,60,,,60,60,,,,,370,370,60', ',370,370,,370,60,,,,60,60,,60,60,,,,60,60,60,60,370,,60,,,,370,,370', ',370,370,,370,370,370,,370,370,,,,,370,370,,,370,,,370,370,,,,,256,256', '370,,256,256,,256,370,,,,370,370,,370,370,,,,370,370,370,370,256,,370', ',,,256,,256,,256,256,,256,256,256,,256,256,256,256,,,256,256,,,256,', ',256,256,,,,,65,65,256,,65,65,,65,256,,,,256,256,,256,256,,,,256,256', '256,256,65,,256,,,,65,,65,,65,65,,65,65,65,,65,65,,,,,65,65,,,65,,,65', '65,,,,,358,358,65,,358,358,,358,65,,,,65,65,,65,65,,,,65,65,65,65,358', ',65,,,,358,,358,,358,358,,358,358,358,,358,358,358,358,,,358,358,,,358', ',,358,358,,,,,69,69,358,,69,69,,69,358,,,,358,358,,358,358,,,,358,358', '358,358,69,,358,,,,69,,69,,69,69,,69,69,69,,69,69,69,69,,,69,69,,,69', ',,69,69,,,,,352,352,69,,352,352,,352,69,,,,69,69,,69,69,,,,69,69,69', '69,352,,69,,,,352,,352,,352,352,,352,352,352,,352,352,,,,,352,352,,', '352,,,352,352,,,,,71,71,352,,71,71,,71,352,,,,352,352,,352,352,,,,352', '352,352,352,71,,352,,,,71,,71,,71,71,,71,71,71,,71,71,71,71,,,71,71', ',,71,,,71,71,,,,,72,72,71,,72,72,,72,71,,,,71,71,,71,71,,,,71,71,71', '71,72,,71,,,,72,,72,,72,72,,72,72,72,,72,72,72,72,,,72,72,,,72,,,72', '72,,,,,73,73,72,,73,73,,73,72,,,,72,72,,72,72,,,,72,72,72,72,73,,72', ',,,73,,73,,73,73,,73,73,73,,73,73,73,73,,,73,73,,,73,,,73,73,,,,,74', '74,73,,74,74,,74,73,,,,73,73,,73,73,,,,73,73,73,73,74,,73,,,,74,,74', ',74,74,,74,74,74,,74,74,74,74,,,74,74,,,74,,,74,74,,,,,75,75,74,,75', '75,,75,74,,,,74,74,,74,74,,,,74,74,74,74,75,,74,,,,75,,75,,75,75,,75', '75,75,,75,75,75,75,,,75,75,,,75,,,75,75,,,,,76,76,75,,76,76,,76,75,', ',,75,75,,75,75,,,,75,75,75,75,76,,75,,,,76,,76,,76,76,,76,76,76,,76', '76,76,76,,,76,76,,,76,,,76,76,,,,,77,77,76,,77,77,,77,76,,,,76,76,,76', '76,,,,76,76,76,76,77,,76,,,,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77', '77,,,77,,,77,77,,,,,78,78,77,,78,78,,78,77,,,,77,77,,77,77,,,,77,77', '77,77,78,,77,,,,78,,78,,78,78,,78,78,78,,78,78,78,78,,,78,78,,,78,,', '78,78,,,,,79,79,78,,79,79,,79,78,,,,78,78,,78,78,,,,78,78,78,78,79,', '78,,,,79,79,79,79,79,79,79,79,79,79,,79,79,,,,,79,79,79,79,79,,,79,79', ',,,,,,79,,,,,79,79,,,,79,79,,79,79,,,,79,79,79,79,80,80,79,116,80,80', ',80,,,,,,,,,,116,,116,,116,116,,80,,,,,,80,,80,,80,80,,80,80,80,,80', '80,116,,,,80,80,,,80,,,80,80,,,,,81,81,80,,81,81,,81,80,,,,80,80,,80', '80,,,,80,80,80,80,81,,80,,,,81,,81,,81,81,,81,81,81,,81,81,,,,,81,81', ',,81,,,81,81,,,,,82,82,81,,82,82,,82,81,,,,81,81,,81,81,,,,81,81,81', '81,82,,81,,,,82,,82,,82,82,,82,82,82,,82,82,,,,,82,82,,,82,,,82,82,', ',,,83,83,82,,83,83,,83,82,,,,82,82,,82,82,,,,82,82,82,82,83,,82,,,,83', ',83,,83,83,,83,83,83,,83,83,,,,,83,83,,,83,,,83,83,,,,,84,84,83,,84', '84,,84,83,,,,83,83,,83,83,,,,83,83,83,83,84,,83,,,,84,,84,,84,84,,84', '84,84,,84,84,,,,,84,84,,,84,,,84,84,,,,,85,85,84,,85,85,,85,84,,,,84', '84,,84,84,,,,84,84,84,84,85,,84,,,,85,,85,,85,85,,85,85,85,,85,85,,', ',,85,85,,,85,,,85,85,,,,,86,86,85,,86,86,,86,85,,,,85,85,,85,85,,,,85', '85,85,85,86,,85,,,,86,,86,,86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86', '86,,,,,87,87,86,,87,87,,87,86,,,,86,86,,86,86,,,,86,86,86,86,87,,86', ',,,87,,87,,87,87,,87,87,87,,87,87,,,,,87,87,,,87,,,87,87,,,,,88,88,87', ',88,88,,88,87,,,,87,87,,87,87,,,,87,87,87,87,88,,87,,,,88,,88,,88,88', ',88,88,88,,88,88,,,,,88,88,,,88,,,88,88,,,,,89,89,88,,89,89,,89,88,', ',,88,88,,88,88,,,,88,88,88,88,89,,88,,,,89,,89,,89,89,,89,89,89,,89', '89,,,,,89,89,,,89,,,89,89,,,,,90,90,89,,90,90,,90,89,,,,89,89,,89,89', ',,,89,89,89,89,90,,89,,,,90,,90,,90,90,,90,90,90,,90,90,,,,,90,90,,', '90,,,90,90,,,,,91,91,90,,91,91,,91,90,,,,90,90,,90,90,,,,90,90,90,90', '91,,90,,,,91,,91,,91,91,,91,91,91,,91,91,,,,,91,91,,,91,,,91,91,,,,', '92,92,91,,92,92,,92,91,,,,91,91,,91,91,,,,91,91,91,91,92,,91,,,,92,', '92,,92,92,,92,92,92,,92,92,,,,,92,92,,,92,,,92,92,,,,,93,93,92,,93,93', ',93,92,,,,92,92,,92,92,,,,92,92,92,92,93,,92,,,,93,,93,,93,93,,93,93', '93,,93,93,,,,,93,93,,,93,,,93,93,,,,,94,94,93,,94,94,,94,93,,,,93,93', ',93,93,,,,93,93,93,93,94,,93,,,,94,,94,,94,94,,94,94,94,,94,94,,,,,94', '94,,,94,,,94,94,,,,,95,95,94,,95,95,,95,94,,,,94,94,,94,94,,,,94,94', '94,94,95,,94,,,,95,,95,,95,95,,95,95,95,,95,95,,,,,95,95,,,95,,,95,95', ',,,,96,96,95,,96,96,,96,95,,,,95,95,,95,95,,,,95,95,95,95,96,,95,,,', '96,,96,,96,96,,96,96,96,,96,96,,,,,96,96,,,96,,,96,96,,,,,97,97,96,', '97,97,,97,96,,,,96,96,,96,96,,,,96,96,96,96,97,,96,,,,97,,97,,97,97', ',97,97,97,,97,97,,,,,97,97,,,97,,,97,97,,,,,98,98,97,,98,98,,98,97,', ',,97,97,,97,97,,,,97,97,97,97,98,,97,,,,98,,98,,98,98,,98,98,98,,98', '98,,,,,98,98,,,98,,,98,98,,,,,99,99,98,,99,99,,99,98,,,,98,98,,98,98', ',,,98,98,98,98,99,,98,,,,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,', '99,,,99,99,,,,,100,100,99,,100,100,,100,99,,,99,99,99,,99,99,,,,99,99', '99,99,100,100,99,,,,100,,100,,100,100,,100,100,100,,100,100,100,100', ',,100,100,,,100,,,100,100,,,,,173,173,100,,173,173,,173,100,,,,100,100', ',100,100,,,,100,100,100,100,173,,100,,,,173,,173,,173,173,,173,173,173', ',173,173,,,,,173,173,,,173,,,173,173,,,,,172,172,173,,172,172,,172,173', ',,,173,173,,173,173,,,,173,173,173,173,172,,173,,,,172,,172,,172,172', ',172,172,172,,172,172,,,,,172,172,,,172,,,172,172,,,,,103,103,172,,103', '103,,103,172,,,,172,172,,172,172,,,,172,172,172,172,103,,172,,,,103', ',103,,103,103,,103,103,103,,103,103,,,,,103,103,,,103,,,103,103,,,,', '104,104,103,,104,104,,104,103,,,,103,103,,103,103,,,,103,103,103,103', '104,,103,,,,104,,104,,104,104,,104,104,104,,104,104,,,,,104,104,,,104', ',,104,104,,,,,171,171,104,,171,171,,171,104,,,,104,104,,104,104,,,,104', '104,104,104,171,,104,,,,171,,171,,171,171,,171,171,171,,171,171,,,,', '171,171,,,171,,,171,171,,,,,167,167,171,,167,167,,167,171,,167,,171', '171,,171,171,,,,171,171,171,171,167,,171,,,,167,,167,,167,167,,167,167', '167,,167,167,,,,,167,167,,,167,,,167,167,,,,,107,107,167,,107,107,,107', '167,,,,167,167,,167,167,,,,167,167,167,167,107,,167,,,,107,,107,,107', '107,,107,107,107,,107,107,,,,,107,107,,,107,,,107,107,,,,,261,261,107', ',261,261,,261,107,,,,107,107,,107,107,,,,107,107,107,107,261,,107,,', ',261,,261,,261,261,,261,261,261,,261,261,261,261,,,261,261,,,261,,,261', '261,,,,,276,276,261,,276,276,,276,261,,,,261,261,,261,261,,,,261,261', '261,261,276,,261,,,,276,,276,,276,276,,276,276,276,,276,276,,,,,276', '276,,,276,,,276,276,,,,,277,277,276,,277,277,,277,276,,,,276,276,,276', '276,,,,276,276,276,276,277,,276,,,,277,,277,,277,277,,277,277,277,,277', '277,,,,,277,277,,,277,,,277,277,,,,,278,278,277,,278,278,,278,277,,', ',277,277,,277,277,,,,277,277,277,277,278,,277,,,,278,,278,,278,278,', '278,278,278,,278,278,,,,,278,278,,,278,,,278,278,,,,,,,278,,,,,,278', ',,,278,278,,278,278,,,,278,278,278,278,154,154,278,,154,154,,154,154', ',,,,,,,,,,,,,,,154,,,,,,154,,154,,154,154,,154,154,154,,154,154,154', '154,,,154,154,,,154,,,154,154,,,,,304,304,154,,304,304,,304,154,,,,154', '154,,154,154,,,,154,154,154,154,304,,154,,,,304,,304,,304,304,,304,304', '304,,304,304,,,,,304,304,,,304,,,304,304,,,,,328,328,304,,328,328,,328', '304,,,,304,304,,304,304,,,,304,304,304,304,328,,304,,,,328,,328,,328', '328,,328,328,328,,328,328,328,328,,,328,328,,,328,,,328,328,,,,,,,328', ',,,,,328,,,,328,328,,328,328,,,,328,328,328,328,230,230,328,,230,230', ',230,230,,,,,,,,,,,,,,,,230,,,,,,230,,230,,230,230,,230,230,230,,230', '230,230,230,,,230,230,,,230,,,230,230,,,,,122,122,230,,122,122,,122', '230,,,,230,230,,230,230,,,,230,230,230,230,122,122,230,,,,122,,122,', '122,122,,122,122,122,,122,122,122,122,,,122,122,,,122,,,122,122,,,,', '151,151,122,,151,151,,151,122,,,,122,122,,122,122,,,,122,122,122,122', '151,,122,,,,151,,151,,151,151,,151,151,151,,151,151,151,151,,,151,151', ',,151,,,151,151,,,,,280,280,151,,280,280,,280,151,,,,151,151,,151,151', ',,,151,151,151,151,280,,151,,,,280,,280,,280,280,,280,280,280,,280,280', ',,,,280,280,,,280,,,280,280,,,,,281,281,280,,281,281,,281,280,,,,280', '280,,280,280,,,,280,280,280,280,281,,280,,,,281,,281,,281,281,,281,281', '281,,281,281,,,,,281,281,,,281,,,281,281,,,,,,,281,,,,,,281,,,,281,281', ',281,281,,,,281,281,281,281,326,326,281,,326,326,,326,326,,,,,,,,,,', ',,,,,326,,,,,,326,,326,,326,326,,326,326,326,,326,326,,,,,326,326,,', '326,,,326,326,,,,,319,319,326,,319,319,,319,326,,,,326,326,,326,326', ',,,326,326,326,326,319,,326,,,,319,,319,,319,319,,319,319,319,,319,319', '319,319,,,319,319,,,319,,,319,319,,,,,318,318,319,,318,318,,318,319', ',,,319,319,,319,319,,,,319,319,319,319,318,,319,,,,318,,318,,318,318', ',318,318,318,,318,318,,,,,318,318,,,318,,,318,318,,,,,315,315,318,,315', '315,,315,318,,,,318,318,,318,318,,,,318,318,318,318,315,,318,,,,315', ',315,,315,315,,315,315,315,,315,315,,,,,315,315,,,315,,,315,315,,,,', ',,315,,,,,,315,,,,315,315,,315,315,,,,315,315,315,315,244,244,315,,244', '244,,244,244,,,,,,,,,,,,,,,,244,,,,,,244,,244,,244,244,,244,244,244', ',244,244,244,244,205,,244,244,,,244,,,244,244,,,205,205,,205,244,205', '205,,205,,244,,,,244,244,,244,244,,,,244,244,244,244,,205,244,,,,223', ',,205,205,205,205,205,205,,205,205,223,223,223,223,223,205,223,223,', '223,223,223,223,,249,249,,,249,,,,,,,,205,,223,,,,223,223,,,223,223', '223,223,223,223,,223,223,126,,126,,,223,,,249,,,249,126,126,126,,126', ',126,126,,126,126,126,126,,223,,,,,249,,,,,,,,,126,249,249,,126,126', ',,126,126,126,126,126,126,,126,126,125,,125,,,126,,,,,,,125,125,125', ',125,,125,125,,125,125,125,125,,126,,,,,,,,,,,,,,125,,,,125,125,,,125', '125,125,125,125,125,,125,125,123,,123,,,125,,,,,,,123,123,123,,123,199', '123,123,,123,123,123,123,,125,,,,,199,,199,,199,199,,,,123,,,,123,123', ',9,123,123,123,123,123,123,,123,123,199,,9,9,9,123,9,,9,9,,9,9,9,9,199', '199,,,,,,199,,,,123,,,,9,,,,9,9,,153,9,9,9,9,9,9,,9,9,,,153,153,153', '9,153,,153,153,,153,153,153,153,,,,,,,,,,,,9,,,,153,,,,153,153,,190', '153,153,153,153,153,153,,153,153,,,190,190,190,153,190,,190,190,,190', '190,190,190,,153,153,,,,,,,,,153,,,,190,,331,331,190,190,331,218,190', '190,190,190,190,190,,190,190,,,218,218,218,190,218,,218,218,,218,218', '218,218,,,,,,,,,,331,,190,331,,,218,,,,218,218,,195,218,218,218,218', '218,218,,218,218,331,,,,195,218,195,,195,195,331,331,,,,210,,,,,210', ',,,,,218,210,210,210,195,210,,210,210,,210,210,210,210,195,195,,,,195', '195,,,,,,195,,,210,,,,210,210,,209,210,210,210,210,210,210,,210,210', ',195,209,209,209,210,209,,209,209,,209,209,209,209,,,,,,,,,,,,210,,', ',209,,,,,209,,208,209,209,209,209,209,209,,209,209,,,208,208,208,209', '208,,208,208,,208,208,208,208,198,,,,,,,,,,,209,,,198,208,198,,198,198', '165,,,208,208,208,208,208,208,,208,208,165,165,165,165,165,208,165,165', '198,165,165,165,165,,,,,,,,,,,198,198,,208,,165,,198,,165,165,,346,165', '165,165,165,165,165,,165,165,,,346,346,346,165,346,,346,346,,346,346', '346,346,,,,,,,,,,,,165,,,,346,,,,346,346,,200,346,346,346,346,346,346', ',346,346,,,,,200,346,200,,200,200,,,,,,207,,,,,,,,,,,346,,207,207,200', '207,,207,207,206,207,,200,200,200,200,,,,200,200,,206,206,,206,200,206', '206,207,206,,,,197,,,207,207,207,207,207,207,,207,207,,200,197,206,197', '207,197,197,,,,206,206,206,206,206,206,366,206,206,,,,,,206,207,,197', '366,366,366,,366,,366,366,,366,366,366,366,,197,197,,206,,,,197,,,,', ',,366,,,,366,366,,348,366,366,366,366,366,366,,366,366,,,348,348,348', '366,348,,348,348,,348,348,348,348,,,,,,,,,,,,366,,,,348,,,,348,348,', '349,348,348,348,348,348,348,,348,348,,,349,349,349,348,349,,349,349', ',349,349,349,349,,,,,,,,,,,,348,,,,349,,,,349,349,,350,349,349,349,349', '349,349,,349,349,,,350,350,350,349,350,,350,350,,350,350,350,350,,,', ',,,,,,,,349,,,,350,,,,350,350,,204,350,350,350,350,350,350,,350,350', ',,,204,204,350,204,,204,204,,204,,203,,,,,,,,,,,,,350,203,,203,204,203', '203,,,,,,204,204,204,204,204,204,,204,204,,,,,,204,203,,,,,202,,,203', '203,203,203,203,203,,203,203,,,202,204,202,203,202,202,,,,196,,,,,,', ',,,,,,,196,203,196,202,196,196,,,,,,202,202,202,202,202,202,,202,202', ',,,,,202,196,,,,,201,,,,,196,196,,,,196,196,,,201,202,201,196,201,201', ',,,347,,,,,,,,,,,,347,347,347,196,347,201,347,347,,347,347,347,347,201', '201,201,201,,,,201,201,,,,,,201,347,,,,347,347,,,347,347,347,347,347', '347,,347,347,,,,201,,347,,,273,273,273,273,,273,273,273,,273,,273,273', ',,,,,347,273,273,273,,,,273,,,,,,,,,,,,273,273,,,,,,,,,,,,273,273,273', '273,275,275,275,275,,275,275,275,,275,,275,275,,,,,,,275,275,275,,,', '275,,,,,,,,,,,,275,275,,,,,,,,,,,,275,275,275,275,305,305,305,305,,305', '305,305,,305,,305,305,,,,,,,305,305,305,,,,305,,,,,,,,,,,,305,305,,', ',,,,,,,,,305,305,305,305,217,217,217,217,,217,217,217,,217,,217,217', ',,,,,,217,217,217,,,,217,,,,,,,,,,,,217,217,,,,,,,,,,,,217,217,217,217' ] racc_action_check = arr = ::Array.new(6785, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end racc_action_pointer = [ -2, 330, nil, nil, 118, 314, nil, 173, 316, 5605, 466, 526, 82, nil, nil, 646, 706, 766, 826, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 253, 291, 1054, 1114, 1174, 9, 935, nil, nil, nil, nil, nil, 1378, 1438, 1522, nil, nil, nil, nil, nil, 1582, nil, 221, 215, nil, 1762, nil, nil, 6, 1882, 266, 2002, 2062, 2122, 2182, 2242, 2302, 2362, 2422, 2482, 2566, 2626, 2686, 2746, 2806, 2866, 2926, 2986, 3046, 3106, 3166, 3226, 3286, 3346, 3406, 3466, 3526, 3586, 3646, 3706, 3766, 235, 266, 3946, 4006, 265, 264, 4186, 232, nil, nil, nil, nil, nil, 1526, 68, 2564, 998, 222, nil, 236, 210, 4774, 5558, nil, 5501, 5444, 218, 207, nil, 195, nil, nil, nil, nil, nil, nil, nil, 200, nil, 192, nil, 83, nil, nil, 185, nil, 174, nil, nil, 170, 4834, 221, 5652, 4510, nil, 132, nil, nil, nil, nil, 121, 107, 101, 94, 5957, 57, 4126, -1, 75, 2, 4066, 3886, 3826, nil, nil, 5, 128, 63, 287, 24, 9, -9, -2, nil, nil, nil, nil, nil, nil, 5699, 105, 1262, 2, 350, 5793, 6429, 6121, 5937, 5575, 6051, 6474, 6406, 6361, 6338, 5342, 6096, 6076, 5912, 5865, 5818, nil, nil, 1318, 27, 213, nil, 6699, 5746, 9, nil, 174, 169, 5387, nil, 192, nil, 264, nil, nil, 4714, nil, 282, nil, nil, nil, nil, nil, 271, 215, -28, 236, -11, nil, 5302, 262, 406, 144, 110, 5418, -8, 586, 4, 11, nil, 1258, 1702, nil, nil, nil, nil, 4246, nil, nil, nil, nil, 66, nil, nil, nil, nil, 73, 79, 6537, 96, 6591, 4306, 4366, 4426, nil, 4894, 4954, 143, nil, 139, nil, 157, nil, 171, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 176, 4570, 6645, 190, nil, 188, nil, 194, 198, nil, 138, 90, 5218, nil, nil, 5158, 5098, nil, 216, 116, 223, 203, 0, 5038, nil, 4630, 238, nil, 5746, nil, nil, nil, nil, nil, nil, nil, nil, nil, 179, 57, 241, nil, nil, 6004, 6497, 6197, 6244, 6291, 267, 1942, 209, 272, nil, nil, 270, 1822, nil, nil, nil, 258, nil, nil, nil, 6150, 18, -15, nil, 1642, 295, 276, nil, 301, 305, nil, nil, 306, nil, nil, 994, nil, nil, nil, 310, 280, nil, nil, nil, nil, nil, 313, nil, nil, nil, nil, 910, nil, 346, 178, nil, 319, nil, nil, 58, nil, 323, nil, 324, nil, 325, nil, nil, nil, 296, nil, nil, nil, nil ] racc_action_default = [ -3, -243, -1, -2, -4, -5, -8, -10, -16, -21, -243, -243, -243, -33, -34, -243, -243, -243, -243, -61, -62, -63, -64, -65, -66, -67, -68, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80, -81, -86, -90, -243, -243, -243, -243, -243, -176, -177, -178, -179, -180, -243, -243, -243, -191, -192, -193, -194, -195, -243, -197, -243, -210, -213, -243, -218, -219, -243, -243, -7, -243, -243, -243, -243, -243, -243, -243, -243, -126, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -243, -121, -242, -242, -22, -23, -243, -242, -136, -157, -158, -159, -160, -46, -243, -47, -54, -243, -87, -243, -243, -243, -243, -97, -243, -243, -242, -220, -145, -147, -148, -149, -150, -151, -153, -154, -14, -220, -182, -220, -184, -243, -187, -188, -243, -196, -243, -201, -204, -243, -208, -243, -243, -243, 420, -6, -9, -11, -12, -13, -17, -18, -19, -20, -243, -220, -243, -79, -80, -81, -231, -237, -225, -127, -130, -243, -228, -226, -234, -177, -178, -179, -180, -224, -229, -230, -232, -233, -235, -59, -243, -36, -37, -38, -39, -40, -41, -42, -43, -44, -45, -48, -49, -50, -51, -52, -53, -55, -56, -243, -57, -115, -243, -220, -83, -91, -126, -125, -243, -124, -243, -222, -243, -28, -242, -161, -243, -58, -92, -243, -95, -220, -164, -166, -167, -168, -169, -171, -243, -243, -174, -243, -89, -243, -243, -243, -243, -242, -221, -243, -221, -243, -243, -185, -243, -243, -198, -199, -200, -202, -243, -205, -206, -207, -209, -220, -211, -214, -216, -217, -8, -243, -126, -243, -221, -243, -243, -243, -35, -243, -243, -220, -117, -243, -85, -220, -129, -243, -225, -226, -227, -228, -231, -234, -236, -237, -238, -239, -240, -241, -122, -123, -243, -223, -126, -243, -139, -243, -162, -220, -243, -94, -243, -221, -243, -172, -173, -243, -243, -88, -243, -100, -243, -106, -243, -243, -110, -242, -243, -155, -243, -146, -152, -15, -181, -183, -186, -189, -190, -203, -243, -243, -220, -26, -128, -133, -131, -132, -60, -119, -243, -221, -82, -243, -25, -29, -220, -242, -140, -141, -142, -243, -93, -96, -165, -170, -243, -100, -99, -243, -243, -106, -105, -243, -243, -109, -111, -243, -137, -138, -243, -156, -212, -215, -243, -30, -116, -118, -84, -120, -27, -243, -163, -175, -98, -101, -243, -104, -243, -242, -134, -243, -144, -24, -31, -135, -243, -103, -243, -108, -243, -113, -114, -143, -222, -102, -107, -112, -32 ] racc_goto_table = [ 2, 114, 116, 117, 119, 118, 226, 222, 127, 131, 133, 191, 212, 146, 166, 268, 303, 332, 241, 232, 327, 70, 378, 411, 248, 226, 225, 326, 123, 125, 126, 219, 221, 282, 252, 396, 253, 105, 106, 137, 137, 145, 369, 138, 140, 247, 373, 148, 345, 241, 266, 153, 392, 229, 211, 356, 306, 359, 157, 158, 159, 160, 274, 329, 385, 165, 190, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 156, 137, 395, 218, 218, 214, 333, 223, 398, 365, 317, 316, 338, 382, 377, 262, 161, 162, 163, 164, 263, 3, 260, 137, 284, 261, 259, 242, 149, 151, 264, 1, nil, nil, nil, nil, nil, 307, 241, 310, 283, nil, 313, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 127, 271, 131, 133, nil, nil, 330, nil, nil, nil, nil, 265, 286, 116, 272, nil, nil, 123, 125, 126, nil, nil, 341, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 351, 285, nil, nil, 354, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 210, nil, nil, nil, nil, 384, nil, 362, nil, 343, 419, nil, 241, 131, 133, 340, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 380, nil, nil, nil, 311, nil, 190, nil, nil, nil, nil, nil, 334, 386, 357, nil, 145, 339, 321, 323, nil, nil, 148, 367, nil, nil, nil, 391, nil, nil, 380, nil, nil, nil, nil, nil, 346, 347, 348, 388, 349, 350, nil, nil, nil, 360, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 223, nil, nil, nil, 131, 133, nil, nil, 412, nil, nil, 366, nil, nil, 190, 415, 334, nil, nil, nil, nil, nil, 190, nil, nil, nil, nil, 389, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 210, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 123, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 402, nil, nil, nil, nil, nil, nil, nil, nil, nil, 223, nil, nil, nil, nil, nil, 407, nil, 409, 413 ] racc_goto_check = [ 2, 10, 10, 10, 37, 6, 49, 13, 57, 35, 34, 19, 50, 80, 11, 88, 14, 65, 36, 42, 47, 5, 59, 48, 15, 49, 58, 46, 10, 10, 10, 53, 53, 51, 15, 43, 15, 9, 9, 6, 6, 6, 44, 8, 8, 58, 45, 6, 54, 36, 42, 10, 59, 41, 20, 16, 61, 62, 6, 6, 6, 6, 15, 64, 12, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 5, 6, 44, 10, 10, 8, 67, 10, 45, 68, 69, 71, 75, 65, 47, 76, 9, 9, 9, 9, 77, 3, 81, 6, 15, 82, 84, 8, 85, 86, 87, 1, nil, nil, nil, nil, nil, 49, 36, 42, 50, nil, 15, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 57, 6, 35, 34, nil, nil, 49, nil, nil, nil, nil, 2, 11, 10, 2, nil, nil, 10, 10, 10, nil, nil, 15, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 15, 37, nil, nil, 15, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, 88, nil, 15, nil, 11, 14, nil, 36, 35, 34, 80, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 49, nil, nil, nil, 2, nil, 10, nil, nil, nil, nil, nil, 6, 15, 11, nil, 6, 6, 2, 2, nil, nil, 6, 19, nil, nil, nil, 15, nil, nil, 49, nil, nil, nil, nil, nil, 10, 10, 10, 50, 10, 10, nil, nil, nil, 57, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, 35, 34, nil, nil, 49, nil, nil, 10, nil, nil, 10, 13, 6, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, 37, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, 2, nil, 2, 2 ] racc_goto_pointer = [ nil, 117, 0, 107, nil, 17, -13, nil, -9, 27, -14, -65, -279, -100, -206, -104, -249, nil, nil, -69, -45, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -36, -37, -103, -36, nil, nil, nil, -67, -102, -335, -280, -278, -219, -226, -377, -102, -87, -180, nil, -72, -227, nil, nil, -37, -82, -306, nil, -169, -251, nil, -185, -232, nil, -158, -219, -144, nil, -142, nil, nil, nil, -157, -49, -44, nil, nil, -47, -39, -36, nil, -35, 52, 52, -35, -138 ] racc_goto_default = [ nil, nil, 379, nil, 4, 5, 6, 7, nil, 8, 9, nil, nil, nil, nil, nil, 224, 13, 14, 325, nil, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, nil, 40, 41, 120, nil, nil, 124, nil, nil, nil, nil, nil, 220, nil, nil, 102, nil, 174, 176, 175, 109, nil, nil, 108, nil, nil, 128, nil, 129, 130, 134, 233, 234, 235, 236, 237, 240, 142, 144, 55, 56, 57, 60, nil, nil, nil, 147, nil, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 92, :_reduce_1, 1, 92, :_reduce_2, 0, 92, :_reduce_3, 1, 93, :_reduce_4, 1, 95, :_reduce_5, 3, 95, :_reduce_6, 2, 95, :_reduce_7, 1, 96, :_reduce_8, 3, 96, :_reduce_9, 1, 97, :_reduce_none, 3, 97, :_reduce_11, 3, 97, :_reduce_12, 3, 97, :_reduce_13, 1, 99, :_reduce_14, 3, 99, :_reduce_15, 1, 98, :_reduce_none, 3, 98, :_reduce_17, 3, 98, :_reduce_18, 3, 98, :_reduce_19, 3, 98, :_reduce_20, 1, 100, :_reduce_none, 2, 100, :_reduce_22, 2, 100, :_reduce_23, 7, 100, :_reduce_24, 5, 100, :_reduce_25, 5, 100, :_reduce_26, 4, 107, :_reduce_27, 1, 104, :_reduce_28, 3, 104, :_reduce_29, 1, 103, :_reduce_30, 2, 103, :_reduce_31, 4, 103, :_reduce_32, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 4, 101, :_reduce_35, 3, 101, :_reduce_36, 3, 101, :_reduce_37, 3, 101, :_reduce_38, 3, 101, :_reduce_39, 3, 101, :_reduce_40, 3, 101, :_reduce_41, 3, 101, :_reduce_42, 3, 101, :_reduce_43, 3, 101, :_reduce_44, 3, 101, :_reduce_45, 2, 101, :_reduce_46, 2, 101, :_reduce_47, 3, 101, :_reduce_48, 3, 101, :_reduce_49, 3, 101, :_reduce_50, 3, 101, :_reduce_51, 3, 101, :_reduce_52, 3, 101, :_reduce_53, 2, 101, :_reduce_54, 3, 101, :_reduce_55, 3, 101, :_reduce_56, 3, 101, :_reduce_57, 3, 101, :_reduce_58, 1, 110, :_reduce_59, 3, 110, :_reduce_60, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_77, 1, 108, :_reduce_78, 1, 108, :_reduce_79, 1, 108, :_reduce_80, 1, 108, :_reduce_81, 5, 109, :_reduce_82, 3, 109, :_reduce_83, 6, 109, :_reduce_84, 4, 109, :_reduce_85, 1, 113, :_reduce_86, 2, 113, :_reduce_87, 4, 129, :_reduce_88, 3, 129, :_reduce_89, 1, 129, :_reduce_90, 3, 130, :_reduce_91, 2, 128, :_reduce_92, 3, 132, :_reduce_93, 2, 132, :_reduce_94, 2, 131, :_reduce_95, 4, 131, :_reduce_96, 2, 116, :_reduce_97, 5, 134, :_reduce_98, 4, 134, :_reduce_99, 0, 135, :_reduce_none, 2, 135, :_reduce_101, 4, 135, :_reduce_102, 3, 135, :_reduce_103, 6, 117, :_reduce_104, 5, 117, :_reduce_105, 0, 136, :_reduce_none, 4, 136, :_reduce_107, 3, 136, :_reduce_108, 5, 115, :_reduce_109, 1, 137, :_reduce_110, 2, 137, :_reduce_111, 5, 138, :_reduce_112, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 111, :_reduce_none, 4, 111, :_reduce_116, 1, 142, :_reduce_117, 3, 142, :_reduce_118, 3, 141, :_reduce_119, 6, 114, :_reduce_120, 2, 114, :_reduce_121, 3, 143, :_reduce_122, 3, 143, :_reduce_123, 1, 144, :_reduce_none, 1, 144, :_reduce_none, 0, 102, :_reduce_126, 1, 102, :_reduce_127, 3, 102, :_reduce_128, 1, 146, :_reduce_none, 1, 146, :_reduce_none, 3, 145, :_reduce_131, 3, 145, :_reduce_132, 3, 145, :_reduce_133, 6, 118, :_reduce_134, 7, 119, :_reduce_135, 1, 151, :_reduce_136, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 152, :_reduce_none, 2, 152, :_reduce_140, 1, 153, :_reduce_none, 1, 153, :_reduce_none, 7, 120, :_reduce_143, 6, 120, :_reduce_144, 1, 154, :_reduce_145, 3, 154, :_reduce_146, 1, 156, :_reduce_none, 1, 156, :_reduce_none, 1, 156, :_reduce_149, 1, 156, :_reduce_none, 1, 157, :_reduce_151, 3, 157, :_reduce_152, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 155, :_reduce_none, 2, 155, :_reduce_156, 1, 148, :_reduce_none, 1, 148, :_reduce_none, 1, 148, :_reduce_none, 1, 148, :_reduce_160, 1, 149, :_reduce_161, 2, 149, :_reduce_162, 4, 149, :_reduce_163, 1, 133, :_reduce_164, 3, 133, :_reduce_165, 1, 159, :_reduce_none, 1, 159, :_reduce_none, 1, 160, :_reduce_none, 1, 160, :_reduce_none, 3, 162, :_reduce_170, 1, 162, :_reduce_171, 2, 163, :_reduce_172, 2, 161, :_reduce_173, 1, 164, :_reduce_174, 4, 164, :_reduce_175, 1, 112, :_reduce_176, 1, 122, :_reduce_177, 1, 122, :_reduce_178, 1, 122, :_reduce_179, 1, 122, :_reduce_180, 4, 123, :_reduce_181, 2, 123, :_reduce_182, 4, 123, :_reduce_183, 2, 123, :_reduce_184, 3, 124, :_reduce_185, 4, 124, :_reduce_186, 2, 124, :_reduce_187, 1, 165, :_reduce_188, 3, 165, :_reduce_189, 3, 166, :_reduce_190, 1, 126, :_reduce_none, 1, 126, :_reduce_none, 1, 126, :_reduce_none, 1, 167, :_reduce_194, 1, 167, :_reduce_195, 2, 168, :_reduce_196, 1, 170, :_reduce_197, 1, 172, :_reduce_198, 1, 173, :_reduce_199, 2, 171, :_reduce_200, 1, 174, :_reduce_201, 1, 175, :_reduce_202, 2, 175, :_reduce_203, 2, 169, :_reduce_204, 2, 176, :_reduce_205, 2, 176, :_reduce_206, 3, 94, :_reduce_207, 0, 178, :_reduce_none, 1, 178, :_reduce_none, 0, 177, :_reduce_210, 2, 177, :_reduce_211, 4, 177, :_reduce_212, 1, 121, :_reduce_213, 3, 121, :_reduce_214, 5, 121, :_reduce_215, 1, 179, :_reduce_none, 1, 179, :_reduce_none, 1, 127, :_reduce_218, 1, 125, :_reduce_219, 0, 106, :_reduce_none, 1, 106, :_reduce_221, 0, 105, :_reduce_none, 1, 105, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 0, 140, :_reduce_242 ] racc_reduce_n = 243 racc_shift_n = 420 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :WORD => 6, :LBRACK => 7, :RBRACK => 8, :LBRACE => 9, :RBRACE => 10, :SYMBOL => 11, :FARROW => 12, :COMMA => 13, :TRUE => 14, :FALSE => 15, :EQUALS => 16, :APPENDS => 17, :DELETES => 18, :LESSEQUAL => 19, :NOTEQUAL => 20, :DOT => 21, :COLON => 22, :LLCOLLECT => 23, :RRCOLLECT => 24, :QMARK => 25, :LPAREN => 26, :RPAREN => 27, :ISEQUAL => 28, :GREATEREQUAL => 29, :GREATERTHAN => 30, :LESSTHAN => 31, :IF => 32, :ELSE => 33, :DEFINE => 34, :ELSIF => 35, :VARIABLE => 36, :CLASS => 37, :INHERITS => 38, :NODE => 39, :BOOLEAN => 40, :NAME => 41, :SEMIC => 42, :CASE => 43, :DEFAULT => 44, :AT => 45, :ATAT => 46, :LCOLLECT => 47, :RCOLLECT => 48, :CLASSREF => 49, :NOT => 50, :OR => 51, :AND => 52, :UNDEF => 53, :PARROW => 54, :PLUS => 55, :MINUS => 56, :TIMES => 57, :DIV => 58, :LSHIFT => 59, :RSHIFT => 60, :UMINUS => 61, :MATCH => 62, :NOMATCH => 63, :REGEX => 64, :IN_EDGE => 65, :OUT_EDGE => 66, :IN_EDGE_SUB => 67, :OUT_EDGE_SUB => 68, :IN => 69, :UNLESS => 70, :PIPE => 71, :LAMBDA => 72, :SELBRACE => 73, :NUMBER => 74, :HEREDOC => 75, :SUBLOCATE => 76, :RENDER_STRING => 77, :RENDER_EXPR => 78, :EPP_START => 79, :EPP_END => 80, :EPP_END_TRIM => 81, :FUNCTION => 82, :PRIVATE => 83, :ATTR => 84, :TYPE => 85, :LOW => 86, :HIGH => 87, :LISTSTART => 88, :SPLAT => 89, :MODULO => 90 } racc_nt_base = 91 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "STRING", "DQPRE", "DQMID", "DQPOST", "WORD", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "DELETES", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "ATAT", "LCOLLECT", "RCOLLECT", "CLASSREF", "NOT", "OR", "AND", "UNDEF", "PARROW", "PLUS", "MINUS", "TIMES", "DIV", "LSHIFT", "RSHIFT", "UMINUS", "MATCH", "NOMATCH", "REGEX", "IN_EDGE", "OUT_EDGE", "IN_EDGE_SUB", "OUT_EDGE_SUB", "IN", "UNLESS", "PIPE", "LAMBDA", "SELBRACE", "NUMBER", "HEREDOC", "SUBLOCATE", "RENDER_STRING", "RENDER_EXPR", "EPP_START", "EPP_END", "EPP_END_TRIM", "FUNCTION", "PRIVATE", "ATTR", "TYPE", "LOW", "HIGH", "LISTSTART", "SPLAT", "MODULO", "$start", "program", "statements", "epp_expression", "syntactic_statements", "syntactic_statement", "assignment", "relationship", "assignments", "resource", "expression", "attribute_operations", "additional_resource_bodies", "resource_bodies", "endsemi", "endcomma", "resource_body", "primary_expression", "call_function_expression", "expressions", "selector_entries", "variable", "call_method_with_lambda_expression", "collection_expression", "case_expression", "if_expression", "unless_expression", "definition_expression", "hostclass_expression", "node_definition_expression", "epp_render_expression", "reserved_word", "array", "hash", "regex", "quotedtext", "type", "lambda", "call_method_expression", "named_access", "lambda_parameter_list", "lambda_rest", "parameters", "if_part", "else", "unless_else", "case_options", "case_option", "options_statements", "nil", "selector_entry", "selector_entry_list", "collect_query", "optional_query", "attribute_operation", "attribute_name", "keyword", "classname", "parameter_list", "opt_statements", "stacked_classname", "classparent", "classnameordefault", "hostnames", "nodeparent", "hostname", "dotted_name", "name_or_number", "parameter", "untyped_parameter", "typed_parameter", "regular_parameter", "splat_parameter", "parameter_type", "hashpairs", "hashpair", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list", "optional_statements", "epp_end" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted module_eval(<<'.,.,', 'egrammar.ra', 65) def _reduce_1(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 66) def _reduce_2(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 67) def _reduce_3(val, _values, result) result = create_empty_program() result end .,., module_eval(<<'.,.,', 'egrammar.ra', 71) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 78) def _reduce_5(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 79) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 80) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 87) def _reduce_8(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 88) def _reduce_9(val, _values, result) result = aryfy(val[0]).push(val[1]).push(val[2]) result end .,., # reduce 10 omitted module_eval(<<'.,.,', 'egrammar.ra', 93) def _reduce_11(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 94) def _reduce_12(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 95) def _reduce_13(val, _values, result) result = val[0].minus_set(val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 98) def _reduce_14(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 99) def _reduce_15(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 16 omitted module_eval(<<'.,.,', 'egrammar.ra', 103) def _reduce_17(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_18(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_19(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 106) def _reduce_20(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., # reduce 21 omitted module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_22(val, _values, result) result = val[1] unless Factory.set_resource_form(result, :virtual) # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Virtual (@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 126) def _reduce_23(val, _values, result) result = val[1] unless Factory.set_resource_form(result, :exported) # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Exported (@@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 137) def _reduce_24(val, _values, result) bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5] result = Factory.RESOURCE(val[0], bodies) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 144) def _reduce_25(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 153) def _reduce_26(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO should it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 175) def _reduce_27(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 178) def _reduce_28(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 179) def _reduce_29(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 185) def _reduce_30(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 186) def _reduce_31(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 187) def _reduce_32(val, _values, result) result = val[2] result end .,., # reduce 33 omitted # reduce 34 omitted module_eval(<<'.,.,', 'egrammar.ra', 194) def _reduce_35(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 195) def _reduce_36(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 196) def _reduce_37(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 197) def _reduce_38(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 198) def _reduce_39(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 199) def _reduce_40(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 200) def _reduce_41(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 201) def _reduce_42(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 202) def _reduce_43(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 203) def _reduce_44(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 204) def _reduce_45(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 205) def _reduce_46(val, _values, result) result = val[1].minus() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 206) def _reduce_47(val, _values, result) result = val[1].unfold() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 207) def _reduce_48(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 208) def _reduce_49(val, _values, result) result = val[0] == val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 209) def _reduce_50(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 210) def _reduce_51(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 211) def _reduce_52(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 212) def _reduce_53(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 213) def _reduce_54(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 214) def _reduce_55(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 215) def _reduce_56(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 216) def _reduce_57(val, _values, result) result = val[0].select(*val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 217) def _reduce_58(val, _values, result) result = val[1].paren() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 227) def _reduce_59(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 228) def _reduce_60(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 61 omitted # reduce 62 omitted # reduce 63 omitted # reduce 64 omitted # reduce 65 omitted # reduce 66 omitted # reduce 67 omitted # reduce 68 omitted # reduce 69 omitted # reduce 70 omitted # reduce 71 omitted # reduce 72 omitted # reduce 73 omitted # reduce 74 omitted # reduce 75 omitted # reduce 76 omitted module_eval(<<'.,.,', 'egrammar.ra', 247) def _reduce_77(val, _values, result) result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 248) def _reduce_78(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 249) def _reduce_79(val, _values, result) result = Factory.literal(:default) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 250) def _reduce_80(val, _values, result) result = Factory.literal(:undef) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 251) def _reduce_81(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 260) def _reduce_82(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 264) def _reduce_83(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 268) def _reduce_84(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 273) def _reduce_85(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 281) def _reduce_86(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 282) def _reduce_87(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 285) def _reduce_88(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 286) def _reduce_89(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 287) def _reduce_90(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 291) def _reduce_91(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 299) def _reduce_92(val, _values, result) result = Factory.LAMBDA(val[0][:value], val[1][:value]) loc result, val[0][:start], val[1][:end] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 304) def _reduce_93(val, _values, result) result = {:end => val[2], :value =>val[1] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 305) def _reduce_94(val, _values, result) result = {:end => val[1], :value => nil } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 309) def _reduce_95(val, _values, result) result = {:start => val[0], :value => [] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 310) def _reduce_96(val, _values, result) result = {:start => val[0], :value => val[1] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 318) def _reduce_97(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 325) def _reduce_98(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 329) def _reduce_99(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., # reduce 100 omitted module_eval(<<'.,.,', 'egrammar.ra', 337) def _reduce_101(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 341) def _reduce_102(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 345) def _reduce_103(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 352) def _reduce_104(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 356) def _reduce_105(val, _values, result) - result = Factory.UNLESS(val[1], nil, nil) + result = Factory.UNLESS(val[1], nil, val[4]) loc result, val[0], val[4] result end .,., # reduce 106 omitted module_eval(<<'.,.,', 'egrammar.ra', 366) def _reduce_107(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 370) def _reduce_108(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 377) def _reduce_109(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 383) def _reduce_110(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 384) def _reduce_111(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 389) def _reduce_112(val, _values, result) result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] result end .,., # reduce 113 omitted # reduce 114 omitted # reduce 115 omitted module_eval(<<'.,.,', 'egrammar.ra', 405) def _reduce_116(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 410) def _reduce_117(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 411) def _reduce_118(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 416) def _reduce_119(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 426) def _reduce_120(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 430) def _reduce_121(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 435) def _reduce_122(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 436) def _reduce_123(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., # reduce 124 omitted # reduce 125 omitted module_eval(<<'.,.,', 'egrammar.ra', 445) def _reduce_126(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 446) def _reduce_127(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 447) def _reduce_128(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 129 omitted # reduce 130 omitted module_eval(<<'.,.,', 'egrammar.ra', 463) def _reduce_131(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 467) def _reduce_132(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 471) def _reduce_133(val, _values, result) result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 480) def _reduce_134(val, _values, result) result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end result end .,., module_eval(<<'.,.,', 'egrammar.ra', 494) def _reduce_135(val, _values, result) # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 504) def _reduce_136(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., # reduce 137 omitted # reduce 138 omitted # reduce 139 omitted module_eval(<<'.,.,', 'egrammar.ra', 513) def _reduce_140(val, _values, result) result = val[1] result end .,., # reduce 141 omitted # reduce 142 omitted module_eval(<<'.,.,', 'egrammar.ra', 530) def _reduce_143(val, _values, result) result = add_definition(Factory.NODE(val[1], val[3], val[5])) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 534) def _reduce_144(val, _values, result) result = add_definition(Factory.NODE(val[1], val[3], nil)) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 544) def _reduce_145(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 545) def _reduce_146(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 147 omitted # reduce 148 omitted module_eval(<<'.,.,', 'egrammar.ra', 552) def _reduce_149(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., # reduce 150 omitted module_eval(<<'.,.,', 'egrammar.ra', 556) def _reduce_151(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 557) def _reduce_152(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., # reduce 153 omitted # reduce 154 omitted # reduce 155 omitted module_eval(<<'.,.,', 'egrammar.ra', 566) def _reduce_156(val, _values, result) result = val[1] result end .,., # reduce 157 omitted # reduce 158 omitted # reduce 159 omitted module_eval(<<'.,.,', 'egrammar.ra', 585) def _reduce_160(val, _values, result) error val[0], "'class' is not a valid classname" result end .,., module_eval(<<'.,.,', 'egrammar.ra', 589) def _reduce_161(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 590) def _reduce_162(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 591) def _reduce_163(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 595) def _reduce_164(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 596) def _reduce_165(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 166 omitted # reduce 167 omitted # reduce 168 omitted # reduce 169 omitted module_eval(<<'.,.,', 'egrammar.ra', 608) def _reduce_170(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 609) def _reduce_171(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 612) def _reduce_172(val, _values, result) result = val[1]; val[1].captures_rest() result end .,., module_eval(<<'.,.,', 'egrammar.ra', 615) def _reduce_173(val, _values, result) val[1].type_expr(val[0]) ; result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 618) def _reduce_174(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 619) def _reduce_175(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 624) def _reduce_176(val, _values, result) result = Factory.fqn(val[0][:value]).var ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 629) def _reduce_177(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 630) def _reduce_178(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 631) def _reduce_179(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 632) def _reduce_180(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 638) def _reduce_181(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 639) def _reduce_182(val, _values, result) result = Factory.literal([]) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 640) def _reduce_183(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 641) def _reduce_184(val, _values, result) result = Factory.literal([]) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 644) def _reduce_185(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 645) def _reduce_186(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 646) def _reduce_187(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 649) def _reduce_188(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 650) def _reduce_189(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 653) def _reduce_190(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., # reduce 191 omitted # reduce 192 omitted # reduce 193 omitted module_eval(<<'.,.,', 'egrammar.ra', 661) def _reduce_194(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 662) def _reduce_195(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 664) def _reduce_196(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 665) def _reduce_197(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 666) def _reduce_198(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 667) def _reduce_199(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 668) def _reduce_200(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 669) def _reduce_201(val, _values, result) result = Factory.TEXT(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 672) def _reduce_202(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 673) def _reduce_203(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 676) def _reduce_204(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 679) def _reduce_205(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 680) def _reduce_206(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 683) def _reduce_207(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., # reduce 208 omitted # reduce 209 omitted module_eval(<<'.,.,', 'egrammar.ra', 690) def _reduce_210(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'egrammar.ra', 691) def _reduce_211(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 692) def _reduce_212(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 695) def _reduce_213(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 696) def _reduce_214(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 697) def _reduce_215(val, _values, result) result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] result end .,., # reduce 216 omitted # reduce 217 omitted module_eval(<<'.,.,', 'egrammar.ra', 703) def _reduce_218(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 706) def _reduce_219(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., # reduce 220 omitted module_eval(<<'.,.,', 'egrammar.ra', 712) def _reduce_221(val, _values, result) result = nil result end .,., # reduce 222 omitted # reduce 223 omitted # reduce 224 omitted # reduce 225 omitted # reduce 226 omitted # reduce 227 omitted # reduce 228 omitted # reduce 229 omitted # reduce 230 omitted # reduce 231 omitted # reduce 232 omitted # reduce 233 omitted # reduce 234 omitted # reduce 235 omitted # reduce 236 omitted # reduce 237 omitted # reduce 238 omitted # reduce 239 omitted # reduce 240 omitted # reduce 241 omitted module_eval(<<'.,.,', 'egrammar.ra', 739) def _reduce_242(val, _values, result) result = nil result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Parser end # module Pops end # module Puppet diff --git a/lib/puppet/pops/parser/evaluating_parser.rb b/lib/puppet/pops/parser/evaluating_parser.rb index 10fe34f9e..4e84b153e 100644 --- a/lib/puppet/pops/parser/evaluating_parser.rb +++ b/lib/puppet/pops/parser/evaluating_parser.rb @@ -1,144 +1,148 @@ # Does not support "import" and parsing ruby files # class Puppet::Pops::Parser::EvaluatingParser attr_reader :parser def initialize() @parser = Puppet::Pops::Parser::Parser.new() end def parse_string(s, file_source = 'unknown') @file_source = file_source clear() # Handling of syntax error can be much improved (in general), now it bails out of the parser # and does not have as rich information (when parsing a string), need to update it with the file source # (ideally, a syntax error should be entered as an issue, and not just thrown - but that is a general problem # and an improvement that can be made in the eparser (rather than here). # Also a possible improvement (if the YAML parser returns positions) is to provide correct output of position. # begin assert_and_report(parser.parse_string(s)) rescue Puppet::ParseError => e # TODO: This is not quite right, why does not the exception have the correct file? e.file = @file_source unless e.file.is_a?(String) && !e.file.empty? raise e end end def parse_file(file) @file_source = file clear() assert_and_report(parser.parse_file(file)) end def evaluate_string(scope, s, file_source='unknown') evaluate(scope, parse_string(s, file_source)) end def evaluate_file(file) evaluate(parse_file(file)) end def clear() @acceptor = nil end # Create a closure that can be called in the given scope def closure(model, scope) Puppet::Pops::Evaluator::Closure.new(evaluator, model, scope) end def evaluate(scope, model) return nil unless model evaluator.evaluate(model, scope) end def evaluator + # Do not use the cached evaluator if this is a migration run + if (Puppet.lookup(:migration_checker) { nil }) + return Puppet::Pops::Evaluator::EvaluatorImpl.new() + end @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new() @@evaluator end def convert_to_3x(object, scope) - val = @@evaluator.convert(object, scope, nil) + val = evaluator.convert(object, scope, nil) end def validate(parse_result) resulting_acceptor = acceptor() validator(resulting_acceptor).validate(parse_result) resulting_acceptor end def acceptor() Puppet::Pops::Validation::Acceptor.new end def validator(acceptor) Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) end def assert_and_report(parse_result) return nil unless parse_result if parse_result.source_ref.nil? or parse_result.source_ref == '' parse_result.source_ref = @file_source end validation_result = validate(parse_result) Puppet::Pops::IssueReporter.assert_and_report(validation_result, :emit_warnings => true) parse_result end def quote(x) self.class.quote(x) end # Translates an already parsed string that contains control characters, quotes # and backslashes into a quoted string where all such constructs have been escaped. # Parsing the return value of this method using the puppet parser should yield # exactly the same string as the argument passed to this method # # The method makes an exception for the two character sequences \$ and \s. They # will not be escaped since they have a special meaning in puppet syntax. # # TODO: Handle \uXXXX characters ?? # # @param x [String] The string to quote and "unparse" # @return [String] The quoted string # def self.quote(x) escaped = '"' p = nil x.each_char do |c| case p when nil # do nothing when "\t" escaped << '\\t' when "\n" escaped << '\\n' when "\f" escaped << '\\f' # TODO: \cx is a range of characters - skip for now # when "\c" # escaped << '\\c' when '"' escaped << '\\"' when '\\' escaped << if c == '$' || c == 's'; p; else '\\\\'; end # don't escape \ when followed by s or $ else escaped << p end p = c end escaped << p unless p.nil? escaped << '"' end class EvaluatingEppParser < Puppet::Pops::Parser::EvaluatingParser def initialize() @parser = Puppet::Pops::Parser::EppParser.new() end end end diff --git a/lib/puppet/pops/validation.rb b/lib/puppet/pops/validation.rb index eddd7ab2d..570d42fe7 100644 --- a/lib/puppet/pops/validation.rb +++ b/lib/puppet/pops/validation.rb @@ -1,432 +1,455 @@ # A module with base functionality for validation of a model. # # * **Factory** - an abstract factory implementation that makes it easier to create a new validation factory. # * **SeverityProducer** - produces a severity (:error, :warning, :ignore) for a given Issue # * **DiagnosticProducer** - produces a Diagnostic which binds an Issue to an occurrence of that issue # * **Acceptor** - the receiver/sink/collector of computed diagnostics # * **DiagnosticFormatter** - produces human readable output for a Diagnostic # module Puppet::Pops::Validation # This class is an abstract base implementation of a _model validation factory_ that creates a validator instance # and associates it with a fully configured DiagnosticProducer. # # A _validator_ is responsible for validating a model. There may be different versions of validation available # for one and the same model; e.g. different semantics for different puppet versions, or different types of # validation configuration depending on the context/type of validation that should be performed (static, vs. runtime, etc.). # # This class is abstract and must be subclassed. The subclass must implement the methods # {#label_provider} and {#checker}. It is also expected that the sublcass will override # the severity_producer and configure the issues that should be reported as errors (i.e. if they should be ignored, produce # a warning, or a deprecation warning). # # @abstract Subclass must implement {#checker}, and {#label_provider} # @api public # class Factory # Produces a validator with the given acceptor as the recipient of produced diagnostics. # The acceptor is where detected issues are received (and typically collected). # # @param acceptor [Acceptor] the acceptor is the receiver of all detected issues # @return [#validate] a validator responding to `validate(model)` # # @api public # def validator(acceptor) checker(diagnostic_producer(acceptor)) end # Produces the diagnostics producer to use given an acceptor of issues. # # @param acceptor [Acceptor] the acceptor is the receiver of all detected issues # @return [DiagnosticProducer] a detector of issues # # @api public # def diagnostic_producer(acceptor) Puppet::Pops::Validation::DiagnosticProducer.new(acceptor, severity_producer(), label_provider()) end # Produces the SeverityProducer to use # Subclasses should implement and add specific overrides # # @return [SeverityProducer] a severity producer producing error, warning or ignore per issue # # @api public # def severity_producer Puppet::Pops::Validation::SeverityProducer.new end # Produces the checker to use. # # @abstract # # @api public # def checker(diagnostic_producer) raise NoMethodError, "checker" end # Produces the label provider to use. # # @abstract # # @api public # def label_provider raise NoMethodError, "label_provider" end end # Decides on the severity of a given issue. # The produced severity is one of `:error`, `:warning`, or `:ignore`. # By default, a severity of `:error` is produced for all issues. To configure the severity # of an issue call `#severity=(issue, level)`. # # @return [Symbol] a symbol representing the severity `:error`, `:warning`, or `:ignore` # # @api public # class SeverityProducer @@severity_hash = {:ignore => true, :warning => true, :error => true, :deprecation => true } # Creates a new instance where all issues are diagnosed as :error unless overridden. + # @param [Symbol] specifies default severity if :error is not wanted as the default # @api public # - def initialize + def initialize(default_severity = :error) # If diagnose is not set, the default is returned by the block - @severities = Hash.new :error + @severities = Hash.new default_severity end # Returns the severity of the given issue. # @return [Symbol] severity level :error, :warning, or :ignore # @api public # def severity(issue) assert_issue(issue) @severities[issue] end # @see {#severity} # @api public # def [] issue severity issue end # Override a default severity with the given severity level. # # @param issue [Puppet::Pops::Issues::Issue] the issue for which to set severity # @param level [Symbol] the severity level (:error, :warning, or :ignore). # @api public # def []=(issue, level) raise Puppet::DevError.new("Attempt to set validation severity for something that is not an Issue. (Got #{issue.class})") unless issue.is_a? Puppet::Pops::Issues::Issue raise Puppet::DevError.new("Illegal severity level: #{option}") unless @@severity_hash[level] raise Puppet::DevError.new("Attempt to demote the hard issue '#{issue.issue_code}' to #{level}") unless issue.demotable? || level == :error @severities[issue] = level end # Returns `true` if the issue should be reported or not. # @return [Boolean] this implementation returns true for errors and warnings # # @api public # def should_report? issue diagnose = @severities[issue] diagnose == :error || diagnose == :warning || diagnose == :deprecation end # Checks if the given issue is valid. # @api private # def assert_issue issue raise Puppet::DevError.new("Attempt to get validation severity for something that is not an Issue. (Got #{issue.class})") unless issue.is_a? Puppet::Pops::Issues::Issue end # Checks if the given severity level is valid. # @api private # def assert_severity level raise Puppet::DevError.new("Illegal severity level: #{option}") unless @@severity_hash[level] end end # A producer of diagnostics. # An producer of diagnostics is given each issue occurrence as they are found by a diagnostician/validator. It then produces # a Diagnostic, which it passes on to a configured Acceptor. # # This class exists to aid a diagnostician/validator which will typically first check if a particular issue # will be accepted at all (before checking for an occurrence of the issue; i.e. to perform check avoidance for expensive checks). # A validator passes an instance of Issue, the semantic object (the "culprit"), a hash with arguments, and an optional # exception. The semantic object is used to determine the location of the occurrence of the issue (file/line), and it # sets keys in the given argument hash that may be used in the formatting of the issue message. # class DiagnosticProducer # A producer of severity for a given issue # @return [SeverityProducer] # attr_reader :severity_producer # A producer of labels for objects involved in the issue # @return [LabelProvider] # attr_reader :label_provider # Initializes this producer. # # @param acceptor [Acceptor] a sink/collector of diagnostic results # @param severity_producer [SeverityProducer] the severity producer to use to determine severity of a given issue # @param label_provider [LabelProvider] a provider of model element type to human readable label # def initialize(acceptor, severity_producer, label_provider) @acceptor = acceptor @severity_producer = severity_producer @label_provider = label_provider end def accept(issue, semantic, arguments={}, except=nil) return unless will_accept? issue # Set label provider unless caller provided a special label provider arguments[:label] ||= @label_provider arguments[:semantic] ||= semantic # A detail message is always provided, but is blank by default. # TODO: this support is questionable, it requires knowledge that :detail is special arguments[:detail] ||= '' source_pos = Puppet::Pops::Utils.find_closest_positioned(semantic) file = source_pos ? source_pos.locator.file : nil severity = @severity_producer.severity(issue) @acceptor.accept(Diagnostic.new(severity, issue, file, source_pos, arguments, except)) end def will_accept? issue @severity_producer.should_report? issue end end class Diagnostic attr_reader :severity attr_reader :issue attr_reader :arguments attr_reader :exception attr_reader :file attr_reader :source_pos def initialize severity, issue, file, source_pos, arguments={}, exception=nil @severity = severity @issue = issue @file = file @source_pos = source_pos @arguments = arguments # TODO: Currently unused, the intention is to provide more information (stack backtrace, etc.) when # debugging or similar - this to catch internal problems reported as higher level issues. @exception = exception end + + # Two diagnostics are considered equal if the have the same issue, location and severity + # (arguments and exception are ignored) + # + def ==(o) + self.class == o.class && + same_position?(o) && + issue.issue_code == o.issue.issue_code && + file == o.file && + severity == o.severity + end + alias eql? == + + # Position is equal if the diagnostic is not located or if referring to the same offset + def same_position?(o) + source_pos.nil? && o.source_pos.nil? || source_pos.offset == o.source_pos.offset + end + private :same_position? + + def hash + @hash ||= [file, source_pos.offset, issue.issue_code, severity].hash + end end # Formats a diagnostic for output. # Produces a diagnostic output typical for a compiler (suitable for interpretation by tools) # The format is: # `file:line:pos: Message`, where pos, line and file are included if available. # class DiagnosticFormatter def format diagnostic "#{loc(diagnostic)} #{format_severity(diagnostic)}#{format_message(diagnostic)}" end def format_message diagnostic diagnostic.issue.format(diagnostic.arguments) end # This produces "Deprecation notice: " prefix if the diagnostic has :deprecation severity, otherwise "". # The idea is that all other diagnostics are emitted with the methods Puppet.err (or an exception), and # Puppet.warning. # @note Note that it is not a good idea to use Puppet.deprecation_warning as it is for internal deprecation. # def format_severity diagnostic diagnostic.severity == :deprecation ? "Deprecation notice: " : "" end def format_location diagnostic file = diagnostic.file file = (file.is_a?(String) && file.empty?) ? nil : file line = pos = nil if diagnostic.source_pos line = diagnostic.source_pos.line pos = diagnostic.source_pos.pos end if file && line && pos "#{file}:#{line}:#{pos}:" elsif file && line "#{file}:#{line}:" elsif file "#{file}:" else "" end end end # Produces a diagnostic output in the "puppet style", where the location is appended with an "at ..." if the # location is known. # class DiagnosticFormatterPuppetStyle < DiagnosticFormatter def format diagnostic if (location = format_location diagnostic) != "" "#{format_severity(diagnostic)}#{format_message(diagnostic)}#{location}" else format_message(diagnostic) end end # The somewhat (machine) unusable format in current use by puppet. # have to be used here for backwards compatibility. def format_location diagnostic file = diagnostic.file file = (file.is_a?(String) && file.empty?) ? nil : file line = pos = nil if diagnostic.source_pos line = diagnostic.source_pos.line pos = diagnostic.source_pos.pos end if file && line && pos " at #{file}:#{line}:#{pos}" elsif file and line " at #{file}:#{line}" elsif line && pos " at line #{line}:#{pos}" elsif line " at line #{line}" elsif file " in #{file}" else "" end end end # An acceptor of diagnostics. # An acceptor of diagnostics is given each issue as they are found by a diagnostician/validator. An # acceptor can collect all found issues, or decide to collect a few and then report, or give up as the first issue # if found. # This default implementation collects all diagnostics in the order they are produced, and can then # answer questions about what was diagnosed. # class Acceptor # All diagnostic in the order they were issued attr_reader :diagnostics # The number of :warning severity issues + number of :deprecation severity issues attr_reader :warning_count # The number of :error severity issues attr_reader :error_count # Initializes this diagnostics acceptor. # By default, the acceptor is configured with a default severity producer. # @param severity_producer [SeverityProducer] the severity producer to use to determine severity of an issue # # TODO add semantic_label_provider # def initialize() @diagnostics = [] @error_count = 0 @warning_count = 0 end # Returns true when errors have been diagnosed. def errors? @error_count > 0 end # Returns true when warnings have been diagnosed. def warnings? @warning_count > 0 end # Returns true when errors and/or warnings have been diagnosed. def errors_or_warnings? errors? || warnings? end # Returns the diagnosed errors in the order thwy were reported. def errors @diagnostics.select {|d| d.severity == :error } end # Returns the diagnosed warnings in the order thwy were reported. # (This includes :warning and :deprecation severity) def warnings @diagnostics.select {|d| d.severity == :warning || d.severity == :deprecation } end def errors_and_warnings @diagnostics.select {|d| d.severity != :ignore } end # Returns the ignored diagnostics in the order thwy were reported (if reported at all) def ignored @diagnostics.select {|d| d.severity == :ignore } end # Add a diagnostic, or all diagnostics from another acceptor to the set of diagnostics # @param diagnostic [Puppet::Pops::Validation::Diagnostic, Puppet::Pops::Validation::Acceptor] diagnostic(s) that should be accepted def accept(diagnostic) if diagnostic.is_a?(Acceptor) diagnostic.diagnostics.each {|d| self.send(d.severity, d)} else self.send(diagnostic.severity, diagnostic) end end # Prunes the contain diagnostics by removing those for which the given block returns true. # The internal statistics is updated as a consequence of removing. # @return [Array varname_string) end # Can not assign to something in another namespace (i.e. a '::' in the name is not legal) if acceptor.will_accept? Issues::CROSS_SCOPE_ASSIGNMENT if varname_string =~ /::/ acceptor.accept(Issues::CROSS_SCOPE_ASSIGNMENT, o, :name => varname_string) end end # TODO: Could scan for reassignment of the same variable if done earlier in the same container # Or if assigning to a parameter (more work). # TODO: Investigate if there are invalid cases for += assignment end def assign_AccessExpression(o, via_index) # Are indexed assignments allowed at all ? $x[x] = '...' if acceptor.will_accept? Issues::ILLEGAL_INDEXED_ASSIGNMENT acceptor.accept(Issues::ILLEGAL_INDEXED_ASSIGNMENT, o) else # Then the left expression must be assignable-via-index assign(o.left_expr, true) end end def assign_Object(o, via_index) # Can not assign to anything else (differentiate if this is via index or not) # i.e. 10 = 'hello' vs. 10['x'] = 'hello' (the root is reported as being in error in both cases) # acceptor.accept(via_index ? Issues::ILLEGAL_ASSIGNMENT_VIA_INDEX : Issues::ILLEGAL_ASSIGNMENT, o) end #---CHECKS def check_Object(o) end def check_Factory(o) check(o.current) end def check_AccessExpression(o) # Only min range is checked, all other checks are RT checks as they depend on the resulting type # of the LHS. if o.keys.size < 1 acceptor.accept(Issues::MISSING_INDEX, o) end end def check_AssignmentExpression(o) case o.operator when :'=' assign(o.left_expr) rvalue(o.right_expr) when :'+=', :'-=' acceptor.accept(Issues::APPENDS_DELETES_NO_LONGER_SUPPORTED, o, {:operator => o.operator}) else acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end end # Checks that operation with :+> is contained in a ResourceOverride or Collector. # # Parent of an AttributeOperation can be one of: # * CollectExpression # * ResourceOverride # * ResourceBody (ILLEGAL this is a regular resource expression) # * ResourceDefaults (ILLEGAL) # def check_AttributeOperation(o) if o.operator == :'+>' # Append operator use is constrained parent = o.eContainer unless parent.is_a?(Model::CollectExpression) || parent.is_a?(Model::ResourceOverrideExpression) acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>parent}) end end rvalue(o.value_expr) end def check_AttributesOperation(o) # Append operator use is constrained parent = o.eContainer parent = parent.eContainer unless parent.nil? unless parent.is_a?(Model::ResourceExpression) acceptor.accept(Issues::UNSUPPORTED_OPERATOR_IN_CONTEXT, o, :operator=>'* =>') end rvalue(o.expr) end def check_BinaryExpression(o) rvalue(o.left_expr) rvalue(o.right_expr) end def check_BlockExpression(o) o.statements[0..-2].each do |statement| if idem(statement) acceptor.accept(Issues::IDEM_EXPRESSION_NOT_LAST, statement) break # only flag the first end end + migration_checker.report_array_last_in_block(o.statements[-1]) end def check_CallNamedFunctionExpression(o) case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok nil when Puppet::Pops::Model::RenderStringExpression # helpful to point out this easy to make Epp error acceptor.accept(Issues::ILLEGAL_EPP_PARAMETERS, o) else acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end end def check_EppExpression(o) if o.eContainer.is_a?(Puppet::Pops::Model::LambdaExpression) internal_check_no_capture(o.eContainer, o) end end def check_MethodCallExpression(o) unless o.functor_expr.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o) end end def check_CaseExpression(o) rvalue(o.test) # There should only be one LiteralDefault case option value # TODO: Implement this check end def check_CaseOption(o) o.values.each { |v| rvalue(v) } end def check_CollectExpression(o) unless o.type_expr.is_a? Model::QualifiedReference acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_expr, :feature=> 'type name', :container => o) end # If a collect expression tries to collect exported resources and storeconfigs is not on # then it will not work... This was checked in the parser previously. This is a runtime checking # thing as opposed to a language thing. if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.query.is_a?(Model::ExportedQuery) acceptor.accept(Issues::RT_NO_STORECONFIGS, o) end end # Only used for function names, grammar should not be able to produce something faulty, but # check anyway if model is created programatically (it will fail in transformation to AST for sure). def check_NamedAccessExpression(o) name = o.right_expr unless name.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, name, :feature=> 'function name', :container => o.eContainer) end end RESERVED_TYPE_NAMES = { 'type' => true, 'any' => true, 'unit' => true, 'scalar' => true, 'boolean' => true, 'numeric' => true, 'integer' => true, 'float' => true, 'collection' => true, 'array' => true, 'hash' => true, 'tuple' => true, 'struct' => true, 'variant' => true, 'optional' => true, 'enum' => true, 'regexp' => true, 'pattern' => true, 'runtime' => true, } # for 'class', 'define', and function def check_NamedDefinition(o) top(o.eContainer, o) if o.name !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name}) end if RESERVED_TYPE_NAMES[o.name()] acceptor.accept(Issues::RESERVED_TYPE_NAME, o, {:name => o.name}) end if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end end def check_HostClassDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_reserved_params(o) end def check_ResourceTypeDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_reserved_params(o) end def internal_check_capture_last(o) accepted_index = o.parameters.size() -1 o.parameters.each_with_index do |p, index| if p.captures_rest && index != accepted_index acceptor.accept(Issues::CAPTURES_REST_NOT_LAST, p, {:param_name => p.name}) end end end def internal_check_no_capture(o, container = o) o.parameters.each do |p| if p.captures_rest acceptor.accept(Issues::CAPTURES_REST_NOT_SUPPORTED, p, {:container => container, :param_name => p.name}) end end end RESERVED_PARAMETERS = { 'name' => true, 'title' => true, } def internal_check_reserved_params(o) o.parameters.each do |p| if RESERVED_PARAMETERS[p.name] acceptor.accept(Issues::RESERVED_PARAMETER, p, {:container => o, :param_name => p.name}) end end end def check_IfExpression(o) rvalue(o.test) end def check_KeyedEntry(o) rvalue(o.key) rvalue(o.value) # In case there are additional things to forbid than non-rvalues # acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer) end def check_LambdaExpression(o) internal_check_capture_last(o) end def check_LiteralList(o) o.values.each {|v| rvalue(v) } end + def check_LiteralFloat(o) + migration_checker.report_ambiguous_float(o) + end + + def check_LiteralInteger(o) + migration_checker.report_ambiguous_integer(o) + end + def check_NodeDefinition(o) # Check that hostnames are valid hostnames (or regular expressions) hostname(o.host_matches, o) hostname(o.parent, o, 'parent') unless o.parent.nil? top(o.eContainer, o) if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end unless o.parent.nil? acceptor.accept(Issues::ILLEGAL_NODE_INHERITANCE, o.parent) end end # No checking takes place - all expressions using a QualifiedName need to check. This because the # rules are slightly different depending on the container (A variable allows a numeric start, but not # other names). This means that (if the lexer/parser so chooses) a QualifiedName # can be anything when it represents a Bare Word and evaluates to a String. # def check_QualifiedName(o) end # Checks that the value is a valid UpperCaseWord (a CLASSREF), and optionally if it contains a hypen. # DOH: QualifiedReferences are created with LOWER CASE NAMES at parse time def check_QualifiedReference(o) # Is this a valid qualified name? if o.value !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.value}) end end def check_QueryExpression(o) query(o.expr) if o.expr # is optional end def relation_Object(o) rvalue(o) end def relation_CollectExpression(o); end def relation_RelationshipExpression(o); end def check_Parameter(o) if o.name =~ /^(?:0x)?[0-9]+$/ acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name) end end #relationship_side: resource # | resourceref # | collection # | variable # | quotedtext # | selector # | casestatement # | hasharrayaccesses def check_RelationshipExpression(o) relation(o.left_expr) relation(o.right_expr) end def check_ResourceExpression(o) # The expression for type name cannot be statically checked - this is instead done at runtime # to enable better error message of the result of the expression rather than the static instruction. # (This can be revised as there are static constructs that are illegal, but require updating many # tests that expect the detailed reporting). end def check_ResourceBody(o) seenUnfolding = false o.operations.each do |ao| if ao.is_a?(Puppet::Pops::Model::AttributesOperation) if seenUnfolding acceptor.accept(Issues::MULTIPLE_ATTRIBUTES_UNFOLD, ao) else seenUnfolding = true end end end end def check_ResourceDefaultsExpression(o) if o.form && o.form != :regular acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) end end def check_ResourceOverrideExpression(o) if o.form && o.form != :regular acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) end end def check_ReservedWord(o) acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word) end def check_SelectorExpression(o) rvalue(o.left_expr) end def check_SelectorEntry(o) rvalue(o.matching_expr) end def check_UnaryExpression(o) rvalue(o.expr) end def check_UnlessExpression(o) rvalue(o.test) # TODO: Unless may not have an else part that is an IfExpression (grammar denies this though) end # Checks that variable is either strictly 0, or a non 0 starting decimal number, or a valid VAR_NAME def check_VariableExpression(o) # The expression must be a qualified name or an integer name_expr = o.expr return if name_expr.is_a?(Model::LiteralInteger) if !name_expr.is_a?(Model::QualifiedName) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o) else # name must be either a decimal string value, or a valid NAME name = o.expr.value if name[0,1] =~ /[0-9]/ unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_VAR_NAME, o, :name => name) end else unless name =~ Puppet::Pops::Patterns::VAR_NAME acceptor.accept(Issues::ILLEGAL_VAR_NAME, o, :name => name) end end end end #--- HOSTNAME CHECKS # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o, semantic, single_feature_name) if single_feature_name acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>single_feature_name, :container=>semantic}) end o.each {|x| hostname(x, semantic, false) } end def hostname_String(o, semantic, single_feature_name) # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid, # but this allows pathological names like "a..b......c", "----" # TODO: Investigate if more illegal hostnames should be flagged. # if o =~ Puppet::Pops::Patterns::ILLEGAL_HOSTNAME_CHARS acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o) end end def hostname_LiteralValue(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_ConcatenatedString(o, semantic, single_feature_name) # Puppet 3.1. only accepts a concatenated string without interpolated expressions if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) } acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr) elsif o.segments.size() != 1 # corner case, bad model, concatenation of several plain strings acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o) else # corner case, may be ok, but lexer may have replaced with plain string, this is # here if it does not hostname_String(o.segments[0], o.segments[0], false) end end def hostname_QualifiedName(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_QualifiedReference(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_LiteralNumber(o, semantic, single_feature_name) # always ok end def hostname_LiteralDefault(o, semantic, single_feature_name) # always ok end def hostname_LiteralRegularExpression(o, semantic, single_feature_name) # always ok end def hostname_Object(o, semantic, single_feature_name) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=> single_feature_name || 'hostname', :container=>semantic}) end #---QUERY CHECKS # Anything not explicitly allowed is flagged as error. def query_Object(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) end # Puppet AST only allows == and != # def query_ComparisonExpression(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) unless [:'==', :'!='].include? o.operator end # Allows AND, OR, and checks if left/right are allowed in query. def query_BooleanExpression(o) query o.left_expr query o.right_expr end def query_ParenthesizedExpression(o) query(o.expr) end def query_VariableExpression(o); end def query_QualifiedName(o); end def query_LiteralNumber(o); end def query_LiteralString(o); end def query_LiteralBoolean(o); end #---RVALUE CHECKS # By default, all expressions are reported as being rvalues # Implement specific rvalue checks for those that are not. # def rvalue_Expression(o); end def rvalue_CollectExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_Definition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_NodeDefinition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_UnaryExpression(o) ; rvalue o.expr ; end #---TOP CHECK def top_NilClass(o, definition) # ok, reached the top, no more parents end def top_Object(o, definition) # fail, reached a container that is not top level acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end def top_BlockExpression(o, definition) # ok, if this is a block representing the body of a class, or is top level top o.eContainer, definition end def top_HostClassDefinition(o, definition) # ok, stop scanning parents end def top_Program(o, definition) # ok end # A LambdaExpression is a BlockExpression, and this method is needed to prevent the polymorph method for BlockExpression # to accept a lambda. # A lambda can not iteratively create classes, nodes or defines as the lambda does not have a closure. # def top_LambdaExpression(o, definition) # fail, stop scanning parents acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end #--IDEM CHECK def idem_Object(o) false end def idem_Nop(o) true end def idem_NilClass(o) true end def idem_Literal(o) true end def idem_LiteralList(o) true end def idem_LiteralHash(o) true end def idem_Factory(o) idem(o.current) end def idem_AccessExpression(o) true end def idem_BinaryExpression(o) true end def idem_RelationshipExpression(o) # Always side effect false end def idem_AssignmentExpression(o) # Always side effect false end # Handles UnaryMinusExpression, NotExpression, VariableExpression def idem_UnaryExpression(o) true end # Allow (no-effect parentheses) to be used around a productive expression def idem_ParenthesizedExpression(o) idem(o.expr) end def idem_RenderExpression(o) false end def idem_RenderStringExpression(o) false end def idem_BlockExpression(o) # productive if there is at least one productive expression ! o.statements.any? {|expr| !idem(expr) } end # Returns true even though there may be interpolated expressions that have side effect. # Report as idem anyway, as it is very bad design to evaluate an interpolated string for its # side effect only. def idem_ConcatenatedString(o) true end # Heredoc is just a string, but may contain interpolated string (which may have side effects). # This is still bad design and should be reported as idem. def idem_HeredocExpression(o) true end # May technically have side effects inside the Selector, but this is bad design - treat as idem def idem_SelectorExpression(o) true end def idem_IfExpression(o) [o.test, o.then_expr, o.else_expr].all? {|e| idem(e) } end # Case expression is idem, if test, and all options are idem def idem_CaseExpression(o) return false if !idem(o.test) ! o.options.any? {|opt| !idem(opt) } end # An option is idem if values and the then_expression are idem def idem_CaseOption(o) return false if o.values.any? { |value| !idem(value) } idem(o.then_expr) end #--- NON POLYMORPH, NON CHECKING CODE # Produces string part of something named, or nil if not a QualifiedName or QualifiedReference # def varname_to_s(o) case o when Model::QualifiedName o.value when Model::QualifiedReference o.value else nil end end end diff --git a/lib/puppet/provider/group/windows_adsi.rb b/lib/puppet/provider/group/windows_adsi.rb index c6db2af92..76cd65805 100644 --- a/lib/puppet/provider/group/windows_adsi.rb +++ b/lib/puppet/provider/group/windows_adsi.rb @@ -1,86 +1,93 @@ require 'puppet/util/windows' Puppet::Type.type(:group).provide :windows_adsi do desc "Local group management for Windows. Group members can be both users and groups. Additionally, local groups can contain domain users." defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_features :manages_members def members_insync?(current, should) return false unless current # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of account names. # Cannot use munge of the group property to canonicalize @should # since the default array_matching comparison is not commutative should_empty = should.nil? or should.empty? return false if current.empty? != should_empty # dupes automatically weeded out when hashes built - Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) == Puppet::Util::Windows::ADSI::Group.name_sid_hash(should) + current_users = Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) + specified_users = Puppet::Util::Windows::ADSI::Group.name_sid_hash(should) + + if @resource[:auth_membership] + current_users == specified_users + else + (specified_users.keys.to_a & current_users.keys.to_a) == specified_users.keys.to_a + end end def members_to_s(users) return '' if users.nil? or !users.kind_of?(Array) users = users.map do |user_name| sid = Puppet::Util::Windows::SID.name_to_sid_object(user_name) if sid.account =~ /\\/ account, _ = Puppet::Util::Windows::ADSI::User.parse_name(sid.account) else account = sid.account end resource.debug("#{sid.domain}\\#{account} (#{sid.to_s})") "#{sid.domain}\\#{account}" end return users.join(',') end def group @group ||= Puppet::Util::Windows::ADSI::Group.new(@resource[:name]) end def members group.members end def members=(members) - group.set_members(members) + group.set_members(members, @resource[:auth_membership]) end def create @group = Puppet::Util::Windows::ADSI::Group.create(@resource[:name]) @group.commit self.members = @resource[:members] end def exists? Puppet::Util::Windows::ADSI::Group.exists?(@resource[:name]) end def delete Puppet::Util::Windows::ADSI::Group.delete(@resource[:name]) end # Only flush if we created or modified a group, not deleted def flush @group.commit if @group end def gid Puppet::Util::Windows::SID.name_to_sid(@resource[:name]) end def gid=(value) fail "gid is read-only" end def self.instances Puppet::Util::Windows::ADSI::Group.map { |g| new(:ensure => :present, :name => g.name) } end end diff --git a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb index d00d3da6f..66d946301 100644 --- a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb +++ b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb @@ -1,558 +1,558 @@ require 'puppet/parameter' if Puppet.features.microsoft_windows? require 'puppet/util/windows/taskscheduler' end Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do desc %q{This provider manages scheduled tasks on Windows.} defaultfor :operatingsystem => :windows confine :operatingsystem => :windows def self.instances Win32::TaskScheduler.new.tasks.collect do |job_file| job_title = File.basename(job_file, '.job') new( :provider => :win32_taskscheduler, :name => job_title ) end end def exists? Win32::TaskScheduler.new.exists? resource[:name] end def task return @task if @task @task ||= Win32::TaskScheduler.new @task.activate(resource[:name] + '.job') if exists? @task end def clear_task @task = nil @triggers = nil end def enabled task.flags & Win32::TaskScheduler::DISABLED == 0 ? :true : :false end def command task.application_name end def arguments task.parameters end def working_dir task.working_directory end def user account = task.account_information return 'system' if account == '' account end def trigger return @triggers if @triggers @triggers = [] task.trigger_count.times do |i| trigger = begin task.trigger(i) rescue Win32::TaskScheduler::Error # Win32::TaskScheduler can't handle all of the # trigger types Windows uses, so we need to skip the # unhandled types to prevent "puppet resource" from # blowing up. nil end next unless trigger and scheduler_trigger_types.include?(trigger['trigger_type']) puppet_trigger = {} case trigger['trigger_type'] when Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY puppet_trigger['schedule'] = 'daily' puppet_trigger['every'] = trigger['type']['days_interval'].to_s when Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY - puppet_trigger['schedule'] = 'weekly' - puppet_trigger['every'] = trigger['type']['weeks_interval'].to_s - puppet_trigger['on'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) + puppet_trigger['schedule'] = 'weekly' + puppet_trigger['every'] = trigger['type']['weeks_interval'].to_s + puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['on'] = days_from_bitfield(trigger['type']['days']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['which_occurrence'] = occurrence_constant_to_name(trigger['type']['weeks']) puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE puppet_trigger['schedule'] = 'once' end puppet_trigger['start_date'] = self.class.normalized_date("#{trigger['start_year']}-#{trigger['start_month']}-#{trigger['start_day']}") puppet_trigger['start_time'] = self.class.normalized_time("#{trigger['start_hour']}:#{trigger['start_minute']}") puppet_trigger['enabled'] = trigger['flags'] & Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED == 0 puppet_trigger['index'] = i @triggers << puppet_trigger end @triggers end def user_insync?(current, should) return false unless current # Win32::TaskScheduler can return the 'SYSTEM' account as the # empty string. current = 'system' if current == '' # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of the account name. Puppet::Util::Windows::SID.name_to_sid(current) == Puppet::Util::Windows::SID.name_to_sid(should[0]) end def trigger_insync?(current, should) should = [should] unless should.is_a?(Array) current = [current] unless current.is_a?(Array) return false unless current.length == should.length current_in_sync = current.all? do |c| should.any? {|s| triggers_same?(c, s)} end should_in_sync = should.all? do |s| current.any? {|c| triggers_same?(c,s)} end current_in_sync && should_in_sync end def command=(value) task.application_name = value end def arguments=(value) task.parameters = value end def working_dir=(value) task.working_directory = value end def enabled=(value) if value == :true task.flags = task.flags & ~Win32::TaskScheduler::DISABLED else task.flags = task.flags | Win32::TaskScheduler::DISABLED end end def trigger=(value) desired_triggers = value.is_a?(Array) ? value : [value] current_triggers = trigger.is_a?(Array) ? trigger : [trigger] extra_triggers = [] desired_to_search = desired_triggers.dup current_triggers.each do |current| if found = desired_to_search.find {|desired| triggers_same?(current, desired)} desired_to_search.delete(found) else extra_triggers << current['index'] end end needed_triggers = [] current_to_search = current_triggers.dup desired_triggers.each do |desired| if found = current_to_search.find {|current| triggers_same?(current, desired)} current_to_search.delete(found) else needed_triggers << desired end end extra_triggers.reverse_each do |index| task.delete_trigger(index) end needed_triggers.each do |trigger_hash| # Even though this is an assignment, the API for # Win32::TaskScheduler ends up appending this trigger to the # list of triggers for the task, while #add_trigger is only able # to replace existing triggers. *shrug* task.trigger = translate_hash_to_trigger(trigger_hash) end end def user=(value) self.fail("Invalid user: #{value}") unless Puppet::Util::Windows::SID.name_to_sid(value) if value.to_s.downcase != 'system' task.set_account_information(value, resource[:password]) else # Win32::TaskScheduler treats a nil/empty username & password as # requesting the SYSTEM account. task.set_account_information(nil, nil) end end def create clear_task @task = Win32::TaskScheduler.new(resource[:name], dummy_time_trigger) self.command = resource[:command] [:arguments, :working_dir, :enabled, :trigger, :user].each do |prop| send("#{prop}=", resource[prop]) if resource[prop] end end def destroy Win32::TaskScheduler.new.delete(resource[:name] + '.job') end def flush unless resource[:ensure] == :absent self.fail('Parameter command is required.') unless resource[:command] task.save @task = nil end end def triggers_same?(current_trigger, desired_trigger) return false unless current_trigger['schedule'] == desired_trigger['schedule'] return false if current_trigger.has_key?('enabled') && !current_trigger['enabled'] desired = desired_trigger.dup desired['start_date'] ||= current_trigger['start_date'] if current_trigger.has_key?('start_date') desired['every'] ||= current_trigger['every'] if current_trigger.has_key?('every') desired['months'] ||= current_trigger['months'] if current_trigger.has_key?('months') desired['on'] ||= current_trigger['on'] if current_trigger.has_key?('on') desired['day_of_week'] ||= current_trigger['day_of_week'] if current_trigger.has_key?('day_of_week') translate_hash_to_trigger(current_trigger) == translate_hash_to_trigger(desired) end def self.normalized_date(date_string) date = Date.parse("#{date_string}") "#{date.year}-#{date.month}-#{date.day}" end def self.normalized_time(time_string) Time.parse("#{time_string}").strftime('%H:%M') end def dummy_time_trigger now = Time.now { 'flags' => 0, 'random_minutes_interval' => 0, 'end_day' => 0, "end_year" => 0, "minutes_interval" => 0, "end_month" => 0, "minutes_duration" => 0, 'start_year' => now.year, 'start_month' => now.month, 'start_day' => now.day, 'start_hour' => now.hour, 'start_minute' => now.min, 'trigger_type' => Win32::TaskScheduler::ONCE, } end def translate_hash_to_trigger(puppet_trigger) trigger = dummy_time_trigger if puppet_trigger['enabled'] == false trigger['flags'] |= Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED else trigger['flags'] &= ~Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED end extra_keys = puppet_trigger.keys.sort - ['index', 'enabled', 'schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week'] self.fail "Unknown trigger option(s): #{Puppet::Parameter.format_value_for_display(extra_keys)}" unless extra_keys.empty? self.fail "Must specify 'start_time' when defining a trigger" unless puppet_trigger['start_time'] case puppet_trigger['schedule'] when 'daily' trigger['trigger_type'] = Win32::TaskScheduler::DAILY trigger['type'] = { 'days_interval' => Integer(puppet_trigger['every'] || 1) } when 'weekly' trigger['trigger_type'] = Win32::TaskScheduler::WEEKLY trigger['type'] = { 'weeks_interval' => Integer(puppet_trigger['every'] || 1) } trigger['type']['days_of_week'] = if puppet_trigger['day_of_week'] bitfield_from_days_of_week(puppet_trigger['day_of_week']) else scheduler_days_of_week.inject(0) {|day_flags,day| day_flags |= day} end when 'monthly' trigger['type'] = { 'months' => bitfield_from_months(puppet_trigger['months'] || (1..12).to_a), } if puppet_trigger.keys.include?('on') if puppet_trigger.has_key?('day_of_week') or puppet_trigger.has_key?('which_occurrence') self.fail "Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger" end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDATE trigger['type']['days'] = bitfield_from_days(puppet_trigger['on']) elsif puppet_trigger.keys.include?('which_occurrence') or puppet_trigger.keys.include?('day_of_week') self.fail 'which_occurrence cannot be specified as an array' if puppet_trigger['which_occurrence'].is_a?(Array) %w{day_of_week which_occurrence}.each do |field| self.fail "#{field} must be specified when creating a monthly day-of-week based trigger" unless puppet_trigger.has_key?(field) end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDOW trigger['type']['weeks'] = occurrence_name_to_constant(puppet_trigger['which_occurrence']) trigger['type']['days_of_week'] = bitfield_from_days_of_week(puppet_trigger['day_of_week']) else self.fail "Don't know how to create a 'monthly' schedule with the options: #{puppet_trigger.keys.sort.join(', ')}" end when 'once' self.fail "Must specify 'start_date' when defining a one-time trigger" unless puppet_trigger['start_date'] trigger['trigger_type'] = Win32::TaskScheduler::ONCE else self.fail "Unknown schedule type: #{puppet_trigger["schedule"].inspect}" end if start_date = puppet_trigger['start_date'] start_date = Date.parse(start_date) self.fail "start_date must be on or after 1753-01-01" unless start_date >= Date.new(1753, 1, 1) trigger['start_year'] = start_date.year trigger['start_month'] = start_date.month trigger['start_day'] = start_date.day end start_time = Time.parse(puppet_trigger['start_time']) trigger['start_hour'] = start_time.hour trigger['start_minute'] = start_time.min trigger end def validate_trigger(value) value = [value] unless value.is_a?(Array) value.each do |t| if t.has_key?('index') self.fail "'index' is read-only on scheduled_task triggers and should be removed ('index' is usually provided in puppet resource scheduled_task)." end if t.has_key?('enabled') self.fail "'enabled' is read-only on scheduled_task triggers and should be removed ('enabled' is usually provided in puppet resource scheduled_task)." end translate_hash_to_trigger(t) end true end private def bitfield_from_months(months) bitfield = 0 months = [months] unless months.is_a?(Array) months.each do |month| integer_month = Integer(month) rescue nil self.fail 'Month must be specified as an integer in the range 1-12' unless integer_month == month.to_f and integer_month.between?(1,12) bitfield |= scheduler_months[integer_month - 1] end bitfield end def bitfield_from_days(days) bitfield = 0 days = [days] unless days.is_a?(Array) days.each do |day| # The special "day" of 'last' is represented by day "number" # 32. 'last' has the special meaning of "the last day of the # month", no matter how many days there are in the month. day = 32 if day == 'last' integer_day = Integer(day) self.fail "Day must be specified as an integer in the range 1-31, or as 'last'" unless integer_day = day.to_f and integer_day.between?(1,32) bitfield |= 1 << integer_day - 1 end bitfield end def bitfield_from_days_of_week(days_of_week) bitfield = 0 days_of_week = [days_of_week] unless days_of_week.is_a?(Array) days_of_week.each do |day_of_week| bitfield |= day_of_week_name_to_constant(day_of_week) end bitfield end def months_from_bitfield(bitfield) months = [] scheduler_months.each do |month| if bitfield & month != 0 months << month_constant_to_number(month) end end months end def days_from_bitfield(bitfield) days = [] i = 0 while bitfield > 0 if bitfield & 1 > 0 # Day 32 has the special meaning of "the last day of the # month", no matter how many days there are in the month. days << (i == 31 ? 'last' : i + 1) end bitfield = bitfield >> 1 i += 1 end days end def days_of_week_from_bitfield(bitfield) days_of_week = [] scheduler_days_of_week.each do |day_of_week| if bitfield & day_of_week != 0 days_of_week << day_of_week_constant_to_name(day_of_week) end end days_of_week end def scheduler_trigger_types [ Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE ] end def scheduler_days_of_week [ Win32::TaskScheduler::SUNDAY, Win32::TaskScheduler::MONDAY, Win32::TaskScheduler::TUESDAY, Win32::TaskScheduler::WEDNESDAY, Win32::TaskScheduler::THURSDAY, Win32::TaskScheduler::FRIDAY, Win32::TaskScheduler::SATURDAY ] end def scheduler_months [ Win32::TaskScheduler::JANUARY, Win32::TaskScheduler::FEBRUARY, Win32::TaskScheduler::MARCH, Win32::TaskScheduler::APRIL, Win32::TaskScheduler::MAY, Win32::TaskScheduler::JUNE, Win32::TaskScheduler::JULY, Win32::TaskScheduler::AUGUST, Win32::TaskScheduler::SEPTEMBER, Win32::TaskScheduler::OCTOBER, Win32::TaskScheduler::NOVEMBER, Win32::TaskScheduler::DECEMBER ] end def scheduler_occurrences [ Win32::TaskScheduler::FIRST_WEEK, Win32::TaskScheduler::SECOND_WEEK, Win32::TaskScheduler::THIRD_WEEK, Win32::TaskScheduler::FOURTH_WEEK, Win32::TaskScheduler::LAST_WEEK ] end def day_of_week_constant_to_name(constant) case constant when Win32::TaskScheduler::SUNDAY; 'sun' when Win32::TaskScheduler::MONDAY; 'mon' when Win32::TaskScheduler::TUESDAY; 'tues' when Win32::TaskScheduler::WEDNESDAY; 'wed' when Win32::TaskScheduler::THURSDAY; 'thurs' when Win32::TaskScheduler::FRIDAY; 'fri' when Win32::TaskScheduler::SATURDAY; 'sat' end end def day_of_week_name_to_constant(name) case name when 'sun'; Win32::TaskScheduler::SUNDAY when 'mon'; Win32::TaskScheduler::MONDAY when 'tues'; Win32::TaskScheduler::TUESDAY when 'wed'; Win32::TaskScheduler::WEDNESDAY when 'thurs'; Win32::TaskScheduler::THURSDAY when 'fri'; Win32::TaskScheduler::FRIDAY when 'sat'; Win32::TaskScheduler::SATURDAY end end def month_constant_to_number(constant) month_num = 1 while constant >> month_num - 1 > 1 month_num += 1 end month_num end def occurrence_constant_to_name(constant) case constant when Win32::TaskScheduler::FIRST_WEEK; 'first' when Win32::TaskScheduler::SECOND_WEEK; 'second' when Win32::TaskScheduler::THIRD_WEEK; 'third' when Win32::TaskScheduler::FOURTH_WEEK; 'fourth' when Win32::TaskScheduler::LAST_WEEK; 'last' end end def occurrence_name_to_constant(name) case name when 'first'; Win32::TaskScheduler::FIRST_WEEK when 'second'; Win32::TaskScheduler::SECOND_WEEK when 'third'; Win32::TaskScheduler::THIRD_WEEK when 'fourth'; Win32::TaskScheduler::FOURTH_WEEK when 'last'; Win32::TaskScheduler::LAST_WEEK end end end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index d5adaf455..66752bd7a 100644 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -1,188 +1,188 @@ require 'etc' require 'facter' require 'puppet/property/keyvalue' require 'puppet/parameter/boolean' module Puppet newtype(:group) do @doc = "Manage groups. On most platforms this can only create groups. Group membership must be managed on individual users. On some platforms such as OS X, group membership is managed as an attribute of the group, not the user record. Providers must have the feature 'manages_members' to manage the 'members' property of a group record." feature :manages_members, "For directories where membership is an attribute of groups not users." feature :manages_aix_lam, "The provider can manage AIX Loadable Authentication Module (LAM) system." feature :system_groups, "The provider allows you to create system groups with lower GIDs." feature :libuser, "Allows local groups to be managed on systems that also use some other remote NSS method of managing accounts." ensurable do desc "Create or remove the group." newvalue(:present) do provider.create end newvalue(:absent) do provider.delete end end newproperty(:gid) do desc "The group ID. Must be specified numerically. If no group ID is specified when creating a new group, then one will be chosen automatically according to local system standards. This will likely result in the same group having different GIDs on different systems, which is not recommended. On Windows, this property is read-only and will return the group's security identifier (SID)." def retrieve provider.gid end def sync if self.should == :absent raise Puppet::DevError, "GID cannot be deleted" else provider.gid = self.should end end munge do |gid| case gid when String if gid =~ /^[-0-9]+$/ gid = Integer(gid) else self.fail "Invalid GID #{gid}" end when Symbol unless gid == :absent self.devfail "Invalid GID #{gid}" end end return gid end end newproperty(:members, :array_matching => :all, :required_features => :manages_members) do desc "The members of the group. For directory services where group membership is stored in the group objects, not the users." def change_to_s(currentvalue, newvalue) currentvalue = currentvalue.join(",") if currentvalue != :absent newvalue = newvalue.join(",") super(currentvalue, newvalue) end def insync?(current) if provider.respond_to?(:members_insync?) return provider.members_insync?(current, @should) end super(current) end def is_to_s(currentvalue) if provider.respond_to?(:members_to_s) currentvalue = '' if currentvalue.nil? return provider.members_to_s(currentvalue.split(',')) end super(currentvalue) end alias :should_to_s :is_to_s end - newparam(:auth_membership) do + newparam(:auth_membership, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "whether the provider is authoritative for group membership." defaultto true end newparam(:name) do desc "The group name. While naming limitations vary by operating system, it is advisable to restrict names to the lowest common denominator, which is a maximum of 8 characters beginning with a letter. Note that Puppet considers group names to be case-sensitive, regardless of the platform's own rules; be sure to always use the same case when referring to a given group." isnamevar end newparam(:allowdupe, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to allow duplicate GIDs. Defaults to `false`." defaultto false end newparam(:ia_load_module, :required_features => :manages_aix_lam) do desc "The name of the I&A module to use to manage this user" end newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do desc "Specify group AIX attributes in an array of `key=value` pairs." def membership :attribute_membership end def delimiter " " end validate do |value| raise ArgumentError, "Attributes value pairs must be separated by an =" unless value.include?("=") end end newparam(:attribute_membership) do desc "Whether specified attribute value pairs should be treated as the only attributes of the user or whether they should merely be treated as the minimum list." newvalues(:inclusive, :minimum) defaultto :minimum end newparam(:system, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether the group is a system group with lower GID." defaultto false end newparam(:forcelocal, :boolean => true, :required_features => :libuser, :parent => Puppet::Parameter::Boolean) do desc "Forces the management of local accounts when accounts are also being managed by some other NSS" defaultto false end # This method has been exposed for puppet to manage users and groups of # files in its settings and should not be considered available outside of # puppet. # # (see Puppet::Settings#service_group_available?) # # @return [Boolean] if the group exists on the system # @api private def exists? provider.exists? end end end diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb index 05f0b3fad..ea2ccee18 100644 --- a/lib/puppet/util/log.rb +++ b/lib/puppet/util/log.rb @@ -1,343 +1,354 @@ require 'puppet/util/tagging' require 'puppet/util/classgen' require 'puppet/network/format_support' # Pass feedback to the user. Log levels are modeled after syslog's, and it is # expected that that will be the most common log destination. Supports # multiple destinations, one of which is a remote server. class Puppet::Util::Log include Puppet::Util extend Puppet::Util::ClassGen include Puppet::Util::Tagging include Puppet::Network::FormatSupport @levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit] @loglevel = 2 @desttypes = {} # Create a new destination type. def self.newdesttype(name, options = {}, &block) dest = genclass( name, :parent => Puppet::Util::Log::Destination, :prefix => "Dest", :block => block, :hash => @desttypes, :attributes => options ) dest.match(dest.name) dest end require 'puppet/util/log/destination' require 'puppet/util/log/destinations' @destinations = {} @queued = [] class << self include Puppet::Util include Puppet::Util::ClassGen attr_reader :desttypes end # Reset log to basics. Basically just flushes and closes files and # undefs other objects. def Log.close(destination) if @destinations.include?(destination) @destinations[destination].flush if @destinations[destination].respond_to?(:flush) @destinations[destination].close if @destinations[destination].respond_to?(:close) @destinations.delete(destination) end end def self.close_all destinations.keys.each { |dest| close(dest) } raise Puppet::DevError.new("Log.close_all failed to close #{@destinations.keys.inspect}") if !@destinations.empty? end # Flush any log destinations that support such operations. def Log.flush @destinations.each { |type, dest| dest.flush if dest.respond_to?(:flush) } end def Log.autoflush=(v) @destinations.each do |type, dest| dest.autoflush = v if dest.respond_to?(:autoflush=) end end # Create a new log message. The primary role of this method is to # avoid creating log messages below the loglevel. def Log.create(hash) raise Puppet::DevError, "Logs require a level" unless hash.include?(:level) raise Puppet::DevError, "Invalid log level #{hash[:level]}" unless @levels.index(hash[:level]) @levels.index(hash[:level]) >= @loglevel ? Puppet::Util::Log.new(hash) : nil end def Log.destinations @destinations end # Yield each valid level in turn def Log.eachlevel @levels.each { |level| yield level } end # Return the current log level. def Log.level @levels[@loglevel] end # Set the current log level. def Log.level=(level) level = level.intern unless level.is_a?(Symbol) raise Puppet::DevError, "Invalid loglevel #{level}" unless @levels.include?(level) @loglevel = @levels.index(level) end def Log.levels @levels.dup end # Create a new log destination. def Log.newdestination(dest) # Each destination can only occur once. if @destinations.find { |name, obj| obj.name == dest } return end name, type = @desttypes.find do |name, klass| klass.match?(dest) end if type.respond_to?(:suitable?) and not type.suitable?(dest) return end raise Puppet::DevError, "Unknown destination type #{dest}" unless type begin if type.instance_method(:initialize).arity == 1 @destinations[dest] = type.new(dest) else @destinations[dest] = type.new end flushqueue @destinations[dest] rescue => detail Puppet.log_exception(detail) # If this was our only destination, then add the console back in. newdestination(:console) if @destinations.empty? and (dest != :console and dest != "console") end end def Log.with_destination(destination, &block) if @destinations.include?(destination) yield else newdestination(destination) begin yield ensure close(destination) end end end # Route the actual message. FIXME There are lots of things this method # should do, like caching and a bit more. It's worth noting that there's # a potential for a loop here, if the machine somehow gets the destination set as # itself. def Log.newmessage(msg) return if @levels.index(msg.level) < @loglevel queuemessage(msg) if @destinations.length == 0 @destinations.each do |name, dest| dest.handle(msg) end end def Log.queuemessage(msg) @queued.push(msg) end def Log.flushqueue return unless @destinations.size >= 1 @queued.each do |msg| Log.newmessage(msg) end @queued.clear end # Flush the logging queue. If there are no destinations available, # adds in a console logger before flushing the queue. # This is mainly intended to be used as a last-resort attempt # to ensure that logging messages are not thrown away before # the program is about to exit--most likely in a horrific # error scenario. # @return nil def Log.force_flushqueue() if (@destinations.empty? and !(@queued.empty?)) newdestination(:console) end flushqueue end def Log.sendlevel?(level) @levels.index(level) >= @loglevel end # Reopen all of our logs. def Log.reopen Puppet.notice "Reopening log files" types = @destinations.keys @destinations.each { |type, dest| dest.close if dest.respond_to?(:close) } @destinations.clear # We need to make sure we always end up with some kind of destination begin types.each { |type| Log.newdestination(type) } rescue => detail if @destinations.empty? Log.setup_default Puppet.err detail.to_s end end end def self.setup_default Log.newdestination( (Puppet.features.syslog? ? :syslog : (Puppet.features.eventlog? ? :eventlog : Puppet[:puppetdlog]))) end # Is the passed level a valid log level? def self.validlevel?(level) @levels.include?(level) end def self.from_data_hash(data) obj = allocate obj.initialize_from_hash(data) obj end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end - attr_accessor :time, :remote, :file, :line, :source + attr_accessor :time, :remote, :file, :line, :pos, :source, :issue_code, :environment, :node attr_reader :level, :message def initialize(args) self.level = args[:level] self.message = args[:message] self.source = args[:source] || "Puppet" @time = Time.now if tags = args[:tags] tags.each { |t| self.tag(t) } end - [:file, :line].each do |attr| + # Don't add these unless defined (preserve 3.x API as much as possible) + [:file, :line, :pos, :issue_code, :environment, :node].each do |attr| next unless value = args[attr] - send(attr.to_s + "=", value) + send(attr.to_s + '=', value) end Log.newmessage(self) end def initialize_from_hash(data) @level = data['level'].intern @message = data['message'] @source = data['source'] @tags = Puppet::Util::TagSet.new(data['tags']) @time = data['time'] if @time.is_a? String @time = Time.parse(@time) end - @file = data['file'] if data['file'] - @line = data['line'] if data['line'] + # Don't add these unless defined (preserve 3.x API as much as possible) + %w(file line pos issue_code environment node).each do |name| + next unless value = data[name] + send(name + '=', value) + end end def to_hash self.to_data_hash end def to_data_hash - { + hash = { 'level' => @level, 'message' => @message, 'source' => @source, - 'tags' => @tags, + 'tags' => @tags.to_a, 'time' => @time.iso8601(9), 'file' => @file, 'line' => @line, } + # Don't add these unless defined (preserve 3.x API as much as possible) + # TODO: Should apply to file and line also but making them optional in the hash would change 3x API + %w(pos issue_code environment node).each do |name| + attr_name = "@#{name}" + hash[name] = instance_variable_get(attr_name) if instance_variable_defined?(attr_name) + end + hash end def to_pson(*args) to_data_hash.to_pson(*args) end def message=(msg) raise ArgumentError, "Puppet::Util::Log requires a message" unless msg @message = msg.to_s end def level=(level) raise ArgumentError, "Puppet::Util::Log requires a log level" unless level raise ArgumentError, "Puppet::Util::Log requires a symbol or string" unless level.respond_to? "to_sym" @level = level.to_sym raise ArgumentError, "Invalid log level #{@level}" unless self.class.validlevel?(@level) # Tag myself with my log level tag(level) end # If they pass a source in to us, we make sure it is a string, and # we retrieve any tags we can. def source=(source) if source.respond_to?(:path) @source = source.path source.tags.each { |t| tag(t) } self.file = source.file self.line = source.line else @source = source.to_s end end def to_report "#{time} #{source} (#{level}): #{to_s}" end def to_s message end end # This is for backward compatibility from when we changed the constant to Puppet::Util::Log # because the reports include the constant name. Apparently the alias was created in # March 2007, should could probably be removed soon. Puppet::Log = Puppet::Util::Log diff --git a/lib/puppet/util/log/destinations.rb b/lib/puppet/util/log/destinations.rb index 6c5cc7f23..ef69df1d2 100644 --- a/lib/puppet/util/log/destinations.rb +++ b/lib/puppet/util/log/destinations.rb @@ -1,232 +1,253 @@ Puppet::Util::Log.newdesttype :syslog do def self.suitable?(obj) Puppet.features.syslog? end def close Syslog.close end def initialize Syslog.close if Syslog.opened? name = "puppet-#{Puppet.run_mode.name}" options = Syslog::LOG_PID | Syslog::LOG_NDELAY # XXX This should really be configurable. str = Puppet[:syslogfacility] begin facility = Syslog.const_get("LOG_#{str.upcase}") rescue NameError raise Puppet::Error, "Invalid syslog facility #{str}", $!.backtrace end @syslog = Syslog.open(name, options, facility) end def handle(msg) # XXX Syslog currently has a bug that makes it so you # cannot log a message with a '%' in it. So, we get rid # of them. if msg.source == "Puppet" msg.to_s.split("\n").each do |line| @syslog.send(msg.level, line.gsub("%", '%%')) end else msg.to_s.split("\n").each do |line| @syslog.send(msg.level, "(%s) %s" % [msg.source.to_s.gsub("%", ""), line.gsub("%", '%%') ] ) end end end end Puppet::Util::Log.newdesttype :file do require 'fileutils' def self.match?(obj) Puppet::Util.absolute_path?(obj) end def close if defined?(@file) @file.close @file = nil end end def flush @file.flush if defined?(@file) end attr_accessor :autoflush def initialize(path) @name = path + @json = path.end_with?('.json') ? 1 : 0 + # first make sure the directory exists # We can't just use 'Config.use' here, because they've # specified a "special" destination. unless Puppet::FileSystem.exist?(Puppet::FileSystem.dir(path)) FileUtils.mkdir_p(File.dirname(path), :mode => 0755) Puppet.info "Creating log directory #{File.dirname(path)}" end # create the log file, if it doesn't already exist - file = File.open(path, File::WRONLY|File::CREAT|File::APPEND) + need_array_start = false + if @json == 1 + need_array_start = true + if File.exists?(path) + sz = File.size(path) + need_array_start = sz == 0 + + # Assume that entries have been written and that a comma + # is needed before next entry + @json = 2 if sz > 2 + end + end + + file = File.open(path, File::WRONLY|File::CREAT|File::APPEND) + file.puts('[') if need_array_start # Give ownership to the user and group puppet will run as begin FileUtils.chown(Puppet[:user], Puppet[:group], path) unless Puppet::Util::Platform.windows? rescue ArgumentError, Errno::EPERM Puppet.err "Unable to set ownership of log file" end @file = file @autoflush = Puppet[:autoflush] end def handle(msg) - @file.puts("#{msg.time} #{msg.source} (#{msg.level}): #{msg}") + if @json > 0 + @json > 1 ? @file.puts(',') : @json = 2 + JSON.dump(msg.to_hash, @file) + else + @file.puts("#{msg.time} #{msg.source} (#{msg.level}): #{msg}") + end @file.flush if @autoflush end end Puppet::Util::Log.newdesttype :logstash_event do require 'time' def format(msg) # logstash_event format is documented at # https://logstash.jira.com/browse/LOGSTASH-675 data = {} data = msg.to_hash data['version'] = 1 data['@timestamp'] = data['time'] data.delete('time') data end def handle(msg) message = format(msg) $stdout.puts message.to_pson end end Puppet::Util::Log.newdesttype :console do require 'puppet/util/colors' include Puppet::Util::Colors def initialize # Flush output immediately. $stderr.sync = true $stdout.sync = true end def handle(msg) levels = { :emerg => { :name => 'Emergency', :color => :hred, :stream => $stderr }, :alert => { :name => 'Alert', :color => :hred, :stream => $stderr }, :crit => { :name => 'Critical', :color => :hred, :stream => $stderr }, :err => { :name => 'Error', :color => :hred, :stream => $stderr }, :warning => { :name => 'Warning', :color => :hred, :stream => $stderr }, :notice => { :name => 'Notice', :color => :reset, :stream => $stdout }, :info => { :name => 'Info', :color => :green, :stream => $stdout }, :debug => { :name => 'Debug', :color => :cyan, :stream => $stdout }, } str = msg.respond_to?(:multiline) ? msg.multiline : msg.to_s str = msg.source == "Puppet" ? str : "#{msg.source}: #{str}" level = levels[msg.level] level[:stream].puts colorize(level[:color], "#{level[:name]}: #{str}") end end # Log to a transaction report. Puppet::Util::Log.newdesttype :report do attr_reader :report match "Puppet::Transaction::Report" def initialize(report) @report = report end def handle(msg) @report << msg end end # Log to an array, just for testing. module Puppet::Test class LogCollector def initialize(logs) @logs = logs end def <<(value) @logs << value end end end Puppet::Util::Log.newdesttype :array do match "Puppet::Test::LogCollector" def initialize(messages) @messages = messages end def handle(msg) @messages << msg end end Puppet::Util::Log.newdesttype :eventlog do Puppet::Util::Log::DestEventlog::EVENTLOG_ERROR_TYPE = 0x0001 Puppet::Util::Log::DestEventlog::EVENTLOG_WARNING_TYPE = 0x0002 Puppet::Util::Log::DestEventlog::EVENTLOG_INFORMATION_TYPE = 0x0004 def self.suitable?(obj) Puppet.features.eventlog? end def initialize @eventlog = Win32::EventLog.open("Application") end def to_native(level) case level when :debug,:info,:notice [self.class::EVENTLOG_INFORMATION_TYPE, 0x01] when :warning [self.class::EVENTLOG_WARNING_TYPE, 0x02] when :err,:alert,:emerg,:crit [self.class::EVENTLOG_ERROR_TYPE, 0x03] end end def handle(msg) native_type, native_id = to_native(msg.level) @eventlog.report_event( :source => "Puppet", :event_type => native_type, :event_id => native_id, :data => (msg.source and msg.source != 'Puppet' ? "#{msg.source}: " : '') + msg.to_s ) end def close if @eventlog @eventlog.close @eventlog = nil end end end diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb index 84a35ddc2..8ad25e495 100644 --- a/lib/puppet/util/logging.rb +++ b/lib/puppet/util/logging.rb @@ -1,181 +1,217 @@ # A module to make logging a bit easier. require 'puppet/util/log' require 'puppet/error' module Puppet::Util::Logging def send_log(level, message) Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata)) end # Create a method for each log level. Puppet::Util::Log.eachlevel do |level| define_method(level) do |args| args = args.join(" ") if args.is_a?(Array) send_log(level, args) end end # Log an exception via Puppet.err. Will also log the backtrace if Puppet[:trace] is set. # Parameters: # [exception] an Exception to log # [message] an optional String overriding the message to be logged; by default, we log Exception.message. # If you pass a String here, your string will be logged instead. You may also pass nil if you don't # wish to log a message at all; in this case it is likely that you are only calling this method in order # to take advantage of the backtrace logging. def log_exception(exception, message = :default, options = {}) - err(format_exception(exception, message, Puppet[:trace] || options[:trace])) + trace = Puppet[:trace] || options[:trace] + if message == :default && exception.is_a?(Puppet::ParseErrorWithIssue) + # Retain all detailed info and keep plain message and stacktrace separate + backtrace = [] + build_exception_trace(backtrace, exception, trace) + Puppet::Util::Log.create({ + :level => :err, + :source => log_source, + :message => exception.basic_message, + :issue_code => exception.issue_code, + :backtrace => backtrace.empty? ? nil : backtrace, + :file => exception.file, + :line => exception.line, + :pos => exception.pos, + :environment => exception.environment, + :node => exception.node + }.merge(log_metadata)) + else + err(format_exception(exception, message, trace)) + end + end + + def build_exception_trace(arr, exception, trace = true) + if trace and exception.backtrace + exception.backtrace.each do |line| + arr << line =~ /^(.+):(\d+.*)$/ ? ("#{Pathname($1).realpath}:#{$2}" rescue line) : line + end + end + if exception.respond_to?(:original) + original = exception.original + unless original.nil? + arr << 'Wrapped exception:' + arr << original.message + build_exception_trace(arr, original, trace) + end + end end + private :build_exception_trace def format_exception(exception, message = :default, trace = true) arr = [] case message when :default arr << exception.message when nil # don't log anything if they passed a nil; they are just calling for the optional backtrace logging else arr << message end if trace and exception.backtrace arr << Puppet::Util.pretty_backtrace(exception.backtrace) end if exception.respond_to?(:original) and exception.original arr << "Wrapped exception:" arr << format_exception(exception.original, :default, trace) end arr.flatten.join("\n") end def log_and_raise(exception, message) log_exception(exception, message) raise exception, message + "\n" + exception.to_s, exception.backtrace end class DeprecationWarning < Exception; end # Logs a warning indicating that the Ruby code path is deprecated. Note that # this method keeps track of the offending lines of code that triggered the # deprecation warning, and will only log a warning once per offending line of # code. It will also stop logging deprecation warnings altogether after 100 # unique deprecation warnings have been logged. Finally, if # Puppet[:disable_warnings] includes 'deprecations', it will squelch all # warning calls made via this method. # # @param message [String] The message to log (logs via warning) # @param key [String] Optional key to mark the message as unique. If not # passed in, the originating call line will be used instead. def deprecation_warning(message, key = nil) issue_deprecation_warning(message, key, nil, nil, true) end # Logs a warning whose origin comes from Puppet source rather than somewhere # internal within Puppet. Otherwise the same as deprecation_warning() # # @param message [String] The message to log (logs via warning) # @param options [Hash] # @option options [String] :file File we are warning from # @option options [Integer] :line Line number we are warning from # @option options [String] :key (:file + :line) Alternative key used to mark # warning as unique # # Either :file and :line and/or :key must be passed. def puppet_deprecation_warning(message, options = {}) key = options[:key] file = options[:file] line = options[:line] raise(Puppet::DevError, "Need either :file and :line, or :key") if (key.nil?) && (file.nil? || line.nil?) key ||= "#{file}:#{line}" issue_deprecation_warning(message, key, file, line, false) end def get_deprecation_offender() # we have to put this in its own method to simplify testing; we need to be able to mock the offender results in # order to test this class, and our framework does not appear to enjoy it if you try to mock Kernel.caller # # let's find the offending line; we need to jump back up the stack a few steps to find the method that called # the deprecated method if Puppet[:trace] caller()[2..-1] else [caller()[2]] end end def clear_deprecation_warnings $deprecation_warnings.clear if $deprecation_warnings end # TODO: determine whether there might be a potential use for adding a puppet configuration option that would # enable this deprecation logging. # utility method that can be called, e.g., from spec_helper config.after, when tracking down calls to deprecated # code. # Parameters: # [deprecations_file] relative or absolute path of a file to log the deprecations to # [pattern] (default nil) if specified, will only log deprecations whose message matches the provided pattern def log_deprecations_to_file(deprecations_file, pattern = nil) # this method may get called lots and lots of times (e.g., from spec_helper config.after) without the global # list of deprecation warnings being cleared out. We don't want to keep logging the same offenders over and over, # so, we need to keep track of what we've logged. # # It'd be nice if we could just clear out the list of deprecation warnings, but then the very next spec might # find the same offender, and we'd end up logging it again. $logged_deprecation_warnings ||= {} File.open(deprecations_file, "a") do |f| if ($deprecation_warnings) then $deprecation_warnings.each do |offender, message| if (! $logged_deprecation_warnings.has_key?(offender)) then $logged_deprecation_warnings[offender] = true if ((pattern.nil?) || (message =~ pattern)) then f.puts(message) f.puts(offender) f.puts() end end end end end end private def issue_deprecation_warning(message, key, file, line, use_caller) return if Puppet[:disable_warnings].include?('deprecations') $deprecation_warnings ||= {} if $deprecation_warnings.length < 100 then key ||= (offender = get_deprecation_offender) if (! $deprecation_warnings.has_key?(key)) then $deprecation_warnings[key] = message call_trace = use_caller ? (offender || get_deprecation_offender).join('; ') : "#{file || 'unknown'}:#{line || 'unknown'}" warning("#{message}\n (at #{call_trace})") end end end def is_resource? defined?(Puppet::Type) && is_a?(Puppet::Type) end def is_resource_parameter? defined?(Puppet::Parameter) && is_a?(Puppet::Parameter) end def log_metadata [:file, :line, :tags].inject({}) do |result, attr| result[attr] = send(attr) if respond_to?(attr) result end end def log_source # We need to guard the existence of the constants, since this module is used by the base Puppet module. (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s to_s end end diff --git a/lib/puppet/util/windows/adsi.rb b/lib/puppet/util/windows/adsi.rb index 041be7765..d16bd23e2 100644 --- a/lib/puppet/util/windows/adsi.rb +++ b/lib/puppet/util/windows/adsi.rb @@ -1,430 +1,431 @@ module Puppet::Util::Windows::ADSI require 'ffi' class << self extend FFI::Library def connectable?(uri) begin !! connect(uri) rescue false end end def connect(uri) begin WIN32OLE.connect(uri) rescue Exception => e raise Puppet::Error.new( "ADSI connection error: #{e}", e ) end end def create(name, resource_type) Puppet::Util::Windows::ADSI.connect(computer_uri).Create(resource_type, name) end def delete(name, resource_type) Puppet::Util::Windows::ADSI.connect(computer_uri).Delete(resource_type, name) end # taken from winbase.h MAX_COMPUTERNAME_LENGTH = 31 def computer_name unless @computer_name max_length = MAX_COMPUTERNAME_LENGTH + 1 # NULL terminated FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string FFI::MemoryPointer.new(:dword, 1) do |buffer_size| buffer_size.write_dword(max_length) # length in TCHARs if GetComputerNameW(buffer, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to get computer name") end @computer_name = buffer.read_wide_string(buffer_size.read_dword) end end end @computer_name end def computer_uri(host = '.') "WinNT://#{host}" end def wmi_resource_uri( host = '.' ) "winmgmts:{impersonationLevel=impersonate}!//#{host}/root/cimv2" end # @api private def sid_uri_safe(sid) return sid_uri(sid) if sid.kind_of?(Win32::Security::SID) begin sid = Win32::Security::SID.new(Win32::Security::SID.string_to_sid(sid)) sid_uri(sid) rescue SystemCallError nil end end def sid_uri(sid) raise Puppet::Error.new( "Must use a valid SID object" ) if !sid.kind_of?(Win32::Security::SID) "WinNT://#{sid.to_s}" end def uri(resource_name, resource_type, host = '.') "#{computer_uri(host)}/#{resource_name},#{resource_type}" end def wmi_connection connect(wmi_resource_uri) end def execquery(query) wmi_connection.execquery(query) end def sid_for_account(name) Puppet.deprecation_warning "Puppet::Util::Windows::ADSI.sid_for_account is deprecated and will be removed in 3.0, use Puppet::Util::Windows::SID.name_to_sid instead." Puppet::Util::Windows::SID.name_to_sid(name) end ffi_convention :stdcall # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724295(v=vs.85).aspx # BOOL WINAPI GetComputerName( # _Out_ LPTSTR lpBuffer, # _Inout_ LPDWORD lpnSize # ); ffi_lib :kernel32 attach_function_private :GetComputerNameW, [:lpwstr, :lpdword], :win32_bool end class User extend Enumerable extend FFI::Library attr_accessor :native_user attr_reader :name, :sid def initialize(name, native_user = nil) @name = name @native_user = native_user end def self.parse_name(name) if name =~ /\// raise Puppet::Error.new( "Value must be in DOMAIN\\user style syntax" ) end matches = name.scan(/((.*)\\)?(.*)/) domain = matches[0][1] || '.' account = matches[0][2] return account, domain end def native_user @native_user ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(@name))) end def sid @sid ||= Puppet::Util::Windows::SID.octet_string_to_sid_object(native_user.objectSID) end def self.uri(name, host = '.') if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end host = '.' if ['NT AUTHORITY', 'BUILTIN', Socket.gethostname].include?(host) Puppet::Util::Windows::ADSI.uri(name, 'user', host) end def uri self.class.uri(sid.account, sid.domain) end def self.logon(name, password) Puppet::Util::Windows::User.password_is?(name, password) end def [](attribute) native_user.Get(attribute) end def []=(attribute, value) native_user.Put(attribute, value) end def commit begin native_user.SetInfo unless native_user.nil? rescue Exception => e raise Puppet::Error.new( "User update failed: #{e}", e ) end self end def password_is?(password) self.class.logon(name, password) end def add_flag(flag_name, value) flag = native_user.Get(flag_name) rescue 0 native_user.Put(flag_name, flag | value) commit end def password=(password) native_user.SetPassword(password) commit fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 add_flag("UserFlags", fADS_UF_DONT_EXPIRE_PASSWD) end def groups # WIN32OLE objects aren't enumerable, so no map groups = [] native_user.Groups.each {|g| groups << g.Name} rescue nil groups end def add_to_groups(*group_names) group_names.each do |group_name| Puppet::Util::Windows::ADSI::Group.new(group_name).add_member_sids(sid) end end alias add_to_group add_to_groups def remove_from_groups(*group_names) group_names.each do |group_name| Puppet::Util::Windows::ADSI::Group.new(group_name).remove_member_sids(sid) end end alias remove_from_group remove_from_groups def set_groups(desired_groups, minimum = true) return if desired_groups.nil? or desired_groups.empty? desired_groups = desired_groups.split(',').map(&:strip) current_groups = self.groups # First we add the user to all the groups it should be in but isn't groups_to_add = desired_groups - current_groups add_to_groups(*groups_to_add) # Then we remove the user from all groups it is in but shouldn't be, if # that's been requested groups_to_remove = current_groups - desired_groups remove_from_groups(*groups_to_remove) unless minimum end def self.create(name) # Windows error 1379: The specified local group already exists. raise Puppet::Error.new( "Cannot create user if group '#{name}' exists." ) if Puppet::Util::Windows::ADSI::Group.exists? name new(name, Puppet::Util::Windows::ADSI.create(name, 'user')) end # UNLEN from lmcons.h - http://stackoverflow.com/a/2155176 MAX_USERNAME_LENGTH = 256 def self.current_user_name user_name = '' max_length = MAX_USERNAME_LENGTH + 1 # NULL terminated FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string FFI::MemoryPointer.new(:dword, 1) do |buffer_size| buffer_size.write_dword(max_length) # length in TCHARs if GetUserNameW(buffer, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to get user name") end # buffer_size includes trailing NULL user_name = buffer.read_wide_string(buffer_size.read_dword - 1) end end user_name end def self.exists?(name) Puppet::Util::Windows::ADSI::connectable?(User.uri(*User.parse_name(name))) end def self.delete(name) Puppet::Util::Windows::ADSI.delete(name, 'user') end def self.each(&block) wql = Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"') users = [] wql.each do |u| users << new(u.name) end users.each(&block) end ffi_convention :stdcall # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724432(v=vs.85).aspx # BOOL WINAPI GetUserName( # _Out_ LPTSTR lpBuffer, # _Inout_ LPDWORD lpnSize # ); ffi_lib :advapi32 attach_function_private :GetUserNameW, [:lpwstr, :lpdword], :win32_bool end class UserProfile def self.delete(sid) begin Puppet::Util::Windows::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'") rescue => e # http://social.technet.microsoft.com/Forums/en/ITCG/thread/0f190051-ac96-4bf1-a47f-6b864bfacee5 # Prior to Vista SP1, there's no builtin way to programmatically # delete user profiles (except for delprof.exe). So try to delete # but warn if we fail raise e unless e.message.include?('80041010') Puppet.warning "Cannot delete user profile for '#{sid}' prior to Vista SP1" end end end class Group extend Enumerable attr_accessor :native_group attr_reader :name, :sid def initialize(name, native_group = nil) @name = name @native_group = native_group end def uri self.class.uri(name) end def self.uri(name, host = '.') if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end Puppet::Util::Windows::ADSI.uri(name, 'group', host) end def native_group @native_group ||= Puppet::Util::Windows::ADSI.connect(uri) end def sid @sid ||= Puppet::Util::Windows::SID.octet_string_to_sid_object(native_group.objectSID) end def commit begin native_group.SetInfo unless native_group.nil? rescue Exception => e raise Puppet::Error.new( "Group update failed: #{e}", e ) end self end def self.name_sid_hash(names) return [] if names.nil? or names.empty? sids = names.map do |name| sid = Puppet::Util::Windows::SID.name_to_sid_object(name) raise Puppet::Error.new( "Could not resolve username: #{name}" ) if !sid [sid.to_s, sid] end Hash[ sids ] end def add_members(*names) Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#add_member_sids') sids = self.class.name_sid_hash(names) add_member_sids(*sids.values) end alias add_member add_members def remove_members(*names) Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#remove_member_sids') sids = self.class.name_sid_hash(names) remove_member_sids(*sids.values) end alias remove_member remove_members def add_member_sids(*sids) sids.each do |sid| native_group.Add(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def remove_member_sids(*sids) sids.each do |sid| native_group.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def members # WIN32OLE objects aren't enumerable, so no map members = [] native_group.Members.each {|m| members << m.Name} members end def member_sids sids = [] native_group.Members.each do |m| sids << Puppet::Util::Windows::SID.octet_string_to_sid_object(m.objectSID) end sids end - def set_members(desired_members) + def set_members(desired_members, inclusive = true) return if desired_members.nil? or desired_members.empty? current_hash = Hash[ self.member_sids.map { |sid| [sid.to_s, sid] } ] desired_hash = self.class.name_sid_hash(desired_members) # First we add all missing members members_to_add = (desired_hash.keys - current_hash.keys).map { |sid| desired_hash[sid] } add_member_sids(*members_to_add) # Then we remove all extra members members_to_remove = (current_hash.keys - desired_hash.keys).map { |sid| current_hash[sid] } - remove_member_sids(*members_to_remove) + + remove_member_sids(*members_to_remove) if inclusive end def self.create(name) # Windows error 2224: The account already exists. raise Puppet::Error.new( "Cannot create group if user '#{name}' exists." ) if Puppet::Util::Windows::ADSI::User.exists? name new(name, Puppet::Util::Windows::ADSI.create(name, 'group')) end def self.exists?(name) Puppet::Util::Windows::ADSI.connectable?(Group.uri(name)) end def self.delete(name) Puppet::Util::Windows::ADSI.delete(name, 'group') end def self.each(&block) wql = Puppet::Util::Windows::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' ) groups = [] wql.each do |g| groups << new(g.name) end groups.each(&block) end end end diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb index 142ede4a1..f379f4284 100644 --- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb +++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb @@ -1,1365 +1,1366 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope before(:each) do Puppet[:strict_variables] = true # These must be set since the 3x logic switches some behaviors on these even if the tests explicitly # use the 4x parser and evaluator. # Puppet[:parser] = 'future' # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppetx' # Tests needs a known configuration of node/scope/compiler since it parses and evaluates # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete # catalog for each tested expression. # @parser = Puppet::Pops::Parser::EvaluatingParser.new @node = Puppet::Node.new('node.example.com') @node.environment = Puppet::Node::Environment.create(:testing, []) @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') @scope.parent = @compiler.topscope end let(:parser) { @parser } let(:scope) { @scope } types = Puppet::Pops::Types::TypeFactory context "When evaluator evaluates literals" do { "1" => 1, "010" => 8, "0x10" => 16, "3.14" => 3.14, "0.314e1" => 3.14, "31.4e-1" => 3.14, "'1'" => '1', "'banana'" => 'banana', '"banana"' => 'banana', "banana" => 'banana', "banana::split" => 'banana::split', "false" => false, "true" => true, "Array" => types.array_of_data(), "/.*/" => /.*/ }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator evaluates Lists and Hashes" do { "[]" => [], "[1,2,3]" => [1,2,3], "[1,[2.0, 2.1, [2.2]],[3.0, 3.1]]" => [1,[2.0, 2.1, [2.2]],[3.0, 3.1]], "[2 + 2]" => [4], "[1,2,3] == [1,2,3]" => true, "[1,2,3] != [2,3,4]" => true, "[1,2,3] == [2,2,3]" => false, "[1,2,3] != [1,2,3]" => false, "[1,2,3][2]" => 3, "[1,2,3] + [4,5]" => [1,2,3,4,5], "[1,2,3] + [[4,5]]" => [1,2,3,[4,5]], "[1,2,3] + 4" => [1,2,3,4], "[1,2,3] << [4,5]" => [1,2,3,[4,5]], "[1,2,3] << {'a' => 1, 'b'=>2}" => [1,2,3,{'a' => 1, 'b'=>2}], "[1,2,3] << 4" => [1,2,3,4], "[1,2,3,4] - [2,3]" => [1,4], "[1,2,3,4] - [2,5]" => [1,3,4], "[1,2,3,4] - 2" => [1,3,4], "[1,2,3,[2],4] - 2" => [1,3,[2],4], "[1,2,3,[2,3],4] - [[2,3]]" => [1,2,3,4], "[1,2,3,3,2,4,2,3] - [2,3]" => [1,4], "[1,2,3,['a',1],['b',2]] - {'a' => 1, 'b'=>2}" => [1,2,3], "[1,2,3,{'a'=>1,'b'=>2}] - [{'a' => 1, 'b'=>2}]" => [1,2,3], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[1,2,3] + {'a' => 1, 'b'=>2}" => [1,2,3,['a',1],['b',2]], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do # This test must be done with match_array since the order of the hash # is undefined and Ruby 1.8.7 and 1.9.3 produce different results. expect(parser.evaluate_string(scope, source, __FILE__)).to match_array(result) end end { "[1,2,3][a]" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "{}" => {}, "{'a'=>1,'b'=>2}" => {'a'=>1,'b'=>2}, "{'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}" => {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}, "{'a'=> 2 + 2}" => {'a'=> 4}, "{'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} != {'x'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} == {'a'=> 2, 'b'=>3}" => false, "{'a'=> 1, 'b'=>2} != {'a'=> 1, 'b'=>2}" => false, "{a => 1, b => 2}[b]" => 2, "{2+2 => sum, b => 2}[4]" => 'sum', "{'a'=>1, 'b'=>2} + {'c'=>3}" => {'a'=>1,'b'=>2,'c'=>3}, "{'a'=>1, 'b'=>2} + {'b'=>3}" => {'a'=>1,'b'=>3}, "{'a'=>1, 'b'=>2} + ['c', 3, 'b', 3]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} + [['c', 3], ['b', 3]]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} - {'b' => 3}" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - ['b', 'c']" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - 'c'" => {'a'=>1, 'b'=>2}, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{'a' => 1, 'b'=>2} << 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When the evaluator perform comparisons" do { "'a' == 'a'" => true, "'a' == 'b'" => false, "'a' != 'a'" => false, "'a' != 'b'" => true, "'a' < 'b' " => true, "'a' < 'a' " => false, "'b' < 'a' " => false, "'a' <= 'b'" => true, "'a' <= 'a'" => true, "'b' <= 'a'" => false, "'a' > 'b' " => false, "'a' > 'a' " => false, "'b' > 'a' " => true, "'a' >= 'b'" => false, "'a' >= 'a'" => true, "'b' >= 'a'" => true, "'a' == 'A'" => true, "'a' != 'A'" => false, "'a' > 'A'" => false, "'a' >= 'A'" => true, "'A' < 'a'" => false, "'A' <= 'a'" => true, "1 == 1" => true, "1 == 2" => false, "1 != 1" => false, "1 != 2" => true, "1 < 2 " => true, "1 < 1 " => false, "2 < 1 " => false, "1 <= 2" => true, "1 <= 1" => true, "2 <= 1" => false, "1 > 2 " => false, "1 > 1 " => false, "2 > 1 " => true, "1 >= 2" => false, "1 >= 1" => true, "2 >= 1" => true, "1 == 1.0 " => true, "1 < 1.1 " => true, "1.0 == 1 " => true, "1.0 < 2 " => true, "'1.0' < 'a'" => true, "'1.0' < '' " => false, "'1.0' < ' '" => false, "'a' > '1.0'" => true, "/.*/ == /.*/ " => true, "/.*/ != /a.*/" => true, "true == true " => true, "false == false" => true, "true == false" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "a > 1" => /String > Integer/, "a >= 1" => /String >= Integer/, "a < 1" => /String < Integer/, "a <= 1" => /String <= Integer/, "1 > a" => /Integer > String/, "1 >= a" => /Integer >= String/, "1 < a" => /Integer < String/, "1 <= a" => /Integer <= String/, }.each do | source, error| it "should not allow comparison of String and Number '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error) end end { "'a' =~ /.*/" => true, "'a' =~ '.*'" => true, "/.*/ != /a.*/" => true, "'a' !~ /b.*/" => true, "'a' !~ 'b.*'" => true, '$x = a; a =~ "$x.*"' => true, "a =~ Pattern['a.*']" => true, "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957 "$x = /a.*/ a =~ $x" => true, "$x = Pattern['a.*'] a =~ $x" => true, "1 =~ Integer" => true, "1 !~ Integer" => false, "[1,2,3] =~ Array[Integer[1,10]]" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "666 =~ /6/" => :error, "[a] =~ /a/" => :error, "{a=>1} =~ /a/" => :error, "/a/ =~ /a/" => :error, "Array =~ /A/" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "1 in [1,2,3]" => true, "4 in [1,2,3]" => false, "a in {x=>1, a=>2}" => true, "z in {x=>1, a=>2}" => false, "ana in bananas" => true, "xxx in bananas" => false, "/ana/ in bananas" => true, "/xxx/ in bananas" => false, "ANA in bananas" => false, # ANA is a type, not a String "String[1] in bananas" => false, # Philosophically true though :-) "'ANA' in bananas" => true, "ana in 'BANANAS'" => true, "/ana/ in 'BANANAS'" => false, "/ANA/ in 'BANANAS'" => true, "xxx in 'BANANAS'" => false, "[2,3] in [1,[2,3],4]" => true, "[2,4] in [1,[2,3],4]" => false, "[a,b] in ['A',['A','B'],'C']" => true, "[x,y] in ['A',['A','B'],'C']" => false, "a in {a=>1}" => true, "x in {a=>1}" => false, "'A' in {a=>1}" => true, "'X' in {a=>1}" => false, "a in {'A'=>1}" => true, "x in {'A'=>1}" => false, "/xxx/ in {'aaaxxxbbb'=>1}" => true, "/yyy/ in {'aaaxxxbbb'=>1}" => false, "15 in [1, 0xf]" => true, "15 in [1, '0xf']" => false, "'15' in [1, 0xf]" => false, "15 in [1, 115]" => false, "1 in [11, '111']" => false, "'1' in [11, '111']" => false, "Array[Integer] in [2, 3]" => false, "Array[Integer] in [2, [3, 4]]" => true, "Array[Integer] in [2, [a, 4]]" => false, "Integer in { 2 =>'a'}" => true, "Integer[5,10] in [1,5,3]" => true, "Integer[5,10] in [1,2,3]" => false, "Integer in {'a'=>'a'}" => false, "Integer in {'a'=>1}" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "if /(ana)/ in bananas {$1}" => 'ana', "if /(xyz)/ in bananas {$1} else {$1}" => nil, "$a = bananas =~ /(ana)/; $b = /(xyz)/ in bananas; $1" => 'ana', "$a = xyz =~ /(xyz)/; $b = /(ana)/ in bananas; $1" => 'ana', "if /p/ in [pineapple, bananas] {$0}" => 'p', "if /b/ in [pineapple, bananas] {$0}" => 'b', }.each do |source, result| it "sets match variables for a regexp search using in such that '#{source}' produces '#{result}'" do parser.evaluate_string(scope, source, __FILE__).should == result end end { 'Any' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', 'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File', 'NotYetKnownResourceType'], # Note, Data > Collection is false (so not included) 'Data' => ['Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Array', 'Hash',], 'Scalar' => ['Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern'], 'Numeric' => ['Integer', 'Float'], 'CatalogEntry' => ['Class', 'Resource', 'File', 'NotYetKnownResourceType'], 'Integer[1,10]' => ['Integer[2,3]'], }.each do |general, specials| specials.each do |special | it "should compute that #{general} > #{special}" do parser.evaluate_string(scope, "#{general} > #{special}", __FILE__).should == true end it "should compute that #{special} < #{general}" do parser.evaluate_string(scope, "#{special} < #{general}", __FILE__).should == true end it "should compute that #{general} != #{special}" do parser.evaluate_string(scope, "#{special} != #{general}", __FILE__).should == true end end end { 'Integer[1,10] > Integer[2,3]' => true, 'Integer[1,10] == Integer[2,3]' => false, 'Integer[1,10] > Integer[0,5]' => false, 'Integer[1,10] > Integer[1,10]' => false, 'Integer[1,10] >= Integer[1,10]' => true, 'Integer[1,10] == Integer[1,10]' => true, }.each do |source, result| it "should parse and evaluate the integer range comparison expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator performs arithmetic" do context "on Integers" do { "2+2" => 4, "2 + 2" => 4, "7 - 3" => 4, "6 * 3" => 18, "6 / 3" => 2, "6 % 3" => 0, "10 % 3" => 1, "-(6/3)" => -2, "-6/3 " => -2, "8 >> 1" => 4, "8 << 1" => 16, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end context "on Floats" do { "2.2 + 2.2" => 4.4, "7.7 - 3.3" => 4.4, "6.1 * 3.1" => 18.91, "6.6 / 3.3" => 2.0, "-(6.0/3.0)" => -2.0, "-6.0/3.0 " => -2.0, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "3.14 << 2" => :error, "3.14 >> 2" => :error, "6.6 % 3.3" => 0.0, "10.0 % 3.0" => 1.0, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "on strings requiring boxing to Numeric" do { "'2' + '2'" => 4, "'-2' + '2'" => 0, "'- 2' + '2'" => 0, '"-\t 2" + "2"' => 0, "'+2' + '2'" => 4, "'+ 2' + '2'" => 4, "'2.2' + '2.2'" => 4.4, "'-2.2' + '2.2'" => 0.0, "'0xF7' + '010'" => 0xFF, "'0xF7' + '0x8'" => 0xFF, "'0367' + '010'" => 0xFF, "'012.3' + '010'" => 20.3, "'-0x2' + '0x4'" => 2, "'+0x2' + '0x4'" => 6, "'-02' + '04'" => 2, "'+02' + '04'" => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'0888' + '010'" => :error, "'0xWTF' + '010'" => :error, "'0x12.3' + '010'" => :error, "'0x12.3' + '010'" => :error, '"-\n 2" + "2"' => :error, '"-\v 2" + "2"' => :error, '"-2\n" + "2"' => :error, '"-2\n " + "2"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end end end # arithmetic context "When the evaluator evaluates assignment" do { "$a = 5" => 5, "$a = 5; $a" => 5, "$a = 5; $b = 6; $a" => 5, "$a = $b = 5; $a == $b" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[a,b,c] = [1,2,3]" => /attempt to assign to 'an Array Expression'/, "[a,b,c] = {b=>2,c=>3,a=>1}" => /attempt to assign to 'an Array Expression'/, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to error with #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError, result) end end end context "When the evaluator evaluates conditionals" do { "if true {5}" => 5, "if false {5}" => nil, "if false {2} else {5}" => 5, "if false {2} elsif true {5}" => 5, "if false {2} elsif false {5}" => nil, "unless false {5}" => 5, "unless true {5}" => nil, "unless true {2} else {5}" => 5, + "unless true {} else {5}" => 5, "$a = if true {5} $a" => 5, "$a = if false {5} $a" => nil, "$a = if false {2} else {5} $a" => 5, "$a = if false {2} elsif true {5} $a" => 5, "$a = if false {2} elsif false {5} $a" => nil, "$a = unless false {5} $a" => 5, "$a = unless true {5} $a" => nil, "$a = unless true {2} else {5} $a" => 5, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "case 1 { 1 : { yes } }" => 'yes', "case 2 { 1,2,3 : { yes} }" => 'yes', "case 2 { 1,3 : { no } 2: { yes} }" => 'yes', "case 2 { 1,3 : { no } 5: { no } default: { yes }}" => 'yes', "case 2 { 1,3 : { no } 5: { no } }" => nil, "case 'banana' { 1,3 : { no } /.*ana.*/: { yes } }" => 'yes', "case 'banana' { /.*(ana).*/: { $1 } }" => 'ana', "case [1] { Array : { yes } }" => 'yes', "case [1] { Array[String] : { no } Array[Integer]: { yes } }" => 'yes', "case 1 { Integer : { yes } Type[Integer] : { no } }" => 'yes', "case Integer { Integer : { no } Type[Integer] : { yes } }" => 'yes', # supports unfold "case ringo { *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle', "case undef { undef : { 'yes' } }" => 'yes', "case undef { *undef : { 'no' } default :{ 'yes' }}" => 'yes', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "2 ? { 1 => no, 2 => yes}" => 'yes', "3 ? { 1 => no, 2 => no, default => yes }" => 'yes', "3 ? { 1 => no, default => yes, 3 => no }" => 'no', "3 ? { 1 => no, 3 => no, default => yes }" => 'no', "4 ? { 1 => no, default => yes, 3 => no }" => 'yes', "4 ? { 1 => no, 3 => no, default => yes }" => 'yes', "'banana' ? { /.*(ana).*/ => $1 }" => 'ana', "[2] ? { Array[String] => yes, Array => yes}" => 'yes', "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle', "undef ? undef => 'yes'" => 'yes', "undef ? {*undef => 'no', default => 'yes'}" => 'yes', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it 'fails if a selector does not match' do expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/) end end context "When evaluator evaluated unfold" do { "*[1,2,3]" => [1,2,3], "*1" => [1], "*'a'" => ['a'] }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__) expect(result).to include(['a', 10]) expect(result).to include(['b', 20]) end end context "When evaluator performs [] operations" do { "[1,2,3][0]" => 1, "[1,2,3][2]" => 3, "[1,2,3][3]" => nil, "[1,2,3][-1]" => 3, "[1,2,3][-2]" => 2, "[1,2,3][-4]" => nil, "[1,2,3,4][0,2]" => [1,2], "[1,2,3,4][1,3]" => [2,3,4], "[1,2,3,4][-2,2]" => [3,4], "[1,2,3,4][-3,2]" => [2,3], "[1,2,3,4][3,5]" => [4], "[1,2,3,4][5,2]" => [], "[1,2,3,4][0,-1]" => [1,2,3,4], "[1,2,3,4][0,-2]" => [1,2,3], "[1,2,3,4][0,-4]" => [1], "[1,2,3,4][0,-5]" => [], "[1,2,3,4][-5,2]" => [1], "[1,2,3,4][-5,-3]" => [1,2], "[1,2,3,4][-6,-3]" => [1,2], "[1,2,3,4][2,-3]" => [], "[1,*[2,3],4]" => [1,2,3,4], "[1,*[2,3],4][1]" => 2, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{a=>1, b=>2, c=>3}[a]" => 1, "{a=>1, b=>2, c=>3}[c]" => 3, "{a=>1, b=>2, c=>3}[x]" => nil, "{a=>1, b=>2, c=>3}[c,b]" => [3,2], "{a=>1, b=>2, c=>3}[a,b,c]" => [1,2,3], "{a=>{b=>{c=>'it works'}}}[a][b][c]" => 'it works', "$a = {undef => 10} $a[free_lunch]" => nil, "$a = {undef => 10} $a[undef]" => 10, "$a = {undef => 10} $a[$a[free_lunch]]" => 10, "$a = {} $a[free_lunch] == undef" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'abc'[0]" => 'a', "'abc'[2]" => 'c', "'abc'[-1]" => 'c', "'abc'[-2]" => 'b', "'abc'[-3]" => 'a', "'abc'[-4]" => '', "'abc'[3]" => '', "abc[0]" => 'a', "abc[2]" => 'c', "abc[-1]" => 'c', "abc[-2]" => 'b', "abc[-3]" => 'a', "abc[-4]" => '', "abc[3]" => '', "'abcd'[0,2]" => 'ab', "'abcd'[1,3]" => 'bcd', "'abcd'[-2,2]" => 'cd', "'abcd'[-3,2]" => 'bc', "'abcd'[3,5]" => 'd', "'abcd'[5,2]" => '', "'abcd'[0,-1]" => 'abcd', "'abcd'[0,-2]" => 'abc', "'abcd'[0,-4]" => 'a', "'abcd'[0,-5]" => '', "'abcd'[-5,2]" => 'a', "'abcd'[-5,-3]" => 'ab', "'abcd'[-6,-3]" => 'ab', "'abcd'[2,-3]" => '', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Type operations (full set tested by tests covering type calculator) { "Array[Integer]" => types.array_of(types.integer), "Array[Integer,1]" => types.constrain_size(types.array_of(types.integer),1, :default), "Array[Integer,1,2]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1,2]]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1]]" => types.constrain_size(types.array_of(types.integer),1, :default), "Hash[Integer,Integer]" => types.hash_of(types.integer, types.integer), "Hash[Integer,Integer,1]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Hash[Integer,Integer,1,2]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1,2]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Resource[File]" => types.resource('File'), "Resource['File']" => types.resource(types.resource('File')), "File[foo]" => types.resource('file', 'foo'), "File[foo, bar]" => [types.resource('file', 'foo'), types.resource('file', 'bar')], "Pattern[a, /b/, Pattern[c], Regexp[d]]" => types.pattern('a', 'b', 'c', 'd'), "String[1,2]" => types.constrain_size(types.string,1, 2), "String[Integer[1,2]]" => types.constrain_size(types.string,1, 2), "String[Integer[1]]" => types.constrain_size(types.string,1, :default), }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # LHS where [] not supported, and missing key(s) { "Array[]" => :error, "'abc'[]" => :error, "Resource[]" => :error, "File[]" => :error, "String[]" => :error, "1[]" => :error, "3.14[]" => :error, "/.*/[]" => :error, "$a=[1] $a[]" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(/Syntax error/) end end # Errors when wrong number/type of keys are used { "Array[0]" => 'Array-Type[] arguments must be types. Got Fixnum', "Hash[0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Hash[Integer, 0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Array[Integer,1,2,3]" => 'Array-Type[] accepts 1 to 3 arguments. Got 4', "Array[Integer,String]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String-Type", "Hash[Integer,String, 1,2,3]" => 'Hash-Type[] accepts 2 to 4 arguments. Got 5', "'abc'[x]" => "The value 'x' cannot be converted to Numeric", "'abc'[1.0]" => "A String[] cannot use Float where Integer is expected", "'abc'[1,2,3]" => "String supports [] with one or two arguments. Got 3", "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Integer', "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Integer where a resource title String is expected', "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Integer where a resource title String is expected', "String[a]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String", "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Integer where String or Regexp or Pattern-Type or Regexp-Type is expected', "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Integer where String or Regexp is expected', "Regexp[a,b]" => 'A Regexp-Type[] accepts 1 argument. Got 2', "true[0]" => "Operator '[]' is not applicable to a Boolean", "1[0]" => "Operator '[]' is not applicable to an Integer", "3.14[0]" => "Operator '[]' is not applicable to a Float", "/.*/[0]" => "Operator '[]' is not applicable to a Regexp", "[1][a]" => "The value 'a' cannot be converted to Numeric", "[1][0.0]" => "An Array[] cannot use Float where Integer is expected", "[1]['0.0']" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, 0.0]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1.0, -1]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, -1.0]" => "An Array[] cannot use Float where Integer is expected", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Regexp.new(Regexp.quote(result))) end end context "on catalog types" do it "[n] gets resource parameter [n]" do source = "notify { 'hello': message=>'yo'} Notify[hello][message]" parser.evaluate_string(scope, source, __FILE__).should == 'yo' end it "[n] gets class parameter [n]" do source = "class wonka($produces='chocolate'){ } include wonka Class[wonka][produces]" # This is more complicated since it needs to run like 3.x and do an import_ast adapted_parser = Puppet::Parser::E4ParserAdapter.new adapted_parser.file = __FILE__ ast = adapted_parser.parse(source) Puppet.override({:global_scope => scope}, "test") do scope.known_resource_types.import_ast(ast, '') ast.code.safeevaluate(scope).should == 'chocolate' end end # Resource default and override expressions and resource parameter access with [] { # Properties "notify { id: message=>explicit} Notify[id][message]" => "explicit", "Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default", "notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override", # Parameters "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default", "notify {foo:} Notify[foo]{withpath=>by_override} Notify[foo][withpath]" => "by_override", # Metaparameters "notify { foo: tag => evoe} Notify[foo][tag]" => "evoe", # Does not produce the defaults for tag parameter (title, type or names of scopes) "notify { foo: } Notify[foo][tag]" => nil, # But a default may be specified on the type "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default", "Notify { tag=>by_default } notify { foo: } Notify[foo]{ tag=>by_override } Notify[foo][tag]" => "by_override", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Virtual and realized resource default and overridden resource parameter access with [] { # Properties "@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@notify { id: message=>explicit } realize Notify[id] Notify[id][message]" => "explicit", "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default", "Notify { message=>by_default } @notify { id: tag=>thisone } Notify <| tag == thisone |>; Notify[id][message]" => "by_default", "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override", # Parameters "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } @notify { id: } Notify[id][withpath]" => "by_default", "@notify { id: } realize Notify[id] Notify[id]{withpath=>by_override} Notify[id][withpath]" => "by_override", # Metaparameters "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit", }.each do |source, result| it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Exported resource attributes { "@@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@@notify { id: message=>explicit, tag=>thisone } Notify <<| tag == thisone |>> Notify[id][message]" => "explicit", }.each do |source, result| it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Resource default and override expressions and resource parameter access error conditions { "notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/, "notify { id: message=>explicit} Notify[id][mustard]" => /does not have a parameter called 'mustard'/, # NOTE: these meta-esque parameters are not recognized as such "notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/, "notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/, "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/ }.each do |source, result| it "should parse '#{source}' and raise error matching #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result) end end context 'with errors' do { "Class['fail-whale']" => /Illegal name/, "Class[0]" => /An Integer cannot be used where a String is expected/, "Class[/.*/]" => /A Regexp cannot be used where a String is expected/, "Class[4.1415]" => /A Float cannot be used where a String is expected/, "Class[Integer]" => /An Integer-Type cannot be used where a String is expected/, "Class[File['tmp']]" => /A File\['tmp'\] Resource-Reference cannot be used where a String is expected/, }.each do | source, error_pattern| it "an error is flagged for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error_pattern) end end end end # end [] operations end context "When the evaluator performs boolean operations" do { "true and true" => true, "false and true" => false, "true and false" => false, "false and false" => false, "true or true" => true, "false or true" => true, "true or false" => true, "false or false" => false, "! true" => false, "!! true" => true, "!! false" => false, "! 'x'" => false, "! ''" => false, "! undef" => true, "! [a]" => false, "! []" => false, "! {a=>1}" => false, "! {}" => false, "true and false and '0xwtf' + 1" => false, "false or true or '0xwtf' + 1" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "false || false || '0xwtf' + 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluator performs operations on literal undef" do it "computes non existing hash lookup as undef" do parser.evaluate_string(scope, "{a => 1}[b] == undef", __FILE__).should == true parser.evaluate_string(scope, "undef == {a => 1}[b]", __FILE__).should == true end end context "When evaluator performs calls" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { 'sprintf( "x%iy", $a )' => "x10y", # unfolds 'sprintf( *["x%iy", $a] )' => "x10y", '"x%iy".sprintf( $a )' => "x10y", '$b.reduce |$memo,$x| { $memo + $x }' => 6, 'reduce($b) |$memo,$x| { $memo + $x }' => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end it "provides location information on error in unparenthesized call logic" do expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line 1\:1/) end it 'defaults can be given in a lambda and used only when arg is missing' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Integer', 'count' required_block_param end def test(count) yield(*[].fill(10, 0, count)) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30) expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20) end it 'a given undef does not select the default value' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Any', 'lambda_arg' required_block_param end def test(lambda_arg) yield(lambda_arg) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true) end it 'a given undef is given as nil' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:assert_no_undef) do dispatch :assert_no_undef do param 'Any', 'x' end def assert_no_undef(x) case x when Array return unless x.include?(:undef) when Hash return unless x.keys.include?(:undef) || x.values.include?(:undef) else return unless x == :undef end raise "contains :undef" end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'assert_no_undef', the_func, __FILE__) expect{parser.evaluate_string(scope, "assert_no_undef(undef)")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef([undef])")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error() end context 'using the 3x function api' do it 'can call a 3x function' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } parser.evaluate_string(scope, "bazinga(42)", __FILE__).should == 42 end it 'maps :undef to empty string' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } parser.evaluate_string(scope, "$a = {} bazinga($a[nope])", __FILE__).should == '' parser.evaluate_string(scope, "bazinga(undef)", __FILE__).should == '' end it 'does not map :undef to empty string in arrays' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0][0] } parser.evaluate_string(scope, "$a = {} $b = [$a[nope]] bazinga($b)", __FILE__).should == :undef parser.evaluate_string(scope, "bazinga([undef])", __FILE__).should == :undef end it 'does not map :undef to empty string in hashes' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0]['a'] } parser.evaluate_string(scope, "$a = {} $b = {a => $a[nope]} bazinga($b)", __FILE__).should == :undef parser.evaluate_string(scope, "bazinga({a => undef})", __FILE__).should == :undef end end end context "When evaluator performs string interpolation" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { '"value is $a yo"' => "value is 10 yo", '"value is \$a yo"' => "value is $a yo", '"value is ${a} yo"' => "value is 10 yo", '"value is \${a} yo"' => "value is ${a} yo", '"value is ${$a} yo"' => "value is 10 yo", '"value is ${$a*2} yo"' => "value is 20 yo", '"value is ${sprintf("x%iy",$a)} yo"' => "value is x10y yo", '"value is ${"x%iy".sprintf($a)} yo"' => "value is x10y yo", '"value is ${[1,2,3]} yo"' => "value is [1, 2, 3] yo", '"value is ${/.*/} yo"' => "value is /.*/ yo", '$x = undef "value is $x yo"' => "value is yo", '$x = default "value is $x yo"' => "value is default yo", '$x = Array[Integer] "value is $x yo"' => "value is Array[Integer] yo", '"value is ${Array[Integer]} yo"' => "value is Array[Integer] yo", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate an interpolation of a hash" do source = '"value is ${{a=>1,b=>2}} yo"' # This test requires testing against two options because a hash to string # produces a result that is unordered hashstr = {'a' => 1, 'b' => 2}.to_s alt_results = ["value is {a => 1, b => 2} yo", "value is {b => 2, a => 1} yo" ] populate parse_result = parser.evaluate_string(scope, source, __FILE__) alt_results.include?(parse_result).should == true end it 'should accept a variable with leading underscore when used directly' do source = '$_x = 10; "$_x"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a variable with leading underscore when used as an expression' do source = '$_x = 10; "${_x}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a numeric variable expressed as $n' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${$2}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('123') end it 'should accept a numeric variable expressed as just an integer' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${2}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('123') end it 'should accept a numeric variable expressed as $n in an access operation' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${$0[4,3]}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('23d') end it 'should accept a numeric variable expressed as just an integer in an access operation' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${0[4,3]}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('23d') end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluating variables" do context "that are non existing an error is raised for" do it "unqualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity", __FILE__) }.to raise_error(/Unknown variable/) end it "qualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity::graviton", __FILE__) }.to raise_error(/Unknown variable/) end end it "a lex error should be raised for '$foo::::bar'" do expect { parser.evaluate_string(scope, "$foo::::bar") }.to raise_error(Puppet::LexError, /Illegal fully qualified name at line 1:7/) end { '$a = $0' => nil, '$a = $1' => nil, }.each do |source, value| it "it is ok to reference numeric unassigned variables '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end { '$00 = 0' => /must be a decimal value/, '$0xf = 0' => /must be a decimal value/, '$0777 = 0' => /must be a decimal value/, '$123a = 0' => /must be a decimal value/, }.each do |source, error_pattern| it "should raise an error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(error_pattern) end end context "an initial underscore in the last segment of a var name is allowed" do { '$_a = 1' => 1, '$__a = 1' => 1, }.each do |source, value| it "as in this example '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end end end context "When evaluating relationships" do it 'should form a relation with File[a] -> File[b]' do source = "File[a] -> File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'b']) end it 'should form a relation with resource -> resource' do source = "notify{a:} -> notify{b:}" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['Notify', 'a', '->', 'Notify', 'b']) end it 'should form a relation with [File[a], File[b]] -> [File[x], File[y]]' do source = "[File[a], File[b]] -> [File[x], File[y]]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'y']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'y']) end it 'should tolerate (eliminate) duplicates in operands' do source = "[File[a], File[a]] -> File[x]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.relationships.size.should == 1 end it 'should form a relation with <-' do source = "File[a] <- File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'a']) end it 'should form a relation with <-' do source = "File[a] <~ File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '~>', 'File', 'a']) end end context "When evaluating heredoc" do it "evaluates plain heredoc" do src = "@(END)\nThis is\nheredoc text\nEND\n" parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin" do src = [ "@(END)", " This is", " heredoc text", " | END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin and right newline trim" do src = [ "@(END)", " This is", " heredoc text", " |- END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text" end it "parses escape specification" do src = <<-CODE @(END/t) Tex\\tt\\n |- END CODE parser.evaluate_string(scope, src).should == "Tex\tt\\n" end it "parses syntax checked specification" do src = <<-CODE @(END:json) ["foo", "bar"] |- END CODE parser.evaluate_string(scope, src).should == '["foo", "bar"]' end it "parses syntax checked specification with error and reports it" do src = <<-CODE @(END:json) ['foo', "bar"] |- END CODE expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/) end it "parses interpolated heredoc expression" do src = <<-CODE $name = 'Fjodor' @("END") Hello $name |- END CODE parser.evaluate_string(scope, src).should == "Hello Fjodor" end end context "Handles Deprecations and Discontinuations" do it 'of import statements' do source = "\nimport foo" # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error(/'import' has been discontinued.*line 2:1/) end end context "Detailed Error messages are reported" do it 'for illegal type references' do source = '1+1 { "title": }' # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error( /Illegal Resource Type expression, expected result to be a type name, or untitled Resource.*line 1:2/) end it 'for non r-value producing <| |>' do expect { parser.parse_string("$a = File <| |>", nil) }.to raise_error(/A Virtual Query does not produce a value at line 1:6/) end it 'for non r-value producing <<| |>>' do expect { parser.parse_string("$a = File <<| |>>", nil) }.to raise_error(/An Exported Query does not produce a value at line 1:6/) end it 'for non r-value producing define' do - Puppet.expects(:err).with("Invalid use of expression. A 'define' expression does not produce a value at line 1:6") - Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") + Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => "Invalid use of expression. A 'define' expression does not produce a value", :line => 1, :pos => 6)) + Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Classes, definitions, and nodes may only appear at toplevel or inside other classes', :line => 1, :pos => 6)) expect { parser.parse_string("$a = define foo { }", nil) }.to raise_error(/2 errors/) end it 'for non r-value producing class' do - Puppet.expects(:err).with("Invalid use of expression. A Host Class Definition does not produce a value at line 1:6") - Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") + Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Invalid use of expression. A Host Class Definition does not produce a value', :line => 1, :pos => 6)) + Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Classes, definitions, and nodes may only appear at toplevel or inside other classes', :line => 1, :pos => 6)) expect { parser.parse_string("$a = class foo { }", nil) }.to raise_error(/2 errors/) end it 'for unclosed quote with indication of start position of string' do source = <<-SOURCE.gsub(/^ {6}/,'') $a = "xx yyy SOURCE # first char after opening " reported as being in error. expect { parser.parse_string(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' at line 1:7/) end it 'for multiple errors with a summary exception' do - Puppet.expects(:err).with("Invalid use of expression. A Node Definition does not produce a value at line 1:6") - Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") + Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Invalid use of expression. A Node Definition does not produce a value', :line => 1, :pos => 6)) + Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Classes, definitions, and nodes may only appear at toplevel or inside other classes', :line => 1, :pos => 6)) expect { parser.parse_string("$a = node x { }",nil) }.to raise_error(/2 errors/) end it 'for a bad hostname' do expect { parser.parse_string("node 'macbook+owned+by+name' { }", nil) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.*at line 1:6/) end it 'for a hostname with interpolation' do source = <<-SOURCE.gsub(/^ {6}/,'') $name = 'fred' node "macbook-owned-by$name" { } SOURCE expect { parser.parse_string(source, nil) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node at line 2:23/) end end context 'does not leak variables' do it 'local variables are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $y SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'y'/) end it 'lambda parameters are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $x SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'x'/) end it 'does not leak match variables' do source = <<-SOURCE if 'xyz' =~ /(x)(y)(z)/ { notice $2 } case 'abc' { /(a)(b)(c)/ : { $x = $2 } } "-$x-$2-" SOURCE expect(parser.evaluate_string(scope, source)).to eq('-b--') end end matcher :have_relationship do |expected| calc = Puppet::Pops::Types::TypeCalculator.new match do |compiler| op_name = {'->' => :relationship, '~>' => :subscription} compiler.relationships.any? do | relation | relation.source.type == expected[0] && relation.source.title == expected[1] && relation.type == op_name[expected[2]] && relation.target.type == expected[3] && relation.target.title == expected[4] end end failure_message_for_should do |actual| "Relationship #{expected[0]}[#{expected[1]}] #{expected[2]} #{expected[3]}[#{expected[4]}] but was unknown to compiler" end end end diff --git a/spec/unit/pops/issues_spec.rb b/spec/unit/pops/issues_spec.rb index 82dce1b44..bf0b5fd88 100644 --- a/spec/unit/pops/issues_spec.rb +++ b/spec/unit/pops/issues_spec.rb @@ -1,196 +1,196 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe "Puppet::Pops::Issues" do include Puppet::Pops::Issues it "should have an issue called NAME_WITH_HYPHEN" do x = Puppet::Pops::Issues::NAME_WITH_HYPHEN x.class.should == Puppet::Pops::Issues::Issue x.issue_code.should == :NAME_WITH_HYPHEN end it "should should format a message that requires an argument" do x = Puppet::Pops::Issues::NAME_WITH_HYPHEN x.format(:name => 'Boo-Hoo', :label => Puppet::Pops::Model::ModelLabelProvider.new, :semantic => "dummy" ).should == "A String may not have a name containing a hyphen. The name 'Boo-Hoo' is not legal" end it "should should format a message that does not require an argument" do x = Puppet::Pops::Issues::NOT_TOP_LEVEL x.format().should == "Classes, definitions, and nodes may only appear at toplevel or inside other classes" end end describe "Puppet::Pops::IssueReporter" do let(:acceptor) { Puppet::Pops::Validation::Acceptor.new } def fake_positioned(number) stub("positioned_#{number}", :line => number, :pos => number) end def diagnostic(severity, number) Puppet::Pops::Validation::Diagnostic.new( severity, Puppet::Pops::Issues::Issue.new(number) { "#{severity}#{number}" }, "#{severity}file", fake_positioned(number)) end def warning(number) diagnostic(:warning, number) end def deprecation(number) diagnostic(:deprecation, number) end def error(number) diagnostic(:error, number) end context "given warnings" do before(:each) do acceptor.accept( warning(1) ) acceptor.accept( deprecation(1) ) end it "emits warnings if told to emit them" do - Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/)) + Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "does not emit warnings if not told to emit them" do - Puppet.expects(:warning).never + Puppet::Log.expects(:create).never Puppet::Pops::IssueReporter.assert_and_report(acceptor, {}) end it "emits no warnings if :max_warnings is 0" do acceptor.accept( warning(2) ) Puppet[:max_warnings] = 0 - Puppet.expects(:warning).once.with(regexp_matches(/deprecation1/)) + Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/deprecation1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "emits no more than 1 warning if :max_warnings is 1" do acceptor.accept( warning(2) ) acceptor.accept( warning(3) ) Puppet[:max_warnings] = 1 - Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/)) + Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "does not emit more deprecations warnings than the max deprecation warnings" do acceptor.accept( deprecation(2) ) Puppet[:max_deprecations] = 0 - Puppet.expects(:warning).once.with(regexp_matches(/warning1/)) + Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/warning1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "does not emit deprecation warnings, but does emit regular warnings if disable_warnings includes deprecations" do Puppet[:disable_warnings] = 'deprecations' - Puppet.expects(:warning).once.with(regexp_matches(/warning1/)) + Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/warning1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end end context "given errors" do it "logs nothing, but raises the given :message if :emit_errors is repressing error logging" do acceptor.accept( error(1) ) - Puppet.expects(:err).never + Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_errors => false, :message => 'special'}) end.to raise_error(Puppet::ParseError, 'special') end it "prefixes :message if a single error is raised" do acceptor.accept( error(1) ) - Puppet.expects(:err).never + Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'}) end.to raise_error(Puppet::ParseError, /special error1/) end it "logs nothing and raises immediately if there is only one error" do acceptor.accept( error(1) ) - Puppet.expects(:err).never + Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /error1/) end it "logs nothing and raises immediately if there are multiple errors but max_errors is 0" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) Puppet[:max_errors] = 0 - Puppet.expects(:err).never + Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /error1/) end it "logs the :message if there is more than one allowed error" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) - Puppet.expects(:err).times(3).with(regexp_matches(/error1|error2|special/)) + Puppet::Log.expects(:create).times(3).with(has_entries(:level => :err, :message => regexp_matches(/error1|error2|special/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'}) end.to raise_error(Puppet::ParseError, /Giving up/) end it "emits accumulated errors before raising a 'giving up' message if there are more errors than allowed" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) acceptor.accept( error(3) ) Puppet[:max_errors] = 2 - Puppet.expects(:err).times(2).with(regexp_matches(/error1|error2/)) + Puppet::Log.expects(:create).times(2).with(has_entries(:level => :err, :message => regexp_matches(/error1|error2/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/) end it "emits accumulated errors before raising a 'giving up' message if there are multiple errors but fewer than limits" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) acceptor.accept( error(3) ) Puppet[:max_errors] = 4 - Puppet.expects(:err).times(3).with(regexp_matches(/error[123]/)) + Puppet::Log.expects(:create).times(3).with(has_entries(:level => :err, :message => regexp_matches(/error[123]/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/) end it "emits errors regardless of disable_warnings setting" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) Puppet[:disable_warnings] = 'deprecations' - Puppet.expects(:err).times(2).with(regexp_matches(/error1|error2/)) + Puppet::Log.expects(:create).times(2).with(has_entries(:level => :err, :message => regexp_matches(/error1|error2/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /Giving up/) end end context "given both" do it "logs warnings and errors" do acceptor.accept( warning(1) ) acceptor.accept( error(1) ) acceptor.accept( error(2) ) acceptor.accept( error(3) ) acceptor.accept( deprecation(1) ) Puppet[:max_errors] = 2 - Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/)) - Puppet.expects(:err).times(2).with(regexp_matches(/error[123]/)) + Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) + Puppet::Log.expects(:create).twice.with(has_entries(:level => :err, :message => regexp_matches(/error[123]/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end.to raise_error(Puppet::ParseError, /3 errors.*2 warnings.*Giving up/) end end end diff --git a/spec/unit/pops/migration_spec.rb b/spec/unit/pops/migration_spec.rb new file mode 100644 index 000000000..4f506faec --- /dev/null +++ b/spec/unit/pops/migration_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' + +require 'puppet/pops' +require 'puppet/pops/evaluator/evaluator_impl' +require 'puppet/loaders' +require 'puppet_spec/pops' +require 'puppet_spec/scope' +require 'puppet/parser/e4_parser_adapter' + +describe 'Puppet::Pops::MigrationMigrationChecker' do + include PuppetSpec::Pops + include PuppetSpec::Scope + before(:each) do + Puppet[:strict_variables] = true + + # These must be set since the 3x logic switches some behaviors on these even if the tests explicitly + # use the 4x parser and evaluator. + # + Puppet[:parser] = 'future' + + # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) + require 'puppetx' + + # Tests needs a known configuration of node/scope/compiler since it parses and evaluates + # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete + # catalog for each tested expression. + # + @parser = Puppet::Pops::Parser::EvaluatingParser.new + @node = Puppet::Node.new('node.example.com') + @node.environment = Puppet::Node::Environment.create(:testing, []) + @compiler = Puppet::Parser::Compiler.new(@node) + @scope = Puppet::Parser::Scope.new(@compiler) + @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') + @scope.parent = @compiler.topscope + end + + let(:scope) { @scope } + + describe "when there is no MigrationChecker in the PuppetContext" do + it "a null implementation of the MigrationChecker gets created (once per impl that needs one)" do + migration_checker = Puppet::Pops::Migration::MigrationChecker.new() + Puppet::Pops::Migration::MigrationChecker.expects(:new).at_least_once.returns(migration_checker) + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "1", __FILE__).should == 1 + Puppet::Pops::Migration::MigrationChecker.unstub(:new) + end + end + + describe "when there is a MigrationChecker in the Puppet Context" do + it "does not create any MigrationChecker instances when parsing and evaluating" do + migration_checker = mock() + Puppet::Pops::Migration::MigrationChecker.expects(:new).never + Puppet.override({:migration_checker => migration_checker}, "test-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "true", __FILE__) + end + Puppet::Pops::Migration::MigrationChecker.unstub(:new) + end + end + + describe "when validating parsed code" do + it "is called for each integer" do + migration_checker = mock() + migration_checker.expects(:report_ambiguous_integer).times(3) + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "$a = [1,2,3]", __FILE__) + end + end + + it "is called for each float" do + migration_checker = mock() + migration_checker.expects(:report_ambiguous_float).times(3) + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "$a = [1.0,2.0,3.1415]", __FILE__) + end + end + + it "last expressions in blocks are checked" do + migration_checker = mock() + migration_checker.expects(:report_array_last_in_block).twice # the program itself is a block too + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "$b = {} if true { $a = $b [false] }", __FILE__) + end + end + + end + + describe "when evaluating code" do + it "is called for boolean coercion of String" do + migration_checker = mock() + migration_checker.expects(:report_empty_string_true).times(2) + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "$a = ('a' and '')", __FILE__) + end + end + + context "with a case expression" do + it "the test expression is checked for UC_bareword" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).once + migration_checker.expects(:report_option_type_mismatch).once + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "case Foo { default: {}}", __FILE__) + end + end + + it "all case options are checked for UC_bareword" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).times(4) + migration_checker.expects(:report_option_type_mismatch).times(4) + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, + "case true { Foo, Bar: {} Fee, 'not': {}}", __FILE__) + end + end + end + + context "with a selector expression" do + it "the test expression is checked for UC_bareword" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).once + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "Foo ? { default => false}", __FILE__) + end + end + + it "all options are checked for UC_bareword" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).times(3) + migration_checker.expects(:report_option_type_mismatch).times(3) + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, + "true ? { Foo => false, Bar => false, 'not' => false, default => true}", __FILE__) + end + end + end + + context "with a comparison of" do + ['==', '!=', ].each do |operator| + it "'a' #{operator} 'b'" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).twice + migration_checker.expects(:report_equality_type_mismatch).once + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "'a' #{operator} 'b'", __FILE__) + end + end + end + + ['<', '>', '<=', '>='].each do |operator| + it "'a' #{operator} 'b'" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).twice + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "'a' #{operator} 'b'", __FILE__) + end + end + end + + ['=~', '!~'].each do |operator| + it "'a' #{operator} /.*/" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).once + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "'a' #{operator} /.*/", __FILE__) + end + end + end + end + + context "with an in operator" do + it "'a' in [true,false]" do + migration_checker = mock() + migration_checker.expects(:report_uc_bareword_type).once + migration_checker.expects(:report_in_expression).once + Puppet.override({:migration_checker => migration_checker}, "migration-context") do + Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "'a' in [true, false]", __FILE__) + end + end + end + end +end diff --git a/spec/unit/pops/validation_spec.rb b/spec/unit/pops/validation_spec.rb new file mode 100644 index 000000000..dfe51438e --- /dev/null +++ b/spec/unit/pops/validation_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' +require 'puppet/pops' + +describe 'Puppet::Pops::Validation::Diagnostic' do + + # Mocks a SourcePosAdapter as it is used in these use cases + # of a Diagnostic + # + class MockSourcePos + attr_reader :offset + def initialize(offset) + @offset = offset + end + end + + it "computes equal hash value ignoring arguments" do + issue = Puppet::Pops::Issues::DIV_BY_ZERO + source_pos = MockSourcePos.new(10) + d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos, {:foo => 10}) + d2 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos.clone, {:bar => 20}) + expect(d1.hash).to eql(d2.hash) + end + + it "computes non equal hash value for different severities" do + issue = Puppet::Pops::Issues::DIV_BY_ZERO + source_pos = MockSourcePos.new(10) + d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos, {}) + d2 = Puppet::Pops::Validation::Diagnostic.new(:error, issue, "foo", source_pos.clone, {}) + expect(d1.hash).to_not eql(d2.hash) + end + + it "computes non equal hash value for different offsets" do + issue = Puppet::Pops::Issues::DIV_BY_ZERO + source_pos1 = MockSourcePos.new(10) + source_pos2 = MockSourcePos.new(11) + d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos1, {}) + d2 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos2, {}) + expect(d1.hash).to_not eql(d2.hash) + end + + it "can be used in a set" do + the_set = Set.new() + issue = Puppet::Pops::Issues::DIV_BY_ZERO + source_pos = MockSourcePos.new(10) + d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos, {}) + d2 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos.clone, {}) + d3 = Puppet::Pops::Validation::Diagnostic.new(:error, issue, "foo", source_pos.clone, {}) + expect(the_set.add?(d1)).to_not be_nil + expect(the_set.add?(d2)).to be_nil + expect(the_set.add?(d3)).to_not be_nil + end + +end + +describe "Puppet::Pops::Validation::SeverityProducer" do + it 'sets default severity given in initializer' do + producer = Puppet::Pops::Validation::SeverityProducer.new(:warning) + expect(producer.severity(Puppet::Pops::Issues::DIV_BY_ZERO)).to be(:warning) + end + + it 'sets default severity to :error if not given' do + producer = Puppet::Pops::Validation::SeverityProducer.new() + expect(producer.severity(Puppet::Pops::Issues::DIV_BY_ZERO)).to be(:error) + end + +end diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb index 7c9366f72..3c9329ee1 100644 --- a/spec/unit/provider/group/windows_adsi_spec.rb +++ b/spec/unit/provider/group/windows_adsi_spec.rb @@ -1,168 +1,200 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:group).new( :title => 'testers', :provider => :windows_adsi ) end let(:provider) { resource.provider } let(:connection) { stub 'connection' } before :each do Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do it "should enumerate all groups" do names = ['group1', 'group2', 'group3'] stub_groups = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns stub_groups described_class.instances.map(&:name).should =~ names end end describe "group type :members property helpers" do let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') } let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') } + let(:user3) { stub(:account => 'user3', :domain => '.', :to_s => 'user3sid') } before :each do Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user1').returns(user1) Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user2').returns(user2) + Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user3').returns(user3) end describe "#members_insync?" do it "should return false when current is nil" do provider.members_insync?(nil, ['user2']).should be_false end + it "should return false when should is nil" do provider.members_insync?(['user1'], nil).should be_false end + it "should return false for differing lists of members" do provider.members_insync?(['user1'], ['user2']).should be_false provider.members_insync?(['user1'], []).should be_false provider.members_insync?([], ['user2']).should be_false end + it "should return true for same lists of members" do provider.members_insync?(['user1', 'user2'], ['user1', 'user2']).should be_true end + it "should return true for same lists of unordered members" do provider.members_insync?(['user1', 'user2'], ['user2', 'user1']).should be_true end + it "should return true for same lists of members irrespective of duplicates" do provider.members_insync?(['user1', 'user2', 'user2'], ['user2', 'user1', 'user1']).should be_true end + + context "when auth_membership => true" do + before :each do + # this is also the default + resource[:auth_membership] = true + end + + it "should return false when should user(s) are not the only items in the current" do + provider.members_insync?(['user1', 'user2'], ['user1']).should be_false + end + end + + context "when auth_membership => false" do + before :each do + resource[:auth_membership] = false + end + + it "should return true when current user(s) contains at least the should list" do + provider.members_insync?(['user1','user2'], ['user1']).should be_true + end + + it "should return true when current user(s) contains at least the should list, even unordered" do + provider.members_insync?(['user3','user1','user2'], ['user2','user1']).should be_true + end + end end describe "#members_to_s" do it "should return an empty string on non-array input" do [Object.new, {}, 1, :symbol, ''].each do |input| provider.members_to_s(input).should be_empty end end it "should return an empty string on empty or nil users" do provider.members_to_s([]).should be_empty provider.members_to_s(nil).should be_empty end it "should return a user string like DOMAIN\\USER" do provider.members_to_s(['user1']).should == '.\user1' end it "should return a user string like DOMAIN\\USER,DOMAIN2\\USER2" do provider.members_to_s(['user1', 'user2']).should == '.\user1,.\user2' end end end describe "when managing members" do it "should be able to provide a list of members" do provider.group.stubs(:members).returns ['user1', 'user2', 'user3'] provider.members.should =~ ['user1', 'user2', 'user3'] end it "should be able to set group members" do provider.group.stubs(:members).returns ['user1', 'user2'] member_sids = [ stub(:account => 'user1', :domain => 'testcomputername'), stub(:account => 'user2', :domain => 'testcomputername'), stub(:account => 'user3', :domain => 'testcomputername'), ] provider.group.stubs(:member_sids).returns(member_sids[0..1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(member_sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user3').returns(member_sids[2]) provider.group.expects(:remove_member_sids).with(member_sids[0]) provider.group.expects(:add_member_sids).with(member_sids[2]) provider.members = ['user2', 'user3'] end end describe 'when creating groups' do it "should be able to create a group" do resource[:members] = ['user1', 'user2'] group = stub 'group' Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group create = sequence('create') group.expects(:commit).in_sequence(create) - group.expects(:set_members).with(['user1', 'user2']).in_sequence(create) + group.expects(:set_members).with(['user1', 'user2'], true).in_sequence(create) provider.create end it 'should not create a group if a user by the same name exists' do Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create group if user 'testers' exists./ ) end it 'should commit a newly created group' do provider.group.expects( :commit ) provider.flush end end it "should be able to test whether a group exists" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') provider.should be_exists Puppet::Util::Windows::ADSI.stubs(:connect).returns nil provider.should_not be_exists end it "should be able to delete a group" do connection.expects(:Delete).with('group', 'testers') provider.delete end it "should report the group's SID as gid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') provider.gid.should == 'S-1-5-32-547' end it "should fail when trying to manage the gid property" do provider.expects(:fail).with { |msg| msg =~ /gid is read-only/ } provider.send(:gid=, 500) end it "should prefer the domain component from the resolved SID" do provider.members_to_s(['.\Administrators']).should == 'BUILTIN\Administrators' end end diff --git a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb index 270ca6bae..9aabb469b 100644 --- a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -1,1607 +1,1607 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows/taskscheduler' if Puppet.features.microsoft_windows? shared_examples_for "a trigger that handles start_date and start_time" do let(:trigger) do described_class.new( :name => 'Shared Test Task', :command => 'C:\Windows\System32\notepad.exe' ).translate_hash_to_trigger(trigger_hash) end before :each do Win32::TaskScheduler.any_instance.stubs(:save) end describe 'the given start_date' do before :each do trigger_hash['start_time'] = '00:00' end def date_component { 'start_year' => trigger['start_year'], 'start_month' => trigger['start_month'], 'start_day' => trigger['start_day'] } end it 'should be able to be specified in ISO 8601 calendar date format' do trigger_hash['start_date'] = '2011-12-31' date_component.should == { 'start_year' => 2011, 'start_month' => 12, 'start_day' => 31 } end it 'should fail if before 1753-01-01' do trigger_hash['start_date'] = '1752-12-31' expect { date_component }.to raise_error( Puppet::Error, 'start_date must be on or after 1753-01-01' ) end it 'should succeed if on 1753-01-01' do trigger_hash['start_date'] = '1753-01-01' date_component.should == { 'start_year' => 1753, 'start_month' => 1, 'start_day' => 1 } end it 'should succeed if after 1753-01-01' do trigger_hash['start_date'] = '1753-01-02' date_component.should == { 'start_year' => 1753, 'start_month' => 1, 'start_day' => 2 } end end describe 'the given start_time' do before :each do trigger_hash['start_date'] = '2011-12-31' end def time_component { 'start_hour' => trigger['start_hour'], 'start_minute' => trigger['start_minute'] } end it 'should be able to be specified as a 24-hour "hh:mm"' do trigger_hash['start_time'] = '17:13' time_component.should == { 'start_hour' => 17, 'start_minute' => 13 } end it 'should be able to be specified as a 12-hour "hh:mm am"' do trigger_hash['start_time'] = '3:13 am' time_component.should == { 'start_hour' => 3, 'start_minute' => 13 } end it 'should be able to be specified as a 12-hour "hh:mm pm"' do trigger_hash['start_time'] = '3:13 pm' time_component.should == { 'start_hour' => 15, 'start_minute' => 13 } end end end describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:scheduled_task).stubs(:defaultprovider).returns(described_class) end describe 'when retrieving' do before :each do @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) described_class.any_instance.stubs(:task).returns(@mock_task) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } describe 'the triggers for a task' do describe 'with only one trigger' do before :each do @mock_task.expects(:trigger_count).returns(1) end it 'should handle a single daily trigger' do @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'days_interval' => 2 }, }) resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'daily', 'every' => '2', 'enabled' => true, 'index' => 0, }] end it 'should handle a single weekly trigger' do scheduled_days_of_week = Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SUNDAY @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'weeks_interval' => 2, 'days_of_week' => scheduled_days_of_week } }) resource.provider.trigger.should == [{ - 'start_date' => '2011-9-12', - 'start_time' => '13:20', - 'schedule' => 'weekly', - 'every' => '2', - 'on' => ['sun', 'mon', 'wed', 'fri'], - 'enabled' => true, - 'index' => 0, + 'start_date' => '2011-9-12', + 'start_time' => '13:20', + 'schedule' => 'weekly', + 'every' => '2', + 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], + 'enabled' => true, + 'index' => 0, }] end it 'should handle a single monthly date-based trigger' do scheduled_months = Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::DECEMBER # 1 3 5 15 'last' scheduled_days = 1 | 1 << 2 | 1 << 4 | 1 << 14 | 1 << 31 @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'months' => scheduled_months, 'days' => scheduled_days } }) resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', 'months' => [1, 2, 8, 9, 12], 'on' => [1, 3, 5, 15, 'last'], 'enabled' => true, 'index' => 0, }] end it 'should handle a single monthly day-of-week-based trigger' do scheduled_months = Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::DECEMBER scheduled_days_of_week = Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SUNDAY @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'months' => scheduled_months, 'weeks' => Win32::TaskScheduler::FIRST_WEEK, 'days_of_week' => scheduled_days_of_week } }) resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', 'months' => [1, 2, 8, 9, 12], 'which_occurrence' => 'first', 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], 'enabled' => true, 'index' => 0, }] end it 'should handle a single one-time trigger' do @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, }) resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'once', 'enabled' => true, 'index' => 0, }] end end it 'should handle multiple triggers' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'flags' => 0, }) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) resource.provider.trigger.should =~ [ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'enabled' => true, 'index' => 0, }, { 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'enabled' => true, 'index' => 1, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'enabled' => true, 'index' => 2, } ] end it 'should skip triggers Win32::TaskScheduler cannot handle' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).raises( Win32::TaskScheduler::Error.new('Unhandled trigger type!') ) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) resource.provider.trigger.should =~ [ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'enabled' => true, 'index' => 2, } ] end it 'should skip trigger types Puppet does not handle' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_EVENT_TRIGGER_AT_LOGON, }) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) resource.provider.trigger.should =~ [ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'enabled' => true, 'index' => 2, } ] end end it 'should get the working directory from the working_directory on the task' do @mock_task.expects(:working_directory).returns('C:\Windows\System32') resource.provider.working_dir.should == 'C:\Windows\System32' end it 'should get the command from the application_name on the task' do @mock_task.expects(:application_name).returns('C:\Windows\System32\notepad.exe') resource.provider.command.should == 'C:\Windows\System32\notepad.exe' end it 'should get the command arguments from the parameters on the task' do @mock_task.expects(:parameters).returns('these are my arguments') resource.provider.arguments.should == 'these are my arguments' end it 'should get the user from the account_information on the task' do @mock_task.expects(:account_information).returns('this is my user') resource.provider.user.should == 'this is my user' end describe 'whether the task is enabled' do it 'should report tasks with the disabled bit set as disabled' do @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) resource.provider.enabled.should == :false end it 'should report tasks without the disabled bit set as enabled' do @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) resource.provider.enabled.should == :true end it 'should not consider triggers for determining if the task is enabled' do @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED, }) resource.provider.enabled.should == :true end end end describe '#exists?' do before :each do @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) described_class.any_instance.stubs(:task).returns(@mock_task) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it "should delegate to Win32::TaskScheduler using the resource's name" do @mock_task.expects(:exists?).with('Test Task').returns(true) resource.provider.exists?.should == true end end describe '#clear_task' do before :each do @mock_task = mock @new_mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) @new_mock_task.responds_like(Win32::TaskScheduler.new) Win32::TaskScheduler.stubs(:new).returns(@mock_task, @new_mock_task) described_class.any_instance.stubs(:exists?).returns(false) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it 'should clear the cached task object' do resource.provider.task.should == @mock_task resource.provider.task.should == @mock_task resource.provider.clear_task resource.provider.task.should == @new_mock_task end it 'should clear the cached list of triggers for the task' do @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @new_mock_task.stubs(:trigger_count).returns(1) @new_mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'flags' => 0, }) mock_task_trigger = { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'enabled' => true, 'index' => 0, } resource.provider.trigger.should == [mock_task_trigger] resource.provider.trigger.should == [mock_task_trigger] resource.provider.clear_task resource.provider.trigger.should == [{ 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'enabled' => true, 'index' => 0, }] end end describe '.instances' do it 'should use the list of .job files to construct the list of scheduled_tasks' do job_files = ['foo.job', 'bar.job', 'baz.job'] Win32::TaskScheduler.any_instance.stubs(:tasks).returns(job_files) job_files.each do |job| job = File.basename(job, '.job') described_class.expects(:new).with(:provider => :win32_taskscheduler, :name => job) end described_class.instances end end describe '#user_insync?', :if => Puppet.features.microsoft_windows? do let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should consider the user as in sync if the name matches' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').twice.returns('SID A') resource.should be_user_insync('joe', ['joe']) end it 'should consider the user as in sync if the current user is fully qualified' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\joe').returns('SID A') resource.should be_user_insync('MACHINE\joe', ['joe']) end it 'should consider a current user of the empty string to be the same as the system user' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID') resource.should be_user_insync('', ['system']) end it 'should consider different users as being different' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') Puppet::Util::Windows::SID.expects(:name_to_sid).with('bob').returns('SID B') resource.should_not be_user_insync('joe', ['bob']) end end describe '#trigger_insync?' do let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should not consider any extra current triggers as in sync' do current = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] desired = {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'} resource.should_not be_trigger_insync(current, desired) end it 'should not consider any extra desired triggers as in sync' do current = {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'} desired = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] resource.should_not be_trigger_insync(current, desired) end it 'should consider triggers to be in sync if the sets of current and desired triggers are equal' do current = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] desired = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] resource.should be_trigger_insync(current, desired) end end describe '#triggers_same?' do let(:provider) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it "should not mutate triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} current.freeze desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired.freeze expect(provider).to be_triggers_same(current, desired) end it "ignores 'index' in current trigger" do current = {'index' => 0, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "ignores 'enabled' in current triggger" do current = {'enabled' => true, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should not consider a disabled 'current' trigger to be the same" do current = {'schedule' => 'once', 'enabled' => false} desired = {'schedule' => 'once'} provider.should_not be_triggers_same(current, desired) end it 'should not consider triggers with different schedules to be the same' do current = {'schedule' => 'once'} desired = {'schedule' => 'weekly'} provider.should_not be_triggers_same(current, desired) end describe 'start_date' do it "considers triggers to be equal when start_date is not specified in the 'desired' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_time' => '15:30', 'every' => 3} provider.should be_triggers_same(current, desired) end end describe 'comparing daily triggers' do it "should consider 'desired' triggers not specifying 'every' to have the same value as the 'current' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} provider.should be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2012-09-12', 'start_time' => '15:30', 'every' => 3} provider.should_not be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:31', 'every' => 3} provider.should_not be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'every' => 3} provider.should be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '5:30', 'every' => 3} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '05:30', 'every' => 3} provider.should be_triggers_same(current, desired) end it "should consider different 'every' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 1} provider.should_not be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30', 'every' => 1} provider.should be_triggers_same(trigger, trigger) end end describe 'comparing one-time triggers' do it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'daily', 'start_date' => '2012-09-12', 'start_time' => '15:30'} provider.should_not be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:31'} provider.should_not be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30'} provider.should be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '1:30'} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30'} provider.should be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30'} provider.should be_triggers_same(trigger, trigger) end end describe 'comparing monthly date-based triggers' do it "should consider 'desired' triggers not specifying 'months' to have the same value as the 'current' trigger" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'on' => [1,'last']} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'on' => [1, 'last']} provider.should be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} provider.should_not be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} provider.should_not be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} provider.should be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '5:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '05:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} provider.should be_triggers_same(current, desired) end it "should consider different 'months' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1], 'on' => [1, 3, 5, 7]} provider.should_not be_triggers_same(current, desired) end it "should consider different 'on' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 5, 7]} provider.should_not be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} provider.should be_triggers_same(trigger, trigger) end end describe 'comparing monthly day-of-week-based triggers' do it "should consider 'desired' triggers not specifying 'months' to have the same value as the 'current' trigger" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } provider.should be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } provider.should_not be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } provider.should_not be_triggers_same(current, desired) end it "should consider different 'months' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3, 5, 7, 9], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } provider.should_not be_triggers_same(current, desired) end it "should consider different 'which_occurrence' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'last', 'day_of_week' => ['mon', 'tues', 'sat'] } provider.should_not be_triggers_same(current, desired) end it "should consider different 'day_of_week' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['fri'] } provider.should_not be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } provider.should be_triggers_same(trigger, trigger) end end describe 'comparing weekly triggers' do it "should consider 'desired' triggers not specifying 'day_of_week' to have the same value as the 'current' trigger" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} provider.should be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} provider.should_not be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} provider.should_not be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} provider.should be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '1:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} provider.should be_triggers_same(current, desired) end it "should consider different 'every' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 1, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} provider.should_not be_triggers_same(current, desired) end it "should consider different 'day_of_week' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['fri']} provider.should_not be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} provider.should be_triggers_same(trigger, trigger) end end end describe '#normalized_date' do it 'should format the date without leading zeros' do described_class.normalized_date('2011-01-01').should == '2011-1-1' end end describe '#normalized_time' do it 'should format the time as {24h}:{minutes}' do described_class.normalized_time('8:37 PM').should == '20:37' end end describe '#translate_hash_to_trigger' do before :each do @puppet_trigger = { 'start_date' => '2011-1-1', 'start_time' => '01:10' } end let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } let(:trigger) { provider.translate_hash_to_trigger(@puppet_trigger) } describe 'when given a one-time trigger' do before :each do @puppet_trigger['schedule'] = 'once' end it 'should set the trigger_type to Win32::TaskScheduler::ONCE' do trigger['trigger_type'].should == Win32::TaskScheduler::ONCE end it 'should not set a type' do trigger.should_not be_has_key('type') end it "should require 'start_date'" do @puppet_trigger.delete('start_date') expect { trigger }.to raise_error( Puppet::Error, /Must specify 'start_date' when defining a one-time trigger/ ) end it "should require 'start_time'" do @puppet_trigger.delete('start_time') expect { trigger }.to raise_error( Puppet::Error, /Must specify 'start_time' when defining a trigger/ ) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'once' }} end end describe 'when given a daily trigger' do before :each do @puppet_trigger['schedule'] = 'daily' end it "should default 'every' to 1" do trigger['type']['days_interval'].should == 1 end it "should use the specified value for 'every'" do @puppet_trigger['every'] = 5 trigger['type']['days_interval'].should == 5 end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now trigger['start_year'].should == today.year trigger['start_month'].should == today.month trigger['start_day'].should == today.day end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'daily', 'every' => 1}} end end describe 'when given a weekly trigger' do before :each do @puppet_trigger['schedule'] = 'weekly' end it "should default 'every' to 1" do trigger['type']['weeks_interval'].should == 1 end it "should use the specified value for 'every'" do @puppet_trigger['every'] = 4 trigger['type']['weeks_interval'].should == 4 end it "should default 'day_of_week' to be every day of the week" do trigger['type']['days_of_week'].should == Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::TUESDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::THURSDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SATURDAY | Win32::TaskScheduler::SUNDAY end it "should use the specified value for 'day_of_week'" do @puppet_trigger['day_of_week'] = ['mon', 'wed', 'fri'] trigger['type']['days_of_week'].should == Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now trigger['start_year'].should == today.year trigger['start_month'].should == today.month trigger['start_day'].should == today.day end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'weekly', 'every' => 1, 'day_of_week' => 'mon'}} end end shared_examples_for 'a monthly schedule' do it "should default 'months' to be every month" do trigger['type']['months'].should == Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::MARCH | Win32::TaskScheduler::APRIL | Win32::TaskScheduler::MAY | Win32::TaskScheduler::JUNE | Win32::TaskScheduler::JULY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::OCTOBER | Win32::TaskScheduler::NOVEMBER | Win32::TaskScheduler::DECEMBER end it "should use the specified value for 'months'" do @puppet_trigger['months'] = [2, 8] trigger['type']['months'].should == Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST end end describe 'when given a monthly date-based trigger' do before :each do @puppet_trigger['schedule'] = 'monthly' @puppet_trigger['on'] = [7, 14] end it_behaves_like 'a monthly schedule' it "should not allow 'which_occurrence' to be specified" do @puppet_trigger['which_occurrence'] = 'first' expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should not allow 'day_of_week' to be specified" do @puppet_trigger['day_of_week'] = 'mon' expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should require 'on'" do @puppet_trigger.delete('on') expect {trigger}.to raise_error( Puppet::Error, /Don't know how to create a 'monthly' schedule with the options: schedule, start_date, start_time/ ) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now trigger['start_year'].should == today.year trigger['start_month'].should == today.month trigger['start_day'].should == today.day end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'monthly', 'months' => 1, 'on' => 1}} end end describe 'when given a monthly day-of-week-based trigger' do before :each do @puppet_trigger['schedule'] = 'monthly' @puppet_trigger['which_occurrence'] = 'first' @puppet_trigger['day_of_week'] = 'mon' end it_behaves_like 'a monthly schedule' it "should not allow 'on' to be specified" do @puppet_trigger['on'] = 15 expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should require 'which_occurrence'" do @puppet_trigger.delete('which_occurrence') expect {trigger}.to raise_error( Puppet::Error, /which_occurrence must be specified when creating a monthly day-of-week based trigger/ ) end it "should require 'day_of_week'" do @puppet_trigger.delete('day_of_week') expect {trigger}.to raise_error( Puppet::Error, /day_of_week must be specified when creating a monthly day-of-week based trigger/ ) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now trigger['start_year'].should == today.year trigger['start_month'].should == today.month trigger['start_day'].should == today.day end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'monthly', 'months' => 1, 'which_occurrence' => 'first', 'day_of_week' => 'mon'}} end end end describe '#validate_trigger' do let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it 'should succeed if all passed triggers translate from hashes to triggers' do triggers_to_validate = [ {'schedule' => 'once', 'start_date' => '2011-09-13', 'start_time' => '13:50'}, {'schedule' => 'weekly', 'start_date' => '2011-09-13', 'start_time' => '13:50', 'day_of_week' => 'mon'} ] provider.validate_trigger(triggers_to_validate).should == true end it 'should use the exception from translate_hash_to_trigger when it fails' do triggers_to_validate = [ {'schedule' => 'once', 'start_date' => '2011-09-13', 'start_time' => '13:50'}, {'schedule' => 'monthly', 'this is invalid' => true} ] expect {provider.validate_trigger(triggers_to_validate)}.to raise_error( Puppet::Error, /#{Regexp.escape("Unknown trigger option(s): ['this is invalid']")}/ ) end end describe '#flush' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :ensure => @ensure ) end before :each do @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) @command = 'C:\Windows\System32\notepad.exe' end describe 'when :ensure is :present' do before :each do @ensure = :present end it 'should save the task' do @mock_task.expects(:save) resource.provider.flush end it 'should fail if the command is not specified' do resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :ensure => @ensure ) expect { resource.provider.flush }.to raise_error( Puppet::Error, 'Parameter command is required.' ) end end describe 'when :ensure is :absent' do before :each do @ensure = :absent @mock_task.stubs(:activate) end it 'should not save the task if :ensure is :absent' do @mock_task.expects(:save).never resource.provider.flush end it 'should not fail if the command is not specified' do @mock_task.stubs(:save) resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :ensure => @ensure ) resource.provider.flush end end end describe 'property setter methods' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe' ) end before :each do @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end describe '#command=' do it 'should set the application_name on the task' do @mock_task.expects(:application_name=).with('C:\Windows\System32\notepad.exe') resource.provider.command = 'C:\Windows\System32\notepad.exe' end end describe '#arguments=' do it 'should set the parameters on the task' do @mock_task.expects(:parameters=).with(['/some /arguments /here']) resource.provider.arguments = ['/some /arguments /here'] end end describe '#working_dir=' do it 'should set the working_directory on the task' do @mock_task.expects(:working_directory=).with('C:\Windows\System32') resource.provider.working_dir = 'C:\Windows\System32' end end describe '#enabled=' do it 'should set the disabled flag if the task should be disabled' do @mock_task.stubs(:flags).returns(0) @mock_task.expects(:flags=).with(Win32::TaskScheduler::DISABLED) resource.provider.enabled = :false end it 'should clear the disabled flag if the task should be enabled' do @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) @mock_task.expects(:flags=).with(0) resource.provider.enabled = :true end end describe '#trigger=' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :trigger => @trigger ) end before :each do @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end it 'should not consider all duplicate current triggers in sync with a single desired trigger' do @trigger = {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'} current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 1}, {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 2}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:delete_trigger).with(1) @mock_task.expects(:delete_trigger).with(2) resource.provider.trigger = @trigger end it 'should remove triggers not defined in the resource' do @trigger = {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'} current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, {'schedule' => 'once', 'start_date' => '2012-09-15', 'start_time' => '15:10', 'index' => 1}, {'schedule' => 'once', 'start_date' => '2013-09-15', 'start_time' => '15:10', 'index' => 2}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:delete_trigger).with(1) @mock_task.expects(:delete_trigger).with(2) resource.provider.trigger = @trigger end it 'should add triggers defined in the resource, but not found on the system' do @trigger = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'}, {'schedule' => 'once', 'start_date' => '2012-09-15', 'start_time' => '15:10'}, {'schedule' => 'once', 'start_date' => '2013-09-15', 'start_time' => '15:10'}, ] current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[1])) @mock_task.expects(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[2])) resource.provider.trigger = @trigger end end describe '#user=', :if => Puppet.features.microsoft_windows? do before :each do @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end it 'should use nil for user and password when setting the user to the SYSTEM account' do Puppet::Util::Windows::SID.stubs(:name_to_sid).with('system').returns('SYSTEM SID') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'system' ) @mock_task.expects(:set_account_information).with(nil, nil) resource.provider.user = 'system' end it 'should use the specified user and password when setting the user to anything other than SYSTEM' do Puppet::Util::Windows::SID.stubs(:name_to_sid).with('my_user_name').returns('SID A') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'my_user_name', :password => 'my password' ) @mock_task.expects(:set_account_information).with('my_user_name', 'my password') resource.provider.user = 'my_user_name' end end end describe '#create' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :enabled => @enabled, :command => @command, :arguments => @arguments, :working_dir => @working_dir, :trigger => { 'schedule' => 'once', 'start_date' => '2011-09-27', 'start_time' => '17:00' } ) end before :each do @enabled = :true @command = 'C:\Windows\System32\notepad.exe' @arguments = '/a /list /of /arguments' @working_dir = 'C:\Windows\Some\Directory' @mock_task = mock @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) @mock_task.stubs(:application_name=) @mock_task.stubs(:parameters=) @mock_task.stubs(:working_directory=) @mock_task.stubs(:set_account_information) @mock_task.stubs(:flags) @mock_task.stubs(:flags=) @mock_task.stubs(:trigger_count).returns(0) @mock_task.stubs(:trigger=) @mock_task.stubs(:save) Win32::TaskScheduler.stubs(:new).returns(@mock_task) described_class.any_instance.stubs(:sync_triggers) end it 'should set the command' do resource.provider.expects(:command=).with(@command) resource.provider.create end it 'should set the arguments' do resource.provider.expects(:arguments=).with(@arguments) resource.provider.create end it 'should set the working_dir' do resource.provider.expects(:working_dir=).with(@working_dir) resource.provider.create end it "should set the user" do resource.provider.expects(:user=).with(:system) resource.provider.create end it 'should set the enabled property' do resource.provider.expects(:enabled=) resource.provider.create end it 'should sync triggers' do resource.provider.expects(:trigger=) resource.provider.create end end end