diff options
| author | Stig Sandbeck Mathisen <ssm@debian.org> | 2013-05-26 17:54:29 +0200 |
|---|---|---|
| committer | Stig Sandbeck Mathisen <ssm@debian.org> | 2013-05-26 17:54:29 +0200 |
| commit | 1a1e7395553ee2244cf3b9c75ac5ce84ebbc4775 (patch) | |
| tree | 7072a41b5b20a4a134607052954310c69867ed7a | |
| parent | 99de0b815d9c05804ddda33d5baa94b23ce1c39e (diff) | |
| parent | 025f00d05226e74a8ae68b2b16122b17a9746f2c (diff) | |
| download | puppet-upstream/3.2.1.tar.gz | |
Imported Upstream version 3.2.1upstream/3.2.1
495 files changed, 28161 insertions, 5816 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 818bb37ac..96665c12d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ top of things. ## Submitting Changes -* Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign). +* Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). * Push your changes to a topic branch in your fork of the repository. * Submit a pull request to the repository in the puppetlabs organization. * Update your Redmine ticket to mark that you have submitted code and are ready for it to be reviewed. @@ -59,7 +59,7 @@ top of things. * [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) * [Bug tracker (Redmine)](http://projects.puppetlabs.com) -* [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) +* [Contributor License Agreement](http://links.puppetlabs.com/cla) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) * #puppet-dev IRC channel on freenode.org @@ -1,8 +1,8 @@ -source :rubygems +source "https://rubygems.org" -def location_for(place) +def location_for(place, fake_version = nil) if place =~ /^(git:[^#]*)#(.*)/ - [{ :git => $1, :branch => $2, :require => false }] + [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else @@ -10,21 +10,33 @@ def location_for(place) end end -group(:development, :test) do - gem "puppet", *location_for('file://.') - gem "facter", *location_for(ENV['FACTER_LOCATION'] || '~> 1.6') - gem "hiera", *location_for(ENV['HIERA_LOCATION'] || '~> 1.0') +# C Ruby (MRI) or Rubinius, but NOT Windows +platforms :ruby do + gem 'pry', :group => :development + gem 'yard', :group => :development + gem 'redcarpet', :group => :development + gem "racc", "~> 1.4", :group => :development +end + +gem "puppet", :path => File.dirname(__FILE__), :require => false +gem "facter", *location_for(ENV['FACTER_LOCATION'] || '~> 1.6') +gem "hiera", *location_for(ENV['HIERA_LOCATION'] || '~> 1.0') +gem "rake", :require => false +gem "rspec", "~> 2.11.0", :require => false +gem "mocha", "~> 0.10.5", :require => false +gem "rgen", "0.6.2", :require => false + +gem "yarjuf", "~> 1.0" + +group(:extra) do gem "rack", "~> 1.4", :require => false - gem "rake", :require => false - gem "rspec", "~> 2.11.0", :require => false - gem "mocha", "~> 0.10.5", :require => false - gem "activerecord", *location_for('~> 3.0.7') - gem "couchrest", *location_for('~> 1.0') - gem "net-ssh", *location_for('~> 2.1') - gem "puppetlabs_spec_helper" - gem "sqlite3" - gem "stomp" - gem "tzinfo" + gem "activerecord", '~> 3.0.7', :require => false + gem "couchrest", '~> 1.0', :require => false + gem "net-ssh", '~> 2.1', :require => false + gem "puppetlabs_spec_helper", :require => false + gem "sqlite3", :require => false + gem "stomp", :require => false + gem "tzinfo", :require => false end platforms :mswin, :mingw do @@ -37,8 +49,9 @@ platforms :mswin, :mingw do gem "win32-service", "~> 0.7.2", :require => false gem "win32-taskscheduler", "~> 0.2.2", :require => false gem "win32console", "~> 1.3.2", :require => false - gem "windows-api", "~> 0.4.1", :require => false + gem "windows-api", "~> 0.4.2", :require => false gem "windows-pr", "~> 1.2.1", :require => false + gem "minitar", "~> 0.5.4", :require => false end if File.exists? "#{__FILE__}.local" diff --git a/README_DEVELOPER.md b/README_DEVELOPER.md index fa5455b93..95c632f20 100644 --- a/README_DEVELOPER.md +++ b/README_DEVELOPER.md @@ -149,7 +149,7 @@ with the following actions. The trick is to symlink `gems` to `src`. $ cd /workspace $ ln -s src gems $ mkdir specifications - $ pushd specifications; ln -s ../gems/puppet/puppet.gemspec; popd + $ pushd specifications; ln -s ../gems/puppet/puppet.gemspec; ln -s ../gems/puppet/lib; popd $ export GEM_PATH="/workspace:${GEM_PATH}" $ gem list puppet @@ -157,6 +157,13 @@ This should list out puppet (2.7.19) +The final directory structure should look like this: + + /workspace/src --- git working directory + /gems -> src + /specifications/puppet.gemspec -> ../gems/puppet/puppet.gemspec + /lib -> ../gems/puppet/lib + ## Bundler ## With a source checkout of Puppet properly setup as a gem, dependencies can be @@ -177,6 +184,308 @@ installed using [Bundler](http://gembundler.com/) Using bundler (1.1.5) Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed. +# Running Tests # + +Puppet Labs projects use a common convention of using Rake to run unit tests. +The tests can be run with the following rake task: + + rake spec + # Or if using Bundler + bundle exec rake spec + +This allows the Rakefile to set up the environment beforehand if needed. This +method is how the unit tests are run in [Jenkins](https://jenkins.puppetlabs.com). + +Under the hood Puppet's tests use `rspec`. To run all of them, you can directly +use 'rspec': + + rspec + # Or if using Bundler + bundle exec rspec + +To run a single file's worth of tests (much faster!), give the filename, and use +the nested format to see the descriptions: + + rspec spec/unit/ssl/host_spec.rb --format nested + +# A brief introduction to testing in Puppet + +Puppet relies heavily on automated testing to ensure that Puppet behaves as +expected and that new features don't interfere with existing behavior. There are +three primary sets of tests that Puppet uses: _unit tests_, _integration tests_, +and _acceptance tests_. + +- - - + +Unit tests are used to test the individual components of Puppet to ensure that +they function as expected in isolation. Unit tests are designed to hide the +actual system implementations and provide canned information so that only the +intended behavior is tested, rather than the targeted code and everything else +connected to it. Unit tests should never affect the state of the system that's +running the test. + +- - - + +Integration tests serve to test different units of code together to ensure that +they interact correctly. While individual methods might perform correctly, when +used with the rest of the system they might fail, so integration tests are a +higher level version of unit tests that serve to check the behavior of +individual subsystems. + +All of the unit and integration tests for Puppet are kept in the spec/ directory. + +- - - + +Acceptance tests are used to test high level behaviors of Puppet that deal with +a number of concerns and aren't easily tested with normal unit tests. Acceptance +tests function by changing system state and checking the system after +the fact to make sure that the intended behavior occurred. Because of this +acceptance tests can be destructive, so the systems being tested should be +throwaway systems. + +All of the acceptance tests for Puppet are kept in the acceptance/tests/ +directory. + +## Puppet Continuous integration + + * Travis-ci (unit tests only): https://travis-ci.org/puppetlabs/puppet/ + * Jenkins (unit and acceptance tests): https://jenkins.puppetlabs.com/view/Puppet%20FOSS/ + +## RSpec + +Puppet uses RSpec to perform unit and integration tests. RSpec handles a number +of concerns to make testing easier: + + * Executing examples and ensuring the actual behavior matches the expected behavior (examples) + * Grouping tests (describe and contexts) + * Setting up test environments and cleaning up afterwards (before and after blocks) + * Isolating tests (mocks and stubs) + +#### Examples and expectations + +At the most basic level, RSpec provides a framework for executing tests (which +are called examples) and ensuring that the actual behavior matches the expected +behavior (which are done with expectations) + +```ruby +# This is an example; it sets the test name and defines the test to run +specify "one equals one" do + # 'should' is an expectation; it adds a check to make sure that the left argument + # matches the right argument + 1.should == 1 +end + +# Examples can be declared with either 'it' or 'specify' +it "one doesn't equal two" do + 1.should_not == 2 +end +``` + +Good examples generally do as little setup as possible and only test one or two +things; it makes tests easier to understand and easier to debug. + +More complete documentation on expectations is available at https://www.relishapp.com/rspec/rspec-expectations/docs + +### Example groups + +Example groups are fairly self explanatory; they group similar examples into a +set. + +```ruby +describe "the number one" do + + it "is larger than zero" do + 1.should be > 0 + end + + it "is an odd number" do + 1.odd?.should be true + end + + it "is not nil" do + 1.should_not be_nil + end +end +``` + +Example groups have a number of uses that we'll get into later, but one of the +simplest demonstrations of what they do is how they help to format +documentation: + +``` +rspec ex.rb --format documentation + +the number one + is larger than zero + is an odd number + is not nil + +Finished in 0.00516 seconds +3 examples, 0 failures +``` + +### Setting up and tearing down tests + +Examples may require some setup before they can run, and might need to clean up +afterwards. `before` and `after` blocks can be used before this, and can be +used inside of example groups to limit how many examples they affect. + +```ruby + +describe "something that could warn" do + before :each do + # Disable warnings for this test + $VERBOSE = nil + end + + after do + # Enable warnings afterwards + $VERBOSE = true + end + + it "doesn't generate a warning" do + MY_CONSTANT = 1 + # reassigning a normally prints out 'warning: already initialized constant FOO' + MY_CONSTANT = 2 + end +end +``` + +### Setting up helper data + +Some examples may require setting up data before hand and making it available to +tests. RSpec provides helper methods with the `let` method call that can be used +inside of tests. + +```ruby +describe "a helper object" do + # This creates an array with three elements that we can retrieve in tests. A + # new copy will be made for each test. + let(:my_helper) do + ['foo', 'bar', 'baz'] + end + + it "should be an array" do + my_helper.should be_a_kind_of Array + end + + it "should have three elements" do + my_helper.should have(3).items + end +end +``` + +Like `before` blocks, helper objects like this are used to avoid doing a lot of +setup in individual examples and share setup between similar tests. + +### Isolating tests with stubs + +RSpec allows you to provide fake data during testing to make sure that +individual tests are only running the code being tested. You can stub out entire +objects, or just stub out individual methods on an object. When a method is +stubbed the method itself will never be called. + +While RSpec comes with its own stubbing framework, Puppet uses the Mocha +framework. + +A brief usage guide for Mocha is available at http://gofreerange.com/mocha/docs/#Usage, +and an overview of Mocha expectations is available at http://gofreerange.com/mocha/docs/Mocha/Expectation.html + +```ruby +describe "stubbing a method on an object" do + let(:my_helper) do + ['foo', 'bar', 'baz'] + end + + it 'should have three items before being stubbed' do + my_helper.size.should == 3 + end + + describe 'when stubbing the size' do + before do + my_helper.stubs(:size).returns 10 + end + + it 'should have the stubbed value for size' do + my_helper.size.should == 10 + end + end +end +``` + +Entire objects can be stubbed as well. + +```ruby +describe "stubbing an object" do + let(:my_helper) do + stub(:not_an_array, :size => 10) + end + + it 'should have the stubbed size' + my_helper.size.should == 10 + end +end +``` + +### Adding expectations with mocks + +It's possible to combine the concepts of stubbing and expectations so that a +method has to be called for the test to pass (like an expectation), and can +return a fixed value (like a stub). + +```ruby +describe "mocking a method on an object" do + let(:my_helper) do + ['foo', 'bar', 'baz'] + end + + describe "when mocking the size" do + before do + my_helper.expects(:size).returns 10 + end + + it "adds an expectation that a method was called" do + my_helper.size + end + end +end +``` + +Like stubs, entire objects can be mocked. + +```ruby +describe "mocking an object" do + let(:my_helper) do + mock(:not_an_array) + end + + before do + not_an_array.expects(:size).returns 10 + end + + it "adds an expectation that the method was called" do + not_an_array.size + end +end +``` + +### RSpec references + + * RSpec core docs: https://www.relishapp.com/rspec/rspec-core/docs + * RSpec guidelines with Ruby: http://betterspecs.org/ + +### Puppet-acceptance + +[puppet-acceptance]: https://github.com/puppetlabs/puppet-acceptance +[test::unit]: http://test-unit.rubyforge.org/ + +Puppet has a custom acceptance testing framework called +[puppet-acceptance][puppet-acceptance] for running acceptance tests. +Puppet-acceptance runs the tests by configuring one or more VMs, copying the +test cases onto the VMs, performing the tests and collecting the results, and +ensuring that the results match the intended behavior. It uses +[test::unit][test::unit] to perform the actual assertions. + # UTF-8 Handling # As Ruby 1.9 becomes more commonly used with Puppet, developers should be aware @@ -242,23 +551,32 @@ include `ext/envpuppet.bat` will help. To quickly run Puppet from source, assuming you already have Ruby installed from [rubyinstaller.org](http://rubyinstaller.org). - gem install sys-admin win32-process win32-dir win32-taskscheduler --no-rdoc --no-ri - gem install win32-service --platform=mswin32 --no-rdoc --no-ri --version 0.7.1 - net use Z: "\\vmware-host\Shared Folders" /persistent:yes - Z: - cd <path_to_puppet> - set PATH=%PATH%;Z:\<path_to_puppet>\ext - envpuppet puppet --version + C:\> cd C:\work\puppet + C:\work\puppet> set PATH=%PATH%;C:\work\puppet\ext + C:\work\puppet> envpuppet bundle install + C:\work\puppet> envpuppet puppet --version 2.7.9 -Some spec tests are known to fail on Windows, e.g. no mount provider -on Windows, so use the following rspec exclude filter: +When writing a test that cannot possibly run on Windows, e.g. there is +no mount type on windows, do the following: + + describe Puppet::MyClass, :unless => Puppet.features.microsoft_windows? do + .. + end + +If the test doesn't currently pass on Windows, e.g. due to on going porting, then use an rspec conditional pending block: + + pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do + <example1> + end + + pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do + <example2> + end - cd <path_to_puppet> - envpuppet rspec --tag ~fails_on_windows spec +Then run the test as: -This will give you a shared filesystem with your Mac and allow you to run -Puppet directly from source without using install.rb or copying files around. + C:\work\puppet> envpuppet bundle exec rspec spec ## Common Issues ## @@ -57,12 +57,16 @@ if File.exist?(build_defs_file) end task :default do - sh %{rake -T} + sh %{rake -T} end -if defined?(RSpec::Core::RakeTask) - RSpec::Core::RakeTask.new do |t| - t.pattern ='spec/{unit,integration}/**/*.rb' - t.fail_on_error = true +task :spec do + sh %{rspec -fd spec} +end + +namespace "ci" do + task :spec do + ENV["LOG_SPEC_ORDER"] = "true" + sh %{rspec -r yarjuf -f JUnit -o result.xml -fd spec} end end diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index adb37d4ac..8ef692756 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,15 +2,14 @@ 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-natty-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' +cows: 'base-lucid-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-raring-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-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-5-i386 pl-6-i386 fedora-16-i386 fedora-17-i386' -rc_mocks: 'pl-5-i386-dev pl-6-i386-dev fedora-16-i386-dev fedora-17-i386-dev' +final_mocks: 'pl-el-5-i386 pl-el-6-i386 pl-fedora-17-i386 pl-fedora-18-i386' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE diff --git a/ext/debian/changelog b/ext/debian/changelog index 7d12e0eef..8d82739ce 100644 --- a/ext/debian/changelog +++ b/ext/debian/changelog @@ -1,8 +1,14 @@ -puppet (3.1.1-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low +puppet (3.2.1-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low - * Update to version 3.1.1-1puppetlabs1 + * Update to version 3.2.1-1puppetlabs1 - -- Puppet Labs Release <info@puppetlabs.com> Fri, 08 Mar 2013 15:03:50 -0800 + -- Puppet Labs Release <info@puppetlabs.com> Wed, 22 May 2013 10:29:49 -0700 + +puppet (3.2.0-0.1rc0puppetlabs1) lucid oneiric precise unstable sid squeeze wheezy precise; urgency=low + + * Add ruby-rgen dependency for new parser in Puppet 3.2 + + -- Matthaus Owens <matthaus@puppetlabs.com> Fri, 12 Apr 2013 14:45:14 +0000 puppet (3.1.0-0.1rc1puppetlabs1) lucid natty oneiric precise unstable sid squeeze wheezy precise; urgency=low diff --git a/ext/debian/control b/ext/debian/control index 910661f40..2eb4fd2ac 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -11,9 +11,9 @@ Homepage: http://projects.puppetlabs.com/projects/puppet Package: puppet-common Architecture: all -Depends: ${misc:Depends}, ruby | ruby-interpreter, libxmlrpc-ruby, libopenssl-ruby, libshadow-ruby1.8 | ruby-shadow , libaugeas-ruby1.8 | libaugeas-ruby1.9, adduser, lsb-base, sysv-rc (>= 2.86) | file-rc, hiera (>= 1.0.0), facter (>= 1.6.12) +Depends: ${misc:Depends}, ruby | ruby-interpreter, libxmlrpc-ruby, libopenssl-ruby, ruby-shadow | libshadow-ruby1.8, libaugeas-ruby | libaugeas-ruby1.9.1 | libaugeas-ruby1.8, adduser, lsb-base, sysv-rc (>= 2.86) | file-rc, hiera (>= 1.0.0), facter (>= 1.6.12), ruby-rgen Recommends: lsb-release, debconf-utils -Suggests: libselinux-ruby1.8 | ruby-selinux, librrd-ruby1.8 | librrd-ruby1.9 +Suggests: ruby-selinux | libselinux-ruby1.8, librrd-ruby1.9.1 | librrd-ruby1.8 Breaks: puppet (<< 2.6.0~rc2-1), puppetmaster (<< 0.25.4-1) Provides: hiera-puppet Conflicts: hiera-puppet @@ -62,7 +62,7 @@ Replaces: puppetmaster (<< 2.6.1~rc2-1) Suggests: apache2 | nginx, puppet-el, vim-puppet, stompserver, ruby-stomp | libstomp-ruby1.8, rdoc, ruby-ldap | libldap-ruby1.8, puppetdb-terminus Description: Puppet master common scripts - This package contains common scripts for the puppet master, + This package contains common scripts for the puppet master, which is the server hosting manifests and files for the puppet nodes. . Puppet lets you centrally manage every important aspect of your system @@ -80,8 +80,8 @@ Package: puppetmaster Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppetmaster-common (= ${source:Version}), facter (>= 1.6.12), lsb-base Breaks: puppet (<< 0.24.7-1) -Suggests: apache2 | nginx, puppet-el, vim-puppet, stompserver, libstomp-ruby1.8 | ruby-stomp, - rdoc, libldap-ruby1.8 | ruby-ldap, puppetdb-terminus +Suggests: apache2 | nginx, puppet-el, vim-puppet, stompserver, ruby-stomp | libstomp-ruby1.8, + rdoc, ruby-ldap | libldap-ruby1.8, puppetdb-terminus Description: Centralized configuration management - master startup and compatibility scripts This package contains the startup and compatibility scripts for the puppet master, which is the server hosting manifests and files for the puppet nodes. @@ -136,7 +136,7 @@ Description: syntax highlighting for puppet manifests in emacs Package: puppet-testsuite Architecture: all -Depends: ${misc:Depends}, ruby | ruby-interpreter, puppet-common (= ${source:Version}), facter (>= 1.6.12), lsb-base, rails (>= 1.2.3-2), rdoc, libldap-ruby1.8 | ruby-ldap, librspec-ruby | ruby-rspec, git-core, libmocha-ruby1.8 | ruby-mocha +Depends: ${misc:Depends}, ruby | ruby-interpreter, puppet-common (= ${source:Version}), facter (>= 1.6.12), lsb-base, rails (>= 1.2.3-2), rdoc, ruby-ldap | libldap-ruby1.8, ruby-rspec | librspec-ruby, git-core, ruby-mocha | libmocha-ruby1.8 Recommends: cron Description: Centralized configuration management - test suite This package provides all the tests from the upstream puppet source code. diff --git a/ext/envpuppet b/ext/envpuppet index daea7c526..1db000215 100755 --- a/ext/envpuppet +++ b/ext/envpuppet @@ -1,4 +1,4 @@ -#! /bin/bash +#! /bin/sh # # Jeff McCune <jeff@puppetlabs.com> # 2010-10-20 @@ -34,7 +34,7 @@ set -e set -u -if [[ "${1:-}" == "--help" ]]; then +if [ "${1:-}" = "--help" ]; then cat <<EO_HELP This command reconfigures the environment once for development. It is designed to wrap around any other command, specifically puppet @@ -99,7 +99,7 @@ myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/facter/lib" mypath="${mypath}:${ENVPUPPET_BASEDIR}/hiera/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/hiera/lib" -if [[ "${ENVPUPPET_BLEEDING:-}" == "true" ]]; then +if [ "${ENVPUPPET_BLEEDING:-}" = "true" ]; then # git://github.com/puppetlabs/facter.git mypath="${mypath}:${ENVPUPPET_BASEDIR}/puppet-interfaces/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/puppet-interfaces/lib" @@ -126,7 +126,7 @@ export ENVPUPPET_OLD_RUBYLIB="${RUBYLIB:-}" export PATH="${mypath%%:}" export RUBYLIB="${myrubylib%%:}" -if [[ $# -eq 0 ]]; then +if [ $# -eq 0 ]; then echo "export ENVPUPPET_OLD_PATH='${ENVPUPPET_OLD_PATH}'" echo "export ENVPUPPET_OLD_RUBYLIB='${ENVPUPPET_OLD_RUBYLIB}'" echo "export ENVPUPPET_BASEDIR='${ENVPUPPET_BASEDIR}'" diff --git a/ext/ips/puppet.p5m b/ext/ips/puppet.p5m index b472e2a31..a3aa39874 100644 --- a/ext/ips/puppet.p5m +++ b/ext/ips/puppet.p5m @@ -1,6 +1,6 @@ -set name=pkg.fmri value=pkg://puppetlabs.com/system/management/puppet@3.1.1,12.0.0-0 +set name=pkg.fmri value=pkg://puppetlabs.com/system/management/puppet@3.2.1,11.4.2-0 set name=pkg.summary value="Puppet, an automated configuration management tool" -set name=pkg.human-version value="3.1.1" +set name=pkg.human-version value="3.2.1" set name=pkg.description value="Puppet, an automated configuration management tool" set name=info.classification value="org.opensolaris.category.2008:System/Administration and Configuration" set name=org.opensolaris.consolidation value="puppet" diff --git a/ext/osx/preflight b/ext/osx/preflight index d8edb4c87..76c711201 100644 --- a/ext/osx/preflight +++ b/ext/osx/preflight @@ -15,79 +15,167 @@ -/bin/rm -Rf "${3}/semver.rb" +/bin/rm -Rf "${3}/hiera/backend/puppet_backend.rb" + +/bin/rm -Rf "${3}/hiera/scope.rb" /bin/rm -Rf "${3}/hiera_puppet.rb" -/bin/rm -Rf "${3}/puppet/face/ca.rb" +/bin/rm -Rf "${3}/puppet.rb" -/bin/rm -Rf "${3}/puppet/face/certificate_revocation_list.rb" +/bin/rm -Rf "${3}/puppet/agent.rb" -/bin/rm -Rf "${3}/puppet/face/resource.rb" +/bin/rm -Rf "${3}/puppet/agent/disabler.rb" -/bin/rm -Rf "${3}/puppet/face/module.rb" +/bin/rm -Rf "${3}/puppet/agent/locker.rb" -/bin/rm -Rf "${3}/puppet/face/plugin.rb" +/bin/rm -Rf "${3}/puppet/application.rb" -/bin/rm -Rf "${3}/puppet/face/config.rb" +/bin/rm -Rf "${3}/puppet/application/agent.rb" -/bin/rm -Rf "${3}/puppet/face/status.rb" +/bin/rm -Rf "${3}/puppet/application/apply.rb" -/bin/rm -Rf "${3}/puppet/face/certificate_request.rb" +/bin/rm -Rf "${3}/puppet/application/ca.rb" -/bin/rm -Rf "${3}/puppet/face/catalog/select.rb" +/bin/rm -Rf "${3}/puppet/application/catalog.rb" -/bin/rm -Rf "${3}/puppet/face/man.rb" +/bin/rm -Rf "${3}/puppet/application/cert.rb" -/bin/rm -Rf "${3}/puppet/face/file/download.rb" +/bin/rm -Rf "${3}/puppet/application/certificate.rb" -/bin/rm -Rf "${3}/puppet/face/file/store.rb" +/bin/rm -Rf "${3}/puppet/application/certificate_request.rb" -/bin/rm -Rf "${3}/puppet/face/certificate.rb" +/bin/rm -Rf "${3}/puppet/application/certificate_revocation_list.rb" -/bin/rm -Rf "${3}/puppet/face/instrumentation_listener.rb" +/bin/rm -Rf "${3}/puppet/application/config.rb" -/bin/rm -Rf "${3}/puppet/face/node/clean.rb" +/bin/rm -Rf "${3}/puppet/application/describe.rb" -/bin/rm -Rf "${3}/puppet/face/file.rb" +/bin/rm -Rf "${3}/puppet/application/device.rb" -/bin/rm -Rf "${3}/puppet/face/help.rb" +/bin/rm -Rf "${3}/puppet/application/doc.rb" -/bin/rm -Rf "${3}/puppet/face/parser.rb" +/bin/rm -Rf "${3}/puppet/application/face_base.rb" -/bin/rm -Rf "${3}/puppet/face/instrumentation_probe.rb" +/bin/rm -Rf "${3}/puppet/application/facts.rb" -/bin/rm -Rf "${3}/puppet/face/secret_agent.rb" +/bin/rm -Rf "${3}/puppet/application/file.rb" -/bin/rm -Rf "${3}/puppet/face/resource_type.rb" +/bin/rm -Rf "${3}/puppet/application/filebucket.rb" -/bin/rm -Rf "${3}/puppet/face/node.rb" +/bin/rm -Rf "${3}/puppet/application/help.rb" -/bin/rm -Rf "${3}/puppet/face/facts.rb" +/bin/rm -Rf "${3}/puppet/application/indirection_base.rb" -/bin/rm -Rf "${3}/puppet/face/module/search.rb" +/bin/rm -Rf "${3}/puppet/application/inspect.rb" -/bin/rm -Rf "${3}/puppet/face/module/build.rb" +/bin/rm -Rf "${3}/puppet/application/instrumentation_data.rb" -/bin/rm -Rf "${3}/puppet/face/module/list.rb" +/bin/rm -Rf "${3}/puppet/application/instrumentation_listener.rb" -/bin/rm -Rf "${3}/puppet/face/module/uninstall.rb" +/bin/rm -Rf "${3}/puppet/application/instrumentation_probe.rb" -/bin/rm -Rf "${3}/puppet/face/module/upgrade.rb" +/bin/rm -Rf "${3}/puppet/application/key.rb" -/bin/rm -Rf "${3}/puppet/face/module/changes.rb" +/bin/rm -Rf "${3}/puppet/application/kick.rb" -/bin/rm -Rf "${3}/puppet/face/module/generate.rb" +/bin/rm -Rf "${3}/puppet/application/man.rb" -/bin/rm -Rf "${3}/puppet/face/module/install.rb" +/bin/rm -Rf "${3}/puppet/application/master.rb" -/bin/rm -Rf "${3}/puppet/face/report.rb" +/bin/rm -Rf "${3}/puppet/application/module.rb" + +/bin/rm -Rf "${3}/puppet/application/node.rb" + +/bin/rm -Rf "${3}/puppet/application/parser.rb" + +/bin/rm -Rf "${3}/puppet/application/plugin.rb" + +/bin/rm -Rf "${3}/puppet/application/queue.rb" + +/bin/rm -Rf "${3}/puppet/application/report.rb" + +/bin/rm -Rf "${3}/puppet/application/resource.rb" + +/bin/rm -Rf "${3}/puppet/application/resource_type.rb" + +/bin/rm -Rf "${3}/puppet/application/secret_agent.rb" + +/bin/rm -Rf "${3}/puppet/application/status.rb" + +/bin/rm -Rf "${3}/puppet/configurer.rb" + +/bin/rm -Rf "${3}/puppet/configurer/downloader.rb" + +/bin/rm -Rf "${3}/puppet/configurer/fact_handler.rb" + +/bin/rm -Rf "${3}/puppet/configurer/plugin_handler.rb" + +/bin/rm -Rf "${3}/puppet/daemon.rb" + +/bin/rm -Rf "${3}/puppet/data_binding.rb" + +/bin/rm -Rf "${3}/puppet/defaults.rb" + +/bin/rm -Rf "${3}/puppet/dsl.rb" + +/bin/rm -Rf "${3}/puppet/dsl/resource_api.rb" + +/bin/rm -Rf "${3}/puppet/dsl/resource_type_api.rb" + +/bin/rm -Rf "${3}/puppet/error.rb" + +/bin/rm -Rf "${3}/puppet/external/base64.rb" + +/bin/rm -Rf "${3}/puppet/external/dot.rb" + +/bin/rm -Rf "${3}/puppet/external/lock.rb" + +/bin/rm -Rf "${3}/puppet/external/nagios.rb" + +/bin/rm -Rf "${3}/puppet/external/nagios/base.rb" + +/bin/rm -Rf "${3}/puppet/external/nagios/grammar.ry" + +/bin/rm -Rf "${3}/puppet/external/nagios/makefile" + +/bin/rm -Rf "${3}/puppet/external/nagios/parser.rb" + +/bin/rm -Rf "${3}/puppet/external/pson/common.rb" + +/bin/rm -Rf "${3}/puppet/external/pson/pure.rb" + +/bin/rm -Rf "${3}/puppet/external/pson/pure/generator.rb" + +/bin/rm -Rf "${3}/puppet/external/pson/pure/parser.rb" + +/bin/rm -Rf "${3}/puppet/external/pson/version.rb" + +/bin/rm -Rf "${3}/puppet/face.rb" + +/bin/rm -Rf "${3}/puppet/face/ca.rb" /bin/rm -Rf "${3}/puppet/face/catalog.rb" -/bin/rm -Rf "${3}/puppet/face/instrumentation_data.rb" +/bin/rm -Rf "${3}/puppet/face/catalog/select.rb" -/bin/rm -Rf "${3}/puppet/face/help/man.erb" +/bin/rm -Rf "${3}/puppet/face/certificate.rb" + +/bin/rm -Rf "${3}/puppet/face/certificate_request.rb" + +/bin/rm -Rf "${3}/puppet/face/certificate_revocation_list.rb" + +/bin/rm -Rf "${3}/puppet/face/config.rb" + +/bin/rm -Rf "${3}/puppet/face/facts.rb" + +/bin/rm -Rf "${3}/puppet/face/file.rb" + +/bin/rm -Rf "${3}/puppet/face/file/download.rb" + +/bin/rm -Rf "${3}/puppet/face/file/store.rb" + +/bin/rm -Rf "${3}/puppet/face/help.rb" /bin/rm -Rf "${3}/puppet/face/help/action.erb" @@ -95,1465 +183,1499 @@ /bin/rm -Rf "${3}/puppet/face/help/global.erb" +/bin/rm -Rf "${3}/puppet/face/help/man.erb" + +/bin/rm -Rf "${3}/puppet/face/instrumentation_data.rb" + +/bin/rm -Rf "${3}/puppet/face/instrumentation_listener.rb" + +/bin/rm -Rf "${3}/puppet/face/instrumentation_probe.rb" + /bin/rm -Rf "${3}/puppet/face/key.rb" -/bin/rm -Rf "${3}/puppet/application.rb" +/bin/rm -Rf "${3}/puppet/face/man.rb" -/bin/rm -Rf "${3}/puppet/configurer.rb" +/bin/rm -Rf "${3}/puppet/face/module.rb" -/bin/rm -Rf "${3}/puppet/relationship.rb" +/bin/rm -Rf "${3}/puppet/face/module/build.rb" -/bin/rm -Rf "${3}/puppet/resource.rb" +/bin/rm -Rf "${3}/puppet/face/module/changes.rb" -/bin/rm -Rf "${3}/puppet/module.rb" +/bin/rm -Rf "${3}/puppet/face/module/generate.rb" -/bin/rm -Rf "${3}/puppet/module_tool.rb" +/bin/rm -Rf "${3}/puppet/face/module/install.rb" -/bin/rm -Rf "${3}/puppet/status.rb" +/bin/rm -Rf "${3}/puppet/face/module/list.rb" -/bin/rm -Rf "${3}/puppet/defaults.rb" +/bin/rm -Rf "${3}/puppet/face/module/search.rb" -/bin/rm -Rf "${3}/puppet/data_binding.rb" +/bin/rm -Rf "${3}/puppet/face/module/uninstall.rb" -/bin/rm -Rf "${3}/puppet/reports/rrdgraph.rb" +/bin/rm -Rf "${3}/puppet/face/module/upgrade.rb" -/bin/rm -Rf "${3}/puppet/reports/store.rb" +/bin/rm -Rf "${3}/puppet/face/node.rb" -/bin/rm -Rf "${3}/puppet/reports/log.rb" +/bin/rm -Rf "${3}/puppet/face/node/clean.rb" -/bin/rm -Rf "${3}/puppet/reports/tagmail.rb" +/bin/rm -Rf "${3}/puppet/face/parser.rb" -/bin/rm -Rf "${3}/puppet/reports/http.rb" +/bin/rm -Rf "${3}/puppet/face/plugin.rb" -/bin/rm -Rf "${3}/puppet/provider/zpool/zpool.rb" +/bin/rm -Rf "${3}/puppet/face/report.rb" -/bin/rm -Rf "${3}/puppet/provider/vlan/cisco.rb" +/bin/rm -Rf "${3}/puppet/face/resource.rb" -/bin/rm -Rf "${3}/puppet/provider/command.rb" +/bin/rm -Rf "${3}/puppet/face/resource_type.rb" -/bin/rm -Rf "${3}/puppet/provider/zfs/zfs.rb" +/bin/rm -Rf "${3}/puppet/face/secret_agent.rb" -/bin/rm -Rf "${3}/puppet/provider/service/debian.rb" +/bin/rm -Rf "${3}/puppet/face/status.rb" -/bin/rm -Rf "${3}/puppet/provider/service/service.rb" +/bin/rm -Rf "${3}/puppet/feature/base.rb" -/bin/rm -Rf "${3}/puppet/provider/service/systemd.rb" +/bin/rm -Rf "${3}/puppet/feature/eventlog.rb" -/bin/rm -Rf "${3}/puppet/provider/service/openrc.rb" +/bin/rm -Rf "${3}/puppet/feature/libuser.rb" -/bin/rm -Rf "${3}/puppet/provider/service/gentoo.rb" +/bin/rm -Rf "${3}/puppet/feature/pson.rb" -/bin/rm -Rf "${3}/puppet/provider/service/daemontools.rb" +/bin/rm -Rf "${3}/puppet/feature/rack.rb" -/bin/rm -Rf "${3}/puppet/provider/service/launchd.rb" +/bin/rm -Rf "${3}/puppet/feature/rails.rb" -/bin/rm -Rf "${3}/puppet/provider/service/init.rb" +/bin/rm -Rf "${3}/puppet/feature/rdoc1.rb" -/bin/rm -Rf "${3}/puppet/provider/service/base.rb" +/bin/rm -Rf "${3}/puppet/feature/rubygems.rb" -/bin/rm -Rf "${3}/puppet/provider/service/redhat.rb" +/bin/rm -Rf "${3}/puppet/feature/selinux.rb" -/bin/rm -Rf "${3}/puppet/provider/service/freebsd.rb" +/bin/rm -Rf "${3}/puppet/feature/ssh.rb" -/bin/rm -Rf "${3}/puppet/provider/service/runit.rb" +/bin/rm -Rf "${3}/puppet/feature/stomp.rb" -/bin/rm -Rf "${3}/puppet/provider/service/smf.rb" +/bin/rm -Rf "${3}/puppet/feature/zlib.rb" -/bin/rm -Rf "${3}/puppet/provider/service/src.rb" +/bin/rm -Rf "${3}/puppet/file_bucket.rb" -/bin/rm -Rf "${3}/puppet/provider/service/windows.rb" +/bin/rm -Rf "${3}/puppet/file_bucket/dipper.rb" -/bin/rm -Rf "${3}/puppet/provider/service/bsd.rb" +/bin/rm -Rf "${3}/puppet/file_bucket/file.rb" -/bin/rm -Rf "${3}/puppet/provider/service/upstart.rb" +/bin/rm -Rf "${3}/puppet/file_collection.rb" -/bin/rm -Rf "${3}/puppet/provider/maillist/mailman.rb" +/bin/rm -Rf "${3}/puppet/file_collection/lookup.rb" -/bin/rm -Rf "${3}/puppet/provider/exec/shell.rb" +/bin/rm -Rf "${3}/puppet/file_serving.rb" -/bin/rm -Rf "${3}/puppet/provider/exec/posix.rb" +/bin/rm -Rf "${3}/puppet/file_serving/base.rb" -/bin/rm -Rf "${3}/puppet/provider/exec/windows.rb" +/bin/rm -Rf "${3}/puppet/file_serving/configuration.rb" -/bin/rm -Rf "${3}/puppet/provider/group/aix.rb" +/bin/rm -Rf "${3}/puppet/file_serving/configuration/parser.rb" -/bin/rm -Rf "${3}/puppet/provider/group/ldap.rb" +/bin/rm -Rf "${3}/puppet/file_serving/content.rb" -/bin/rm -Rf "${3}/puppet/provider/group/directoryservice.rb" +/bin/rm -Rf "${3}/puppet/file_serving/fileset.rb" -/bin/rm -Rf "${3}/puppet/provider/group/groupadd.rb" +/bin/rm -Rf "${3}/puppet/file_serving/metadata.rb" -/bin/rm -Rf "${3}/puppet/provider/group/windows_adsi.rb" +/bin/rm -Rf "${3}/puppet/file_serving/mount.rb" -/bin/rm -Rf "${3}/puppet/provider/group/pw.rb" +/bin/rm -Rf "${3}/puppet/file_serving/mount/file.rb" -/bin/rm -Rf "${3}/puppet/provider/parsedfile.rb" +/bin/rm -Rf "${3}/puppet/file_serving/mount/modules.rb" -/bin/rm -Rf "${3}/puppet/provider/exec.rb" +/bin/rm -Rf "${3}/puppet/file_serving/mount/plugins.rb" -/bin/rm -Rf "${3}/puppet/provider/package/hpux.rb" +/bin/rm -Rf "${3}/puppet/file_serving/terminus_helper.rb" -/bin/rm -Rf "${3}/puppet/provider/package/msi.rb" +/bin/rm -Rf "${3}/puppet/file_serving/terminus_selector.rb" -/bin/rm -Rf "${3}/puppet/provider/package/up2date.rb" +/bin/rm -Rf "${3}/puppet/forge.rb" -/bin/rm -Rf "${3}/puppet/provider/package/fink.rb" +/bin/rm -Rf "${3}/puppet/forge/cache.rb" -/bin/rm -Rf "${3}/puppet/provider/package/aix.rb" +/bin/rm -Rf "${3}/puppet/forge/errors.rb" -/bin/rm -Rf "${3}/puppet/provider/package/aptrpm.rb" +/bin/rm -Rf "${3}/puppet/forge/repository.rb" -/bin/rm -Rf "${3}/puppet/provider/package/pkgdmg.rb" +/bin/rm -Rf "${3}/puppet/indirector.rb" -/bin/rm -Rf "${3}/puppet/provider/package/pacman.rb" +/bin/rm -Rf "${3}/puppet/indirector/active_record.rb" -/bin/rm -Rf "${3}/puppet/provider/package/aptitude.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/active_record.rb" -/bin/rm -Rf "${3}/puppet/provider/package/pkgutil.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/compiler.rb" -/bin/rm -Rf "${3}/puppet/provider/package/pkg.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/json.rb" -/bin/rm -Rf "${3}/puppet/provider/package/windows/msi_package.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/queue.rb" -/bin/rm -Rf "${3}/puppet/provider/package/windows/exe_package.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/package/windows/package.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/static_compiler.rb" -/bin/rm -Rf "${3}/puppet/provider/package/blastwave.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/store_configs.rb" -/bin/rm -Rf "${3}/puppet/provider/package/sun.rb" +/bin/rm -Rf "${3}/puppet/indirector/catalog/yaml.rb" -/bin/rm -Rf "${3}/puppet/provider/package/zypper.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate/ca.rb" -/bin/rm -Rf "${3}/puppet/provider/package/ports.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate/disabled_ca.rb" -/bin/rm -Rf "${3}/puppet/provider/package/gem.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate/file.rb" -/bin/rm -Rf "${3}/puppet/provider/package/yumhelper.py" +/bin/rm -Rf "${3}/puppet/indirector/certificate/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/package/pip.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_request/ca.rb" -/bin/rm -Rf "${3}/puppet/provider/package/macports.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_request/disabled_ca.rb" -/bin/rm -Rf "${3}/puppet/provider/package/portupgrade.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_request/file.rb" -/bin/rm -Rf "${3}/puppet/provider/package/rpm.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_request/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/package/portage.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/ca.rb" -/bin/rm -Rf "${3}/puppet/provider/package/urpmi.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/disabled_ca.rb" -/bin/rm -Rf "${3}/puppet/provider/package/freebsd.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/file.rb" -/bin/rm -Rf "${3}/puppet/provider/package/openbsd.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/package/yum.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_status.rb" -/bin/rm -Rf "${3}/puppet/provider/package/pkgin.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_status/file.rb" -/bin/rm -Rf "${3}/puppet/provider/package/nim.rb" +/bin/rm -Rf "${3}/puppet/indirector/certificate_status/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/package/appdmg.rb" +/bin/rm -Rf "${3}/puppet/indirector/code.rb" -/bin/rm -Rf "${3}/puppet/provider/package/windows.rb" +/bin/rm -Rf "${3}/puppet/indirector/couch.rb" -/bin/rm -Rf "${3}/puppet/provider/package/dpkg.rb" +/bin/rm -Rf "${3}/puppet/indirector/data_binding/hiera.rb" -/bin/rm -Rf "${3}/puppet/provider/package/apt.rb" +/bin/rm -Rf "${3}/puppet/indirector/data_binding/none.rb" -/bin/rm -Rf "${3}/puppet/provider/package/sunfreeware.rb" +/bin/rm -Rf "${3}/puppet/indirector/direct_file_server.rb" -/bin/rm -Rf "${3}/puppet/provider/package/apple.rb" +/bin/rm -Rf "${3}/puppet/indirector/envelope.rb" -/bin/rm -Rf "${3}/puppet/provider/package/rug.rb" +/bin/rm -Rf "${3}/puppet/indirector/errors.rb" -/bin/rm -Rf "${3}/puppet/provider/scheduled_task/win32_taskscheduler.rb" +/bin/rm -Rf "${3}/puppet/indirector/exec.rb" -/bin/rm -Rf "${3}/puppet/provider/file/posix.rb" +/bin/rm -Rf "${3}/puppet/indirector/face.rb" -/bin/rm -Rf "${3}/puppet/provider/file/windows.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/active_record.rb" -/bin/rm -Rf "${3}/puppet/provider/ldap.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/couch.rb" -/bin/rm -Rf "${3}/puppet/provider/user/hpux.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/facter.rb" -/bin/rm -Rf "${3}/puppet/provider/user/aix.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/inventory_active_record.rb" -/bin/rm -Rf "${3}/puppet/provider/user/ldap.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/inventory_service.rb" -/bin/rm -Rf "${3}/puppet/provider/user/directoryservice.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/memory.rb" -/bin/rm -Rf "${3}/puppet/provider/user/useradd.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/network_device.rb" -/bin/rm -Rf "${3}/puppet/provider/user/windows_adsi.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/user/pw.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/store_configs.rb" -/bin/rm -Rf "${3}/puppet/provider/user/user_role_add.rb" +/bin/rm -Rf "${3}/puppet/indirector/facts/yaml.rb" -/bin/rm -Rf "${3}/puppet/provider/network_device.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_bucket_file/file.rb" -/bin/rm -Rf "${3}/puppet/provider/cisco.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_bucket_file/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/naginator.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_bucket_file/selector.rb" -/bin/rm -Rf "${3}/puppet/provider/mount.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_content.rb" -/bin/rm -Rf "${3}/puppet/provider/confine.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_content/file.rb" -/bin/rm -Rf "${3}/puppet/provider/confine_collection.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_content/file_server.rb" -/bin/rm -Rf "${3}/puppet/provider/port/parsed.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_content/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/mcx/mcxcontent.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_content/selector.rb" -/bin/rm -Rf "${3}/puppet/provider/zone/solaris.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_metadata.rb" -/bin/rm -Rf "${3}/puppet/provider/interface/cisco.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_metadata/file.rb" -/bin/rm -Rf "${3}/puppet/provider/computer/computer.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_metadata/file_server.rb" -/bin/rm -Rf "${3}/puppet/provider/aixobject.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_metadata/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/selmodule/semodule.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_metadata/selector.rb" -/bin/rm -Rf "${3}/puppet/provider/confiner.rb" +/bin/rm -Rf "${3}/puppet/indirector/file_server.rb" -/bin/rm -Rf "${3}/puppet/provider/mailalias/aliases.rb" +/bin/rm -Rf "${3}/puppet/indirector/hiera.rb" -/bin/rm -Rf "${3}/puppet/provider/cron/crontab.rb" +/bin/rm -Rf "${3}/puppet/indirector/indirection.rb" -/bin/rm -Rf "${3}/puppet/provider/sshkey/parsed.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_data.rb" -/bin/rm -Rf "${3}/puppet/provider/nameservice.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_data/local.rb" -/bin/rm -Rf "${3}/puppet/provider/confine/variable.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_data/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/confine/false.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_listener.rb" -/bin/rm -Rf "${3}/puppet/provider/confine/feature.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_listener/local.rb" -/bin/rm -Rf "${3}/puppet/provider/confine/exists.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_listener/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/confine/true.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_probe.rb" -/bin/rm -Rf "${3}/puppet/provider/selboolean/getsetsebool.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_probe/local.rb" -/bin/rm -Rf "${3}/puppet/provider/host/parsed.rb" +/bin/rm -Rf "${3}/puppet/indirector/instrumentation_probe/rest.rb" -/bin/rm -Rf "${3}/puppet/provider/augeas/augeas.rb" +/bin/rm -Rf "${3}/puppet/indirector/json.rb" -/bin/rm -Rf "${3}/puppet/provider/macauthorization/macauthorization.rb" +/bin/rm -Rf "${3}/puppet/indirector/key/ca.rb" -/bin/rm -Rf "${3}/puppet/provider/nameservice/objectadd.rb" +/bin/rm -Rf "${3}/puppet/indirector/key/disabled_ca.rb" -/bin/rm -Rf "${3}/puppet/provider/nameservice/directoryservice.rb" +/bin/rm -Rf "${3}/puppet/indirector/key/file.rb" -/bin/rm -Rf "${3}/puppet/provider/nameservice/pw.rb" +/bin/rm -Rf "${3}/puppet/indirector/ldap.rb" -/bin/rm -Rf "${3}/puppet/provider/mount/parsed.rb" +/bin/rm -Rf "${3}/puppet/indirector/memory.rb" -/bin/rm -Rf "${3}/puppet/provider/ssh_authorized_key/parsed.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/active_record.rb" -/bin/rm -Rf "${3}/puppet/provider/package.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/exec.rb" -/bin/rm -Rf "${3}/puppet/resource/status.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/ldap.rb" -/bin/rm -Rf "${3}/puppet/resource/type.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/memory.rb" -/bin/rm -Rf "${3}/puppet/resource/type_collection.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/plain.rb" -/bin/rm -Rf "${3}/puppet/resource/type_collection_helper.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/rest.rb" -/bin/rm -Rf "${3}/puppet/resource/catalog.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/store_configs.rb" -/bin/rm -Rf "${3}/puppet/util.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/write_only_yaml.rb" -/bin/rm -Rf "${3}/puppet/feature/zlib.rb" +/bin/rm -Rf "${3}/puppet/indirector/node/yaml.rb" -/bin/rm -Rf "${3}/puppet/feature/stomp.rb" +/bin/rm -Rf "${3}/puppet/indirector/none.rb" -/bin/rm -Rf "${3}/puppet/feature/rubygems.rb" +/bin/rm -Rf "${3}/puppet/indirector/plain.rb" -/bin/rm -Rf "${3}/puppet/feature/base.rb" +/bin/rm -Rf "${3}/puppet/indirector/queue.rb" -/bin/rm -Rf "${3}/puppet/feature/rdoc1.rb" +/bin/rm -Rf "${3}/puppet/indirector/report/processor.rb" -/bin/rm -Rf "${3}/puppet/feature/pson.rb" +/bin/rm -Rf "${3}/puppet/indirector/report/rest.rb" -/bin/rm -Rf "${3}/puppet/feature/eventlog.rb" +/bin/rm -Rf "${3}/puppet/indirector/report/yaml.rb" -/bin/rm -Rf "${3}/puppet/feature/selinux.rb" +/bin/rm -Rf "${3}/puppet/indirector/request.rb" -/bin/rm -Rf "${3}/puppet/feature/rails.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource/active_record.rb" -/bin/rm -Rf "${3}/puppet/feature/rack.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource/ral.rb" -/bin/rm -Rf "${3}/puppet/feature/ssh.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource/rest.rb" -/bin/rm -Rf "${3}/puppet/version.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource/store_configs.rb" -/bin/rm -Rf "${3}/puppet/agent/locker.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource/validator.rb" -/bin/rm -Rf "${3}/puppet/agent/disabler.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource_type.rb" -/bin/rm -Rf "${3}/puppet/module_tool/errors/upgrader.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource_type/parser.rb" -/bin/rm -Rf "${3}/puppet/module_tool/errors/installer.rb" +/bin/rm -Rf "${3}/puppet/indirector/resource_type/rest.rb" -/bin/rm -Rf "${3}/puppet/module_tool/errors/uninstaller.rb" +/bin/rm -Rf "${3}/puppet/indirector/rest.rb" -/bin/rm -Rf "${3}/puppet/module_tool/errors/shared.rb" +/bin/rm -Rf "${3}/puppet/indirector/run/local.rb" -/bin/rm -Rf "${3}/puppet/module_tool/errors/base.rb" +/bin/rm -Rf "${3}/puppet/indirector/run/rest.rb" -/bin/rm -Rf "${3}/puppet/module_tool/install_directory.rb" +/bin/rm -Rf "${3}/puppet/indirector/ssl_file.rb" -/bin/rm -Rf "${3}/puppet/module_tool/checksums.rb" +/bin/rm -Rf "${3}/puppet/indirector/status.rb" -/bin/rm -Rf "${3}/puppet/module_tool/contents_description.rb" +/bin/rm -Rf "${3}/puppet/indirector/status/local.rb" -/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb" +/bin/rm -Rf "${3}/puppet/indirector/status/rest.rb" -/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/tests/init.pp.erb" +/bin/rm -Rf "${3}/puppet/indirector/store_configs.rb" -/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/README.erb" +/bin/rm -Rf "${3}/puppet/indirector/terminus.rb" -/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb" +/bin/rm -Rf "${3}/puppet/indirector/yaml.rb" -/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/Modulefile.erb" +/bin/rm -Rf "${3}/puppet/interface.rb" -/bin/rm -Rf "${3}/puppet/module_tool/metadata.rb" +/bin/rm -Rf "${3}/puppet/interface/action.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications/upgrader.rb" +/bin/rm -Rf "${3}/puppet/interface/action_builder.rb" + +/bin/rm -Rf "${3}/puppet/interface/action_manager.rb" + +/bin/rm -Rf "${3}/puppet/interface/documentation.rb" + +/bin/rm -Rf "${3}/puppet/interface/face_collection.rb" + +/bin/rm -Rf "${3}/puppet/interface/option.rb" + +/bin/rm -Rf "${3}/puppet/interface/option_builder.rb" + +/bin/rm -Rf "${3}/puppet/interface/option_manager.rb" + +/bin/rm -Rf "${3}/puppet/metatype/manager.rb" + +/bin/rm -Rf "${3}/puppet/module.rb" + +/bin/rm -Rf "${3}/puppet/module_tool.rb" + +/bin/rm -Rf "${3}/puppet/module_tool/applications.rb" /bin/rm -Rf "${3}/puppet/module_tool/applications/application.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications/searcher.rb" +/bin/rm -Rf "${3}/puppet/module_tool/applications/builder.rb" + +/bin/rm -Rf "${3}/puppet/module_tool/applications/checksummer.rb" + +/bin/rm -Rf "${3}/puppet/module_tool/applications/generator.rb" /bin/rm -Rf "${3}/puppet/module_tool/applications/installer.rb" +/bin/rm -Rf "${3}/puppet/module_tool/applications/searcher.rb" + /bin/rm -Rf "${3}/puppet/module_tool/applications/uninstaller.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications/generator.rb" +/bin/rm -Rf "${3}/puppet/module_tool/applications/unpacker.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications/builder.rb" +/bin/rm -Rf "${3}/puppet/module_tool/applications/upgrader.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications/unpacker.rb" +/bin/rm -Rf "${3}/puppet/module_tool/checksums.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications/checksummer.rb" +/bin/rm -Rf "${3}/puppet/module_tool/contents_description.rb" + +/bin/rm -Rf "${3}/puppet/module_tool/dependency.rb" /bin/rm -Rf "${3}/puppet/module_tool/errors.rb" -/bin/rm -Rf "${3}/puppet/module_tool/shared_behaviors.rb" +/bin/rm -Rf "${3}/puppet/module_tool/errors/base.rb" -/bin/rm -Rf "${3}/puppet/module_tool/skeleton.rb" +/bin/rm -Rf "${3}/puppet/module_tool/errors/installer.rb" -/bin/rm -Rf "${3}/puppet/module_tool/dependency.rb" +/bin/rm -Rf "${3}/puppet/module_tool/errors/shared.rb" -/bin/rm -Rf "${3}/puppet/module_tool/modulefile.rb" +/bin/rm -Rf "${3}/puppet/module_tool/errors/uninstaller.rb" -/bin/rm -Rf "${3}/puppet/module_tool/applications.rb" +/bin/rm -Rf "${3}/puppet/module_tool/errors/upgrader.rb" -/bin/rm -Rf "${3}/puppet/reference/metaparameter.rb" +/bin/rm -Rf "${3}/puppet/module_tool/install_directory.rb" -/bin/rm -Rf "${3}/puppet/reference/type.rb" +/bin/rm -Rf "${3}/puppet/module_tool/metadata.rb" -/bin/rm -Rf "${3}/puppet/reference/indirection.rb" +/bin/rm -Rf "${3}/puppet/module_tool/modulefile.rb" -/bin/rm -Rf "${3}/puppet/reference/report.rb" +/bin/rm -Rf "${3}/puppet/module_tool/shared_behaviors.rb" -/bin/rm -Rf "${3}/puppet/reference/function.rb" +/bin/rm -Rf "${3}/puppet/module_tool/skeleton.rb" -/bin/rm -Rf "${3}/puppet/reference/providers.rb" +/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/Modulefile.erb" -/bin/rm -Rf "${3}/puppet/reference/configuration.rb" +/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/README.erb" -/bin/rm -Rf "${3}/puppet/type.rb" +/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb" -/bin/rm -Rf "${3}/puppet/network.rb" +/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb" -/bin/rm -Rf "${3}/puppet/file_bucket/file.rb" +/bin/rm -Rf "${3}/puppet/module_tool/skeleton/templates/generator/tests/init.pp.erb" -/bin/rm -Rf "${3}/puppet/file_bucket/dipper.rb" +/bin/rm -Rf "${3}/puppet/module_tool/tar.rb" -/bin/rm -Rf "${3}/puppet/configurer/plugin_handler.rb" +/bin/rm -Rf "${3}/puppet/module_tool/tar/gnu.rb" -/bin/rm -Rf "${3}/puppet/configurer/downloader.rb" +/bin/rm -Rf "${3}/puppet/module_tool/tar/mini.rb" -/bin/rm -Rf "${3}/puppet/configurer/fact_handler.rb" +/bin/rm -Rf "${3}/puppet/module_tool/tar/solaris.rb" -/bin/rm -Rf "${3}/puppet/type/ssh_authorized_key.rb" +/bin/rm -Rf "${3}/puppet/network.rb" -/bin/rm -Rf "${3}/puppet/type/augeas.rb" +/bin/rm -Rf "${3}/puppet/network/auth_config_parser.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_contactgroup.rb" +/bin/rm -Rf "${3}/puppet/network/authconfig.rb" -/bin/rm -Rf "${3}/puppet/type/router.rb" +/bin/rm -Rf "${3}/puppet/network/authentication.rb" -/bin/rm -Rf "${3}/puppet/type/selmodule.rb" +/bin/rm -Rf "${3}/puppet/network/authorization.rb" -/bin/rm -Rf "${3}/puppet/type/filebucket.rb" +/bin/rm -Rf "${3}/puppet/network/authstore.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_hostdependency.rb" +/bin/rm -Rf "${3}/puppet/network/client_request.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_servicedependency.rb" +/bin/rm -Rf "${3}/puppet/network/format.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_hostgroup.rb" +/bin/rm -Rf "${3}/puppet/network/format_handler.rb" -/bin/rm -Rf "${3}/puppet/type/service.rb" +/bin/rm -Rf "${3}/puppet/network/formats.rb" -/bin/rm -Rf "${3}/puppet/type/k5login.rb" +/bin/rm -Rf "${3}/puppet/network/http.rb" -/bin/rm -Rf "${3}/puppet/type/exec.rb" +/bin/rm -Rf "${3}/puppet/network/http/api.rb" -/bin/rm -Rf "${3}/puppet/type/file/source.rb" +/bin/rm -Rf "${3}/puppet/network/http/api/v1.rb" -/bin/rm -Rf "${3}/puppet/type/file/selcontext.rb" +/bin/rm -Rf "${3}/puppet/network/http/compression.rb" -/bin/rm -Rf "${3}/puppet/type/file/ensure.rb" +/bin/rm -Rf "${3}/puppet/network/http/connection.rb" -/bin/rm -Rf "${3}/puppet/type/file/content.rb" +/bin/rm -Rf "${3}/puppet/network/http/handler.rb" -/bin/rm -Rf "${3}/puppet/type/file/mtime.rb" +/bin/rm -Rf "${3}/puppet/network/http/rack.rb" -/bin/rm -Rf "${3}/puppet/type/file/type.rb" +/bin/rm -Rf "${3}/puppet/network/http/rack/httphandler.rb" -/bin/rm -Rf "${3}/puppet/type/file/group.rb" +/bin/rm -Rf "${3}/puppet/network/http/rack/rest.rb" -/bin/rm -Rf "${3}/puppet/type/file/mode.rb" +/bin/rm -Rf "${3}/puppet/network/http/webrick.rb" -/bin/rm -Rf "${3}/puppet/type/file/ctime.rb" +/bin/rm -Rf "${3}/puppet/network/http/webrick/rest.rb" -/bin/rm -Rf "${3}/puppet/type/file/checksum.rb" +/bin/rm -Rf "${3}/puppet/network/http_pool.rb" -/bin/rm -Rf "${3}/puppet/type/file/target.rb" +/bin/rm -Rf "${3}/puppet/network/resolver.rb" -/bin/rm -Rf "${3}/puppet/type/file/owner.rb" +/bin/rm -Rf "${3}/puppet/network/rest_controller.rb" -/bin/rm -Rf "${3}/puppet/type/zfs.rb" +/bin/rm -Rf "${3}/puppet/network/rights.rb" -/bin/rm -Rf "${3}/puppet/type/component.rb" +/bin/rm -Rf "${3}/puppet/network/server.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_command.rb" +/bin/rm -Rf "${3}/puppet/node.rb" -/bin/rm -Rf "${3}/puppet/type/group.rb" +/bin/rm -Rf "${3}/puppet/node/environment.rb" -/bin/rm -Rf "${3}/puppet/type/mount.rb" +/bin/rm -Rf "${3}/puppet/node/facts.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_hostextinfo.rb" +/bin/rm -Rf "${3}/puppet/parameter.rb" -/bin/rm -Rf "${3}/puppet/type/cron.rb" +/bin/rm -Rf "${3}/puppet/parameter/package_options.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_hostescalation.rb" +/bin/rm -Rf "${3}/puppet/parameter/path.rb" -/bin/rm -Rf "${3}/puppet/type/schedule.rb" +/bin/rm -Rf "${3}/puppet/parameter/value.rb" -/bin/rm -Rf "${3}/puppet/type/resources.rb" +/bin/rm -Rf "${3}/puppet/parameter/value_collection.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_contact.rb" +/bin/rm -Rf "${3}/puppet/parser.rb" -/bin/rm -Rf "${3}/puppet/type/stage.rb" +/bin/rm -Rf "${3}/puppet/parser/ast.rb" -/bin/rm -Rf "${3}/puppet/type/yumrepo.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/arithmetic_operator.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_host.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/astarray.rb" -/bin/rm -Rf "${3}/puppet/type/file.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/asthash.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_timeperiod.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/block_expression.rb" -/bin/rm -Rf "${3}/puppet/type/port.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/boolean_operator.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_service.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/branch.rb" -/bin/rm -Rf "${3}/puppet/type/zpool.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/caseopt.rb" -/bin/rm -Rf "${3}/puppet/type/tidy.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/casestatement.rb" -/bin/rm -Rf "${3}/puppet/type/mailalias.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/collection.rb" -/bin/rm -Rf "${3}/puppet/type/sshkey.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/collexpr.rb" -/bin/rm -Rf "${3}/puppet/type/user.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/comparison_operator.rb" -/bin/rm -Rf "${3}/puppet/type/host.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/definition.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_servicegroup.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/else.rb" -/bin/rm -Rf "${3}/puppet/type/vlan.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/function.rb" -/bin/rm -Rf "${3}/puppet/type/zone.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/hostclass.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_serviceextinfo.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/ifstatement.rb" -/bin/rm -Rf "${3}/puppet/type/computer.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/in_operator.rb" -/bin/rm -Rf "${3}/puppet/type/selboolean.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/lambda.rb" -/bin/rm -Rf "${3}/puppet/type/nagios_serviceescalation.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/leaf.rb" -/bin/rm -Rf "${3}/puppet/type/macauthorization.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/match_operator.rb" -/bin/rm -Rf "${3}/puppet/type/interface.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/method_call.rb" -/bin/rm -Rf "${3}/puppet/type/maillist.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/minus.rb" -/bin/rm -Rf "${3}/puppet/type/package.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/node.rb" -/bin/rm -Rf "${3}/puppet/type/mcx.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/nop.rb" -/bin/rm -Rf "${3}/puppet/type/whit.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/not.rb" -/bin/rm -Rf "${3}/puppet/type/notify.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/relationship.rb" -/bin/rm -Rf "${3}/puppet/type/scheduled_task.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/resource.rb" -/bin/rm -Rf "${3}/puppet/indirector/status/local.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/resource_defaults.rb" -/bin/rm -Rf "${3}/puppet/indirector/status/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/resource_instance.rb" -/bin/rm -Rf "${3}/puppet/indirector/none.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/resource_override.rb" -/bin/rm -Rf "${3}/puppet/indirector/report/yaml.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/resource_reference.rb" -/bin/rm -Rf "${3}/puppet/indirector/report/processor.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/resourceparam.rb" -/bin/rm -Rf "${3}/puppet/indirector/report/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/selector.rb" -/bin/rm -Rf "${3}/puppet/indirector/status.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/tag.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_metadata/selector.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/top_level_construct.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_metadata/file_server.rb" +/bin/rm -Rf "${3}/puppet/parser/ast/vardef.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_metadata/file.rb" +/bin/rm -Rf "${3}/puppet/parser/collector.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_metadata/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/compiler.rb" -/bin/rm -Rf "${3}/puppet/indirector/hiera.rb" +/bin/rm -Rf "${3}/puppet/parser/e_parser_adapter.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_bucket_file/selector.rb" +/bin/rm -Rf "${3}/puppet/parser/files.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_bucket_file/file.rb" +/bin/rm -Rf "${3}/puppet/parser/functions.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_bucket_file/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/collect.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_status/file.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/create_resources.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_status/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/defined.rb" -/bin/rm -Rf "${3}/puppet/indirector/key/ca.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/each.rb" -/bin/rm -Rf "${3}/puppet/indirector/key/file.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/extlookup.rb" -/bin/rm -Rf "${3}/puppet/indirector/key/disabled_ca.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/fail.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_metadata.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/file.rb" -/bin/rm -Rf "${3}/puppet/indirector/envelope.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/foreach.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource/ral.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/fqdn_rand.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource/store_configs.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/generate.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource/active_record.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/hiera.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/hiera_array.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource/validator.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/hiera_hash.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_status.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/hiera_include.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate/ca.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/include.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate/file.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/inline_template.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate/disabled_ca.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/md5.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/realize.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource_type/parser.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/reduce.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource_type/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/regsubst.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/ca.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/reject.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/file.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/require.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/disabled_ca.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/search.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_revocation_list/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/select.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/inventory_active_record.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/sha1.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/store_configs.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/shellquote.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/network_device.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/slice.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/couch.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/split.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/yaml.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/sprintf.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/memory.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/tag.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/facter.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/tagged.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/active_record.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/template.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/functions/versioncmp.rb" -/bin/rm -Rf "${3}/puppet/indirector/facts/inventory_service.rb" +/bin/rm -Rf "${3}/puppet/parser/grammar.ra" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_listener/local.rb" +/bin/rm -Rf "${3}/puppet/parser/lexer.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_listener/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/makefile" -/bin/rm -Rf "${3}/puppet/indirector/catalog/static_compiler.rb" +/bin/rm -Rf "${3}/puppet/parser/methods.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/store_configs.rb" +/bin/rm -Rf "${3}/puppet/parser/parser.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/json.rb" +/bin/rm -Rf "${3}/puppet/parser/parser_factory.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/queue.rb" +/bin/rm -Rf "${3}/puppet/parser/parser_support.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/yaml.rb" +/bin/rm -Rf "${3}/puppet/parser/relationship.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/compiler.rb" +/bin/rm -Rf "${3}/puppet/parser/resource.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/active_record.rb" +/bin/rm -Rf "${3}/puppet/parser/resource/param.rb" -/bin/rm -Rf "${3}/puppet/indirector/catalog/rest.rb" +/bin/rm -Rf "${3}/puppet/parser/scope.rb" -/bin/rm -Rf "${3}/puppet/indirector/exec.rb" +/bin/rm -Rf "${3}/puppet/parser/templatewrapper.rb" -/bin/rm -Rf "${3}/puppet/indirector/store_configs.rb" +/bin/rm -Rf "${3}/puppet/parser/type_loader.rb" -/bin/rm -Rf "${3}/puppet/indirector/json.rb" +/bin/rm -Rf "${3}/puppet/parser/yaml_trimmer.rb" -/bin/rm -Rf "${3}/puppet/indirector/ldap.rb" +/bin/rm -Rf "${3}/puppet/pops.rb" -/bin/rm -Rf "${3}/puppet/indirector/data_binding/none.rb" +/bin/rm -Rf "${3}/puppet/pops/adaptable.rb" -/bin/rm -Rf "${3}/puppet/indirector/data_binding/hiera.rb" +/bin/rm -Rf "${3}/puppet/pops/adapters.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_server.rb" +/bin/rm -Rf "${3}/puppet/pops/containment.rb" -/bin/rm -Rf "${3}/puppet/indirector/face.rb" +/bin/rm -Rf "${3}/puppet/pops/issues.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_listener.rb" +/bin/rm -Rf "${3}/puppet/pops/label_provider.rb" -/bin/rm -Rf "${3}/puppet/indirector/couch.rb" +/bin/rm -Rf "${3}/puppet/pops/model/ast_transformer.rb" -/bin/rm -Rf "${3}/puppet/indirector/errors.rb" +/bin/rm -Rf "${3}/puppet/pops/model/ast_tree_dumper.rb" -/bin/rm -Rf "${3}/puppet/indirector/terminus.rb" +/bin/rm -Rf "${3}/puppet/pops/model/factory.rb" -/bin/rm -Rf "${3}/puppet/indirector/queue.rb" +/bin/rm -Rf "${3}/puppet/pops/model/model.rb" -/bin/rm -Rf "${3}/puppet/indirector/node/exec.rb" +/bin/rm -Rf "${3}/puppet/pops/model/model_label_provider.rb" -/bin/rm -Rf "${3}/puppet/indirector/node/store_configs.rb" +/bin/rm -Rf "${3}/puppet/pops/model/model_tree_dumper.rb" -/bin/rm -Rf "${3}/puppet/indirector/node/write_only_yaml.rb" +/bin/rm -Rf "${3}/puppet/pops/model/tree_dumper.rb" -/bin/rm -Rf "${3}/puppet/indirector/node/ldap.rb" +/bin/rm -Rf "${3}/puppet/pops/parser/egrammar.ra" -/bin/rm -Rf "${3}/puppet/indirector/node/yaml.rb" +/bin/rm -Rf "${3}/puppet/pops/parser/eparser.rb" -/bin/rm -Rf "${3}/puppet/indirector/node/memory.rb" +/bin/rm -Rf "${3}/puppet/pops/parser/grammar.ra" -/bin/rm -Rf "${3}/puppet/indirector/node/active_record.rb" +/bin/rm -Rf "${3}/puppet/pops/parser/lexer.rb" -/bin/rm -Rf "${3}/puppet/indirector/node/plain.rb" +/bin/rm -Rf "${3}/puppet/pops/parser/makefile" -/bin/rm -Rf "${3}/puppet/indirector/node/rest.rb" +/bin/rm -Rf "${3}/puppet/pops/parser/parser_support.rb" -/bin/rm -Rf "${3}/puppet/indirector/yaml.rb" +/bin/rm -Rf "${3}/puppet/pops/patterns.rb" -/bin/rm -Rf "${3}/puppet/indirector/indirection.rb" +/bin/rm -Rf "${3}/puppet/pops/utils.rb" -/bin/rm -Rf "${3}/puppet/indirector/memory.rb" +/bin/rm -Rf "${3}/puppet/pops/validation.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_content.rb" +/bin/rm -Rf "${3}/puppet/pops/validation/checker3_1.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_probe.rb" +/bin/rm -Rf "${3}/puppet/pops/validation/validator_factory_3_1.rb" -/bin/rm -Rf "${3}/puppet/indirector/resource_type.rb" +/bin/rm -Rf "${3}/puppet/pops/visitable.rb" -/bin/rm -Rf "${3}/puppet/indirector/code.rb" +/bin/rm -Rf "${3}/puppet/pops/visitor.rb" -/bin/rm -Rf "${3}/puppet/indirector/run/local.rb" +/bin/rm -Rf "${3}/puppet/property.rb" -/bin/rm -Rf "${3}/puppet/indirector/run/rest.rb" +/bin/rm -Rf "${3}/puppet/property/ensure.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_probe/local.rb" +/bin/rm -Rf "${3}/puppet/property/keyvalue.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_probe/rest.rb" +/bin/rm -Rf "${3}/puppet/property/list.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_data/local.rb" +/bin/rm -Rf "${3}/puppet/property/ordered_list.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_data/rest.rb" +/bin/rm -Rf "${3}/puppet/provider.rb" -/bin/rm -Rf "${3}/puppet/indirector/request.rb" +/bin/rm -Rf "${3}/puppet/provider/aixobject.rb" -/bin/rm -Rf "${3}/puppet/indirector/active_record.rb" +/bin/rm -Rf "${3}/puppet/provider/augeas/augeas.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_request/ca.rb" +/bin/rm -Rf "${3}/puppet/provider/cisco.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_request/file.rb" +/bin/rm -Rf "${3}/puppet/provider/command.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_request/disabled_ca.rb" +/bin/rm -Rf "${3}/puppet/provider/computer/computer.rb" -/bin/rm -Rf "${3}/puppet/indirector/certificate_request/rest.rb" +/bin/rm -Rf "${3}/puppet/provider/confine.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_content/selector.rb" +/bin/rm -Rf "${3}/puppet/provider/confine/exists.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_content/file_server.rb" +/bin/rm -Rf "${3}/puppet/provider/confine/false.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_content/file.rb" +/bin/rm -Rf "${3}/puppet/provider/confine/feature.rb" -/bin/rm -Rf "${3}/puppet/indirector/file_content/rest.rb" +/bin/rm -Rf "${3}/puppet/provider/confine/true.rb" -/bin/rm -Rf "${3}/puppet/indirector/instrumentation_data.rb" +/bin/rm -Rf "${3}/puppet/provider/confine/variable.rb" -/bin/rm -Rf "${3}/puppet/indirector/plain.rb" +/bin/rm -Rf "${3}/puppet/provider/confine_collection.rb" -/bin/rm -Rf "${3}/puppet/indirector/rest.rb" +/bin/rm -Rf "${3}/puppet/provider/confiner.rb" -/bin/rm -Rf "${3}/puppet/indirector/direct_file_server.rb" +/bin/rm -Rf "${3}/puppet/provider/cron/crontab.rb" -/bin/rm -Rf "${3}/puppet/indirector/ssl_file.rb" +/bin/rm -Rf "${3}/puppet/provider/exec.rb" -/bin/rm -Rf "${3}/puppet/parser/relationship.rb" +/bin/rm -Rf "${3}/puppet/provider/exec/posix.rb" -/bin/rm -Rf "${3}/puppet/parser/resource.rb" +/bin/rm -Rf "${3}/puppet/provider/exec/shell.rb" -/bin/rm -Rf "${3}/puppet/parser/grammar.ra" +/bin/rm -Rf "${3}/puppet/provider/exec/windows.rb" -/bin/rm -Rf "${3}/puppet/parser/resource/param.rb" +/bin/rm -Rf "${3}/puppet/provider/file/posix.rb" -/bin/rm -Rf "${3}/puppet/parser/lexer.rb" +/bin/rm -Rf "${3}/puppet/provider/file/windows.rb" -/bin/rm -Rf "${3}/puppet/parser/type_loader.rb" +/bin/rm -Rf "${3}/puppet/provider/group/aix.rb" -/bin/rm -Rf "${3}/puppet/parser/functions.rb" +/bin/rm -Rf "${3}/puppet/provider/group/directoryservice.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/minus.rb" +/bin/rm -Rf "${3}/puppet/provider/group/groupadd.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/relationship.rb" +/bin/rm -Rf "${3}/puppet/provider/group/ldap.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/resource.rb" +/bin/rm -Rf "${3}/puppet/provider/group/pw.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/casestatement.rb" +/bin/rm -Rf "${3}/puppet/provider/group/windows_adsi.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/match_operator.rb" +/bin/rm -Rf "${3}/puppet/provider/host/parsed.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/asthash.rb" +/bin/rm -Rf "${3}/puppet/provider/interface/cisco.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/resource_override.rb" +/bin/rm -Rf "${3}/puppet/provider/ldap.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/nop.rb" +/bin/rm -Rf "${3}/puppet/provider/macauthorization/macauthorization.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/collexpr.rb" +/bin/rm -Rf "${3}/puppet/provider/mailalias/aliases.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/selector.rb" +/bin/rm -Rf "${3}/puppet/provider/maillist/mailman.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/not.rb" +/bin/rm -Rf "${3}/puppet/provider/mcx/mcxcontent.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/boolean_operator.rb" +/bin/rm -Rf "${3}/puppet/provider/mount.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/resource_defaults.rb" +/bin/rm -Rf "${3}/puppet/provider/mount/parsed.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/ifstatement.rb" +/bin/rm -Rf "${3}/puppet/provider/naginator.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/top_level_construct.rb" +/bin/rm -Rf "${3}/puppet/provider/nameservice.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/collection.rb" +/bin/rm -Rf "${3}/puppet/provider/nameservice/directoryservice.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/comparison_operator.rb" +/bin/rm -Rf "${3}/puppet/provider/nameservice/objectadd.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/resource_reference.rb" +/bin/rm -Rf "${3}/puppet/provider/nameservice/pw.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/vardef.rb" +/bin/rm -Rf "${3}/puppet/provider/network_device.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/resource_instance.rb" +/bin/rm -Rf "${3}/puppet/provider/package.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/node.rb" +/bin/rm -Rf "${3}/puppet/provider/package/aix.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/else.rb" +/bin/rm -Rf "${3}/puppet/provider/package/appdmg.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/definition.rb" +/bin/rm -Rf "${3}/puppet/provider/package/apple.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/branch.rb" +/bin/rm -Rf "${3}/puppet/provider/package/apt.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/function.rb" +/bin/rm -Rf "${3}/puppet/provider/package/aptitude.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/leaf.rb" +/bin/rm -Rf "${3}/puppet/provider/package/aptrpm.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/caseopt.rb" +/bin/rm -Rf "${3}/puppet/provider/package/blastwave.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/in_operator.rb" +/bin/rm -Rf "${3}/puppet/provider/package/dpkg.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/tag.rb" +/bin/rm -Rf "${3}/puppet/provider/package/fink.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/astarray.rb" +/bin/rm -Rf "${3}/puppet/provider/package/freebsd.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/arithmetic_operator.rb" +/bin/rm -Rf "${3}/puppet/provider/package/gem.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/resourceparam.rb" +/bin/rm -Rf "${3}/puppet/provider/package/hpux.rb" -/bin/rm -Rf "${3}/puppet/parser/ast/hostclass.rb" +/bin/rm -Rf "${3}/puppet/provider/package/macports.rb" -/bin/rm -Rf "${3}/puppet/parser/parser.rb" +/bin/rm -Rf "${3}/puppet/provider/package/msi.rb" -/bin/rm -Rf "${3}/puppet/parser/collector.rb" +/bin/rm -Rf "${3}/puppet/provider/package/nim.rb" -/bin/rm -Rf "${3}/puppet/parser/files.rb" +/bin/rm -Rf "${3}/puppet/provider/package/openbsd.rb" -/bin/rm -Rf "${3}/puppet/parser/templatewrapper.rb" +/bin/rm -Rf "${3}/puppet/provider/package/opkg.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/regsubst.rb" +/bin/rm -Rf "${3}/puppet/provider/package/pacman.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/versioncmp.rb" +/bin/rm -Rf "${3}/puppet/provider/package/pip.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/split.rb" +/bin/rm -Rf "${3}/puppet/provider/package/pkg.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/inline_template.rb" +/bin/rm -Rf "${3}/puppet/provider/package/pkgdmg.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/hiera.rb" +/bin/rm -Rf "${3}/puppet/provider/package/pkgin.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/require.rb" +/bin/rm -Rf "${3}/puppet/provider/package/pkgutil.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/realize.rb" +/bin/rm -Rf "${3}/puppet/provider/package/portage.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/hiera_include.rb" +/bin/rm -Rf "${3}/puppet/provider/package/ports.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/search.rb" +/bin/rm -Rf "${3}/puppet/provider/package/portupgrade.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/defined.rb" +/bin/rm -Rf "${3}/puppet/provider/package/rpm.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/template.rb" +/bin/rm -Rf "${3}/puppet/provider/package/rug.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/fail.rb" +/bin/rm -Rf "${3}/puppet/provider/package/sun.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/sha1.rb" +/bin/rm -Rf "${3}/puppet/provider/package/sunfreeware.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/fqdn_rand.rb" +/bin/rm -Rf "${3}/puppet/provider/package/up2date.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/file.rb" +/bin/rm -Rf "${3}/puppet/provider/package/urpmi.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/include.rb" +/bin/rm -Rf "${3}/puppet/provider/package/windows.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/hiera_array.rb" +/bin/rm -Rf "${3}/puppet/provider/package/windows/exe_package.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/create_resources.rb" +/bin/rm -Rf "${3}/puppet/provider/package/windows/msi_package.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/tagged.rb" +/bin/rm -Rf "${3}/puppet/provider/package/windows/package.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/shellquote.rb" +/bin/rm -Rf "${3}/puppet/provider/package/yum.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/hiera_hash.rb" +/bin/rm -Rf "${3}/puppet/provider/package/yumhelper.py" -/bin/rm -Rf "${3}/puppet/parser/functions/generate.rb" +/bin/rm -Rf "${3}/puppet/provider/package/zypper.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/extlookup.rb" +/bin/rm -Rf "${3}/puppet/provider/parsedfile.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/tag.rb" +/bin/rm -Rf "${3}/puppet/provider/port/parsed.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/md5.rb" +/bin/rm -Rf "${3}/puppet/provider/scheduled_task/win32_taskscheduler.rb" -/bin/rm -Rf "${3}/puppet/parser/functions/sprintf.rb" +/bin/rm -Rf "${3}/puppet/provider/selboolean/getsetsebool.rb" -/bin/rm -Rf "${3}/puppet/parser/yaml_trimmer.rb" +/bin/rm -Rf "${3}/puppet/provider/selmodule/semodule.rb" -/bin/rm -Rf "${3}/puppet/parser/ast.rb" +/bin/rm -Rf "${3}/puppet/provider/service/base.rb" -/bin/rm -Rf "${3}/puppet/parser/compiler.rb" +/bin/rm -Rf "${3}/puppet/provider/service/bsd.rb" -/bin/rm -Rf "${3}/puppet/parser/parser_support.rb" +/bin/rm -Rf "${3}/puppet/provider/service/daemontools.rb" -/bin/rm -Rf "${3}/puppet/parser/scope.rb" +/bin/rm -Rf "${3}/puppet/provider/service/debian.rb" -/bin/rm -Rf "${3}/puppet/parser/makefile" +/bin/rm -Rf "${3}/puppet/provider/service/freebsd.rb" -/bin/rm -Rf "${3}/puppet/error.rb" +/bin/rm -Rf "${3}/puppet/provider/service/gentoo.rb" -/bin/rm -Rf "${3}/puppet/face.rb" +/bin/rm -Rf "${3}/puppet/provider/service/init.rb" -/bin/rm -Rf "${3}/puppet/run.rb" +/bin/rm -Rf "${3}/puppet/provider/service/launchd.rb" -/bin/rm -Rf "${3}/puppet/simple_graph.rb" +/bin/rm -Rf "${3}/puppet/provider/service/openrc.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate_revocation_list.rb" +/bin/rm -Rf "${3}/puppet/provider/service/openwrt.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate_request.rb" +/bin/rm -Rf "${3}/puppet/provider/service/redhat.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate_authority/interface.rb" +/bin/rm -Rf "${3}/puppet/provider/service/runit.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate_signer.rb" +/bin/rm -Rf "${3}/puppet/provider/service/service.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate_authority.rb" +/bin/rm -Rf "${3}/puppet/provider/service/smf.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate.rb" +/bin/rm -Rf "${3}/puppet/provider/service/src.rb" -/bin/rm -Rf "${3}/puppet/ssl/certificate_factory.rb" +/bin/rm -Rf "${3}/puppet/provider/service/systemd.rb" -/bin/rm -Rf "${3}/puppet/ssl/digest.rb" +/bin/rm -Rf "${3}/puppet/provider/service/upstart.rb" -/bin/rm -Rf "${3}/puppet/ssl/base.rb" +/bin/rm -Rf "${3}/puppet/provider/service/windows.rb" -/bin/rm -Rf "${3}/puppet/ssl/host.rb" +/bin/rm -Rf "${3}/puppet/provider/ssh_authorized_key/parsed.rb" -/bin/rm -Rf "${3}/puppet/ssl/configuration.rb" +/bin/rm -Rf "${3}/puppet/provider/sshkey/parsed.rb" -/bin/rm -Rf "${3}/puppet/ssl/inventory.rb" +/bin/rm -Rf "${3}/puppet/provider/user/aix.rb" -/bin/rm -Rf "${3}/puppet/ssl/key.rb" +/bin/rm -Rf "${3}/puppet/provider/user/directoryservice.rb" -/bin/rm -Rf "${3}/puppet/dsl.rb" +/bin/rm -Rf "${3}/puppet/provider/user/hpux.rb" -/bin/rm -Rf "${3}/puppet/ssl.rb" +/bin/rm -Rf "${3}/puppet/provider/user/ldap.rb" -/bin/rm -Rf "${3}/puppet/indirector.rb" +/bin/rm -Rf "${3}/puppet/provider/user/pw.rb" -/bin/rm -Rf "${3}/puppet/node/environment.rb" +/bin/rm -Rf "${3}/puppet/provider/user/user_role_add.rb" -/bin/rm -Rf "${3}/puppet/node/facts.rb" +/bin/rm -Rf "${3}/puppet/provider/user/useradd.rb" -/bin/rm -Rf "${3}/puppet/interface/option_builder.rb" +/bin/rm -Rf "${3}/puppet/provider/user/windows_adsi.rb" -/bin/rm -Rf "${3}/puppet/interface/option_manager.rb" +/bin/rm -Rf "${3}/puppet/provider/vlan/cisco.rb" -/bin/rm -Rf "${3}/puppet/interface/documentation.rb" +/bin/rm -Rf "${3}/puppet/provider/zfs/zfs.rb" -/bin/rm -Rf "${3}/puppet/interface/option.rb" +/bin/rm -Rf "${3}/puppet/provider/zone/solaris.rb" -/bin/rm -Rf "${3}/puppet/interface/action.rb" +/bin/rm -Rf "${3}/puppet/provider/zpool/zpool.rb" -/bin/rm -Rf "${3}/puppet/interface/face_collection.rb" +/bin/rm -Rf "${3}/puppet/rails.rb" -/bin/rm -Rf "${3}/puppet/interface/action_manager.rb" +/bin/rm -Rf "${3}/puppet/rails/benchmark.rb" -/bin/rm -Rf "${3}/puppet/interface/action_builder.rb" +/bin/rm -Rf "${3}/puppet/rails/database/001_add_created_at_to_all_tables.rb" -/bin/rm -Rf "${3}/puppet/property.rb" +/bin/rm -Rf "${3}/puppet/rails/database/002_remove_duplicated_index_on_all_tables.rb" -/bin/rm -Rf "${3}/puppet/test/test_helper.rb" +/bin/rm -Rf "${3}/puppet/rails/database/003_add_environment_to_host.rb" -/bin/rm -Rf "${3}/puppet/settings.rb" +/bin/rm -Rf "${3}/puppet/rails/database/004_add_inventory_service_tables.rb" -/bin/rm -Rf "${3}/puppet/parameter.rb" +/bin/rm -Rf "${3}/puppet/rails/database/schema.rb" -/bin/rm -Rf "${3}/puppet/application/face_base.rb" +/bin/rm -Rf "${3}/puppet/rails/fact_name.rb" -/bin/rm -Rf "${3}/puppet/application/ca.rb" +/bin/rm -Rf "${3}/puppet/rails/fact_value.rb" -/bin/rm -Rf "${3}/puppet/application/device.rb" +/bin/rm -Rf "${3}/puppet/rails/host.rb" -/bin/rm -Rf "${3}/puppet/application/certificate_revocation_list.rb" +/bin/rm -Rf "${3}/puppet/rails/inventory_fact.rb" -/bin/rm -Rf "${3}/puppet/application/resource.rb" +/bin/rm -Rf "${3}/puppet/rails/inventory_node.rb" -/bin/rm -Rf "${3}/puppet/application/module.rb" +/bin/rm -Rf "${3}/puppet/rails/param_name.rb" -/bin/rm -Rf "${3}/puppet/application/plugin.rb" +/bin/rm -Rf "${3}/puppet/rails/param_value.rb" -/bin/rm -Rf "${3}/puppet/application/config.rb" +/bin/rm -Rf "${3}/puppet/rails/puppet_tag.rb" -/bin/rm -Rf "${3}/puppet/application/indirection_base.rb" +/bin/rm -Rf "${3}/puppet/rails/resource.rb" -/bin/rm -Rf "${3}/puppet/application/status.rb" +/bin/rm -Rf "${3}/puppet/rails/resource_tag.rb" -/bin/rm -Rf "${3}/puppet/application/filebucket.rb" +/bin/rm -Rf "${3}/puppet/rails/source_file.rb" -/bin/rm -Rf "${3}/puppet/application/certificate_request.rb" +/bin/rm -Rf "${3}/puppet/rb_tree_map.rb" -/bin/rm -Rf "${3}/puppet/application/cert.rb" +/bin/rm -Rf "${3}/puppet/reference/configuration.rb" -/bin/rm -Rf "${3}/puppet/application/man.rb" +/bin/rm -Rf "${3}/puppet/reference/function.rb" -/bin/rm -Rf "${3}/puppet/application/doc.rb" +/bin/rm -Rf "${3}/puppet/reference/indirection.rb" -/bin/rm -Rf "${3}/puppet/application/certificate.rb" +/bin/rm -Rf "${3}/puppet/reference/metaparameter.rb" -/bin/rm -Rf "${3}/puppet/application/instrumentation_listener.rb" +/bin/rm -Rf "${3}/puppet/reference/providers.rb" -/bin/rm -Rf "${3}/puppet/application/queue.rb" +/bin/rm -Rf "${3}/puppet/reference/report.rb" -/bin/rm -Rf "${3}/puppet/application/apply.rb" +/bin/rm -Rf "${3}/puppet/reference/type.rb" -/bin/rm -Rf "${3}/puppet/application/file.rb" +/bin/rm -Rf "${3}/puppet/relationship.rb" -/bin/rm -Rf "${3}/puppet/application/agent.rb" +/bin/rm -Rf "${3}/puppet/reports.rb" -/bin/rm -Rf "${3}/puppet/application/inspect.rb" +/bin/rm -Rf "${3}/puppet/reports/http.rb" -/bin/rm -Rf "${3}/puppet/application/help.rb" +/bin/rm -Rf "${3}/puppet/reports/log.rb" -/bin/rm -Rf "${3}/puppet/application/parser.rb" +/bin/rm -Rf "${3}/puppet/reports/rrdgraph.rb" -/bin/rm -Rf "${3}/puppet/application/instrumentation_probe.rb" +/bin/rm -Rf "${3}/puppet/reports/store.rb" -/bin/rm -Rf "${3}/puppet/application/describe.rb" +/bin/rm -Rf "${3}/puppet/reports/tagmail.rb" -/bin/rm -Rf "${3}/puppet/application/secret_agent.rb" +/bin/rm -Rf "${3}/puppet/resource.rb" -/bin/rm -Rf "${3}/puppet/application/resource_type.rb" +/bin/rm -Rf "${3}/puppet/resource/catalog.rb" -/bin/rm -Rf "${3}/puppet/application/node.rb" +/bin/rm -Rf "${3}/puppet/resource/status.rb" -/bin/rm -Rf "${3}/puppet/application/facts.rb" +/bin/rm -Rf "${3}/puppet/resource/type.rb" -/bin/rm -Rf "${3}/puppet/application/report.rb" +/bin/rm -Rf "${3}/puppet/resource/type_collection.rb" -/bin/rm -Rf "${3}/puppet/application/catalog.rb" +/bin/rm -Rf "${3}/puppet/resource/type_collection_helper.rb" -/bin/rm -Rf "${3}/puppet/application/master.rb" +/bin/rm -Rf "${3}/puppet/run.rb" -/bin/rm -Rf "${3}/puppet/application/instrumentation_data.rb" +/bin/rm -Rf "${3}/puppet/scheduler.rb" -/bin/rm -Rf "${3}/puppet/application/kick.rb" +/bin/rm -Rf "${3}/puppet/scheduler/job.rb" -/bin/rm -Rf "${3}/puppet/application/key.rb" +/bin/rm -Rf "${3}/puppet/scheduler/scheduler.rb" -/bin/rm -Rf "${3}/puppet/agent.rb" +/bin/rm -Rf "${3}/puppet/scheduler/splay_job.rb" -/bin/rm -Rf "${3}/puppet/rb_tree_map.rb" +/bin/rm -Rf "${3}/puppet/scheduler/timer.rb" -/bin/rm -Rf "${3}/puppet/forge.rb" +/bin/rm -Rf "${3}/puppet/settings.rb" -/bin/rm -Rf "${3}/puppet/parser.rb" +/bin/rm -Rf "${3}/puppet/settings/base_setting.rb" -/bin/rm -Rf "${3}/puppet/transaction/event.rb" +/bin/rm -Rf "${3}/puppet/settings/boolean_setting.rb" -/bin/rm -Rf "${3}/puppet/transaction/resource_harness.rb" +/bin/rm -Rf "${3}/puppet/settings/config_file.rb" -/bin/rm -Rf "${3}/puppet/transaction/report.rb" +/bin/rm -Rf "${3}/puppet/settings/directory_setting.rb" -/bin/rm -Rf "${3}/puppet/transaction/event_manager.rb" +/bin/rm -Rf "${3}/puppet/settings/duration_setting.rb" -/bin/rm -Rf "${3}/puppet/file_serving.rb" +/bin/rm -Rf "${3}/puppet/settings/errors.rb" -/bin/rm -Rf "${3}/puppet/daemon.rb" +/bin/rm -Rf "${3}/puppet/settings/file_setting.rb" -/bin/rm -Rf "${3}/puppet/forge/cache.rb" +/bin/rm -Rf "${3}/puppet/settings/path_setting.rb" -/bin/rm -Rf "${3}/puppet/forge/errors.rb" +/bin/rm -Rf "${3}/puppet/settings/string_setting.rb" -/bin/rm -Rf "${3}/puppet/forge/repository.rb" +/bin/rm -Rf "${3}/puppet/settings/terminus_setting.rb" -/bin/rm -Rf "${3}/puppet/file_bucket.rb" +/bin/rm -Rf "${3}/puppet/settings/value_translator.rb" -/bin/rm -Rf "${3}/puppet/transaction.rb" +/bin/rm -Rf "${3}/puppet/simple_graph.rb" -/bin/rm -Rf "${3}/puppet/file_collection.rb" +/bin/rm -Rf "${3}/puppet/ssl.rb" -/bin/rm -Rf "${3}/puppet/node.rb" +/bin/rm -Rf "${3}/puppet/ssl/base.rb" -/bin/rm -Rf "${3}/puppet/property/ensure.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate.rb" -/bin/rm -Rf "${3}/puppet/property/list.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate_authority.rb" -/bin/rm -Rf "${3}/puppet/property/ordered_list.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate_authority/interface.rb" -/bin/rm -Rf "${3}/puppet/property/keyvalue.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate_factory.rb" -/bin/rm -Rf "${3}/puppet/file_collection/lookup.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate_request.rb" -/bin/rm -Rf "${3}/puppet/dsl/resource_api.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate_revocation_list.rb" -/bin/rm -Rf "${3}/puppet/dsl/resource_type_api.rb" +/bin/rm -Rf "${3}/puppet/ssl/certificate_signer.rb" -/bin/rm -Rf "${3}/puppet/util/constant_inflector.rb" +/bin/rm -Rf "${3}/puppet/ssl/configuration.rb" -/bin/rm -Rf "${3}/puppet/util/rdoc.rb" +/bin/rm -Rf "${3}/puppet/ssl/digest.rb" -/bin/rm -Rf "${3}/puppet/util/json_lockfile.rb" +/bin/rm -Rf "${3}/puppet/ssl/host.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation/listener.rb" +/bin/rm -Rf "${3}/puppet/ssl/inventory.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation/instrumentable.rb" +/bin/rm -Rf "${3}/puppet/ssl/key.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation/data.rb" +/bin/rm -Rf "${3}/puppet/ssl/validator.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation/listeners/performance.rb" +/bin/rm -Rf "${3}/puppet/status.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation/listeners/log.rb" +/bin/rm -Rf "${3}/puppet/test/test_helper.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation/indirection_probe.rb" +/bin/rm -Rf "${3}/puppet/transaction.rb" -/bin/rm -Rf "${3}/puppet/util/cacher.rb" +/bin/rm -Rf "${3}/puppet/transaction/event.rb" -/bin/rm -Rf "${3}/puppet/util/checksums.rb" +/bin/rm -Rf "${3}/puppet/transaction/event_manager.rb" -/bin/rm -Rf "${3}/puppet/util/terminal.rb" +/bin/rm -Rf "${3}/puppet/transaction/report.rb" -/bin/rm -Rf "${3}/puppet/util/pidlock.rb" +/bin/rm -Rf "${3}/puppet/transaction/resource_harness.rb" -/bin/rm -Rf "${3}/puppet/util/lockfile.rb" +/bin/rm -Rf "${3}/puppet/type.rb" -/bin/rm -Rf "${3}/puppet/util/plugins.rb" +/bin/rm -Rf "${3}/puppet/type/augeas.rb" -/bin/rm -Rf "${3}/puppet/util/suidmanager.rb" +/bin/rm -Rf "${3}/puppet/type/component.rb" -/bin/rm -Rf "${3}/puppet/util/storage.rb" +/bin/rm -Rf "${3}/puppet/type/computer.rb" -/bin/rm -Rf "${3}/puppet/util/metaid.rb" +/bin/rm -Rf "${3}/puppet/type/cron.rb" -/bin/rm -Rf "${3}/puppet/util/tagging.rb" +/bin/rm -Rf "${3}/puppet/type/exec.rb" -/bin/rm -Rf "${3}/puppet/util/graph.rb" +/bin/rm -Rf "${3}/puppet/type/file.rb" -/bin/rm -Rf "${3}/puppet/util/retryaction.rb" +/bin/rm -Rf "${3}/puppet/type/file/checksum.rb" -/bin/rm -Rf "${3}/puppet/util/run_mode.rb" +/bin/rm -Rf "${3}/puppet/type/file/content.rb" -/bin/rm -Rf "${3}/puppet/util/filetype.rb" +/bin/rm -Rf "${3}/puppet/type/file/ctime.rb" -/bin/rm -Rf "${3}/puppet/util/metric.rb" +/bin/rm -Rf "${3}/puppet/type/file/ensure.rb" -/bin/rm -Rf "${3}/puppet/util/autoload.rb" +/bin/rm -Rf "${3}/puppet/type/file/group.rb" -/bin/rm -Rf "${3}/puppet/util/ldap.rb" +/bin/rm -Rf "${3}/puppet/type/file/mode.rb" -/bin/rm -Rf "${3}/puppet/util/network_device.rb" +/bin/rm -Rf "${3}/puppet/type/file/mtime.rb" -/bin/rm -Rf "${3}/puppet/util/adsi.rb" +/bin/rm -Rf "${3}/puppet/type/file/owner.rb" -/bin/rm -Rf "${3}/puppet/util/zaml.rb" +/bin/rm -Rf "${3}/puppet/type/file/selcontext.rb" -/bin/rm -Rf "${3}/puppet/util/queue/stomp.rb" +/bin/rm -Rf "${3}/puppet/type/file/source.rb" -/bin/rm -Rf "${3}/puppet/util/user_attr.rb" +/bin/rm -Rf "${3}/puppet/type/file/target.rb" -/bin/rm -Rf "${3}/puppet/util/execution_stub.rb" +/bin/rm -Rf "${3}/puppet/type/file/type.rb" -/bin/rm -Rf "${3}/puppet/util/subclass_loader.rb" +/bin/rm -Rf "${3}/puppet/type/filebucket.rb" -/bin/rm -Rf "${3}/puppet/util/docs.rb" +/bin/rm -Rf "${3}/puppet/type/group.rb" -/bin/rm -Rf "${3}/puppet/util/diff.rb" +/bin/rm -Rf "${3}/puppet/type/host.rb" -/bin/rm -Rf "${3}/puppet/util/ldap/manager.rb" +/bin/rm -Rf "${3}/puppet/type/interface.rb" -/bin/rm -Rf "${3}/puppet/util/ldap/connection.rb" +/bin/rm -Rf "${3}/puppet/type/k5login.rb" -/bin/rm -Rf "${3}/puppet/util/ldap/generator.rb" +/bin/rm -Rf "${3}/puppet/type/macauthorization.rb" -/bin/rm -Rf "${3}/puppet/util/rubygems.rb" +/bin/rm -Rf "${3}/puppet/type/mailalias.rb" -/bin/rm -Rf "${3}/puppet/util/loadedfile.rb" +/bin/rm -Rf "${3}/puppet/type/maillist.rb" -/bin/rm -Rf "${3}/puppet/util/windows/registry.rb" +/bin/rm -Rf "${3}/puppet/type/mcx.rb" -/bin/rm -Rf "${3}/puppet/util/windows/process.rb" +/bin/rm -Rf "${3}/puppet/type/mount.rb" -/bin/rm -Rf "${3}/puppet/util/windows/error.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_command.rb" -/bin/rm -Rf "${3}/puppet/util/windows/file.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_contact.rb" -/bin/rm -Rf "${3}/puppet/util/windows/user.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_contactgroup.rb" -/bin/rm -Rf "${3}/puppet/util/windows/security.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_host.rb" -/bin/rm -Rf "${3}/puppet/util/windows/sid.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_hostdependency.rb" -/bin/rm -Rf "${3}/puppet/util/methodhelper.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_hostescalation.rb" -/bin/rm -Rf "${3}/puppet/util/logging.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_hostextinfo.rb" -/bin/rm -Rf "${3}/puppet/util/errors.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_hostgroup.rb" -/bin/rm -Rf "${3}/puppet/util/queue.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_service.rb" -/bin/rm -Rf "${3}/puppet/util/log/rate_limited_logger.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_servicedependency.rb" -/bin/rm -Rf "${3}/puppet/util/log/destination.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_serviceescalation.rb" -/bin/rm -Rf "${3}/puppet/util/log/destinations.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_serviceextinfo.rb" -/bin/rm -Rf "${3}/puppet/util/inline_docs.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_servicegroup.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/config.rb" +/bin/rm -Rf "${3}/puppet/type/nagios_timeperiod.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/cisco/device.rb" +/bin/rm -Rf "${3}/puppet/type/notify.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/cisco/facts.rb" +/bin/rm -Rf "${3}/puppet/type/package.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/cisco/interface.rb" +/bin/rm -Rf "${3}/puppet/type/port.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/cisco.rb" +/bin/rm -Rf "${3}/puppet/type/resources.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/ipcalc.rb" +/bin/rm -Rf "${3}/puppet/type/router.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/transport.rb" +/bin/rm -Rf "${3}/puppet/type/schedule.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/transport/telnet.rb" +/bin/rm -Rf "${3}/puppet/type/scheduled_task.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/transport/base.rb" +/bin/rm -Rf "${3}/puppet/type/selboolean.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/transport/ssh.rb" +/bin/rm -Rf "${3}/puppet/type/selmodule.rb" -/bin/rm -Rf "${3}/puppet/util/network_device/base.rb" +/bin/rm -Rf "${3}/puppet/type/service.rb" -/bin/rm -Rf "${3}/puppet/util/monkey_patches.rb" +/bin/rm -Rf "${3}/puppet/type/ssh_authorized_key.rb" -/bin/rm -Rf "${3}/puppet/util/warnings.rb" +/bin/rm -Rf "${3}/puppet/type/sshkey.rb" -/bin/rm -Rf "${3}/puppet/util/execution.rb" +/bin/rm -Rf "${3}/puppet/type/stage.rb" -/bin/rm -Rf "${3}/puppet/util/instance_loader.rb" +/bin/rm -Rf "${3}/puppet/type/tidy.rb" -/bin/rm -Rf "${3}/puppet/util/platform.rb" +/bin/rm -Rf "${3}/puppet/type/user.rb" -/bin/rm -Rf "${3}/puppet/util/log.rb" +/bin/rm -Rf "${3}/puppet/type/vlan.rb" -/bin/rm -Rf "${3}/puppet/util/feature.rb" +/bin/rm -Rf "${3}/puppet/type/whit.rb" -/bin/rm -Rf "${3}/puppet/util/provider_features.rb" +/bin/rm -Rf "${3}/puppet/type/yumrepo.rb" -/bin/rm -Rf "${3}/puppet/util/command_line.rb" +/bin/rm -Rf "${3}/puppet/type/zfs.rb" -/bin/rm -Rf "${3}/puppet/util/command_line/puppet_option_parser.rb" +/bin/rm -Rf "${3}/puppet/type/zone.rb" -/bin/rm -Rf "${3}/puppet/util/command_line/trollop.rb" +/bin/rm -Rf "${3}/puppet/type/zpool.rb" -/bin/rm -Rf "${3}/puppet/util/fileparsing.rb" +/bin/rm -Rf "${3}/puppet/util.rb" -/bin/rm -Rf "${3}/puppet/util/backups.rb" +/bin/rm -Rf "${3}/puppet/util/adsi.rb" -/bin/rm -Rf "${3}/puppet/util/posix.rb" +/bin/rm -Rf "${3}/puppet/util/autoload.rb" -/bin/rm -Rf "${3}/puppet/util/rails/reference_serializer.rb" +/bin/rm -Rf "${3}/puppet/util/backups.rb" -/bin/rm -Rf "${3}/puppet/util/rails/collection_merger.rb" +/bin/rm -Rf "${3}/puppet/util/cacher.rb" -/bin/rm -Rf "${3}/puppet/util/rails/cache_accumulator.rb" +/bin/rm -Rf "${3}/puppet/util/checksums.rb" -/bin/rm -Rf "${3}/puppet/util/pson.rb" +/bin/rm -Rf "${3}/puppet/util/classgen.rb" -/bin/rm -Rf "${3}/puppet/util/symbolic_file_mode.rb" +/bin/rm -Rf "${3}/puppet/util/colors.rb" -/bin/rm -Rf "${3}/puppet/util/selinux.rb" +/bin/rm -Rf "${3}/puppet/util/command_line.rb" -/bin/rm -Rf "${3}/puppet/util/resource_template.rb" +/bin/rm -Rf "${3}/puppet/util/command_line/puppet_option_parser.rb" -/bin/rm -Rf "${3}/puppet/util/log_paths.rb" +/bin/rm -Rf "${3}/puppet/util/command_line/trollop.rb" -/bin/rm -Rf "${3}/puppet/util/windows.rb" +/bin/rm -Rf "${3}/puppet/util/constant_inflector.rb" -/bin/rm -Rf "${3}/puppet/util/classgen.rb" +/bin/rm -Rf "${3}/puppet/util/diff.rb" -/bin/rm -Rf "${3}/puppet/util/instrumentation.rb" +/bin/rm -Rf "${3}/puppet/util/docs.rb" -/bin/rm -Rf "${3}/puppet/util/rdoc/generators/puppet_generator.rb" +/bin/rm -Rf "${3}/puppet/util/errors.rb" -/bin/rm -Rf "${3}/puppet/util/rdoc/generators/template/puppet/puppet.rb" +/bin/rm -Rf "${3}/puppet/util/execution.rb" -/bin/rm -Rf "${3}/puppet/util/rdoc/parser.rb" +/bin/rm -Rf "${3}/puppet/util/execution_stub.rb" -/bin/rm -Rf "${3}/puppet/util/rdoc/code_objects.rb" +/bin/rm -Rf "${3}/puppet/util/feature.rb" -/bin/rm -Rf "${3}/puppet/util/colors.rb" +/bin/rm -Rf "${3}/puppet/util/fileparsing.rb" -/bin/rm -Rf "${3}/puppet/util/monkey_patches/lines.rb" +/bin/rm -Rf "${3}/puppet/util/filetype.rb" -/bin/rm -Rf "${3}/puppet/util/inifile.rb" +/bin/rm -Rf "${3}/puppet/util/graph.rb" -/bin/rm -Rf "${3}/puppet/util/nagios_maker.rb" +/bin/rm -Rf "${3}/puppet/util/inifile.rb" -/bin/rm -Rf "${3}/puppet/util/package.rb" +/bin/rm -Rf "${3}/puppet/util/inline_docs.rb" -/bin/rm -Rf "${3}/puppet/util/reference.rb" +/bin/rm -Rf "${3}/puppet/util/instance_loader.rb" -/bin/rm -Rf "${3}/puppet/file_serving/content.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation.rb" -/bin/rm -Rf "${3}/puppet/file_serving/metadata.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation/data.rb" -/bin/rm -Rf "${3}/puppet/file_serving/mount.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation/indirection_probe.rb" -/bin/rm -Rf "${3}/puppet/file_serving/configuration/parser.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation/instrumentable.rb" -/bin/rm -Rf "${3}/puppet/file_serving/fileset.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation/listener.rb" -/bin/rm -Rf "${3}/puppet/file_serving/terminus_selector.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation/listeners/log.rb" -/bin/rm -Rf "${3}/puppet/file_serving/terminus_helper.rb" +/bin/rm -Rf "${3}/puppet/util/instrumentation/listeners/performance.rb" -/bin/rm -Rf "${3}/puppet/file_serving/base.rb" +/bin/rm -Rf "${3}/puppet/util/json_lockfile.rb" -/bin/rm -Rf "${3}/puppet/file_serving/mount/plugins.rb" +/bin/rm -Rf "${3}/puppet/util/ldap.rb" -/bin/rm -Rf "${3}/puppet/file_serving/mount/file.rb" +/bin/rm -Rf "${3}/puppet/util/ldap/connection.rb" -/bin/rm -Rf "${3}/puppet/file_serving/mount/modules.rb" +/bin/rm -Rf "${3}/puppet/util/ldap/generator.rb" -/bin/rm -Rf "${3}/puppet/file_serving/configuration.rb" +/bin/rm -Rf "${3}/puppet/util/ldap/manager.rb" -/bin/rm -Rf "${3}/puppet/rails/resource.rb" +/bin/rm -Rf "${3}/puppet/util/libuser.conf" -/bin/rm -Rf "${3}/puppet/rails/puppet_tag.rb" +/bin/rm -Rf "${3}/puppet/util/libuser.rb" -/bin/rm -Rf "${3}/puppet/rails/database/001_add_created_at_to_all_tables.rb" +/bin/rm -Rf "${3}/puppet/util/loadedfile.rb" -/bin/rm -Rf "${3}/puppet/rails/database/schema.rb" +/bin/rm -Rf "${3}/puppet/util/lockfile.rb" -/bin/rm -Rf "${3}/puppet/rails/database/002_remove_duplicated_index_on_all_tables.rb" +/bin/rm -Rf "${3}/puppet/util/log.rb" -/bin/rm -Rf "${3}/puppet/rails/database/004_add_inventory_service_tables.rb" +/bin/rm -Rf "${3}/puppet/util/log/destination.rb" -/bin/rm -Rf "${3}/puppet/rails/database/003_add_environment_to_host.rb" +/bin/rm -Rf "${3}/puppet/util/log/destinations.rb" -/bin/rm -Rf "${3}/puppet/rails/resource_tag.rb" +/bin/rm -Rf "${3}/puppet/util/log/rate_limited_logger.rb" -/bin/rm -Rf "${3}/puppet/rails/inventory_fact.rb" +/bin/rm -Rf "${3}/puppet/util/log_paths.rb" -/bin/rm -Rf "${3}/puppet/rails/param_value.rb" +/bin/rm -Rf "${3}/puppet/util/logging.rb" -/bin/rm -Rf "${3}/puppet/rails/fact_name.rb" +/bin/rm -Rf "${3}/puppet/util/metaid.rb" -/bin/rm -Rf "${3}/puppet/rails/host.rb" +/bin/rm -Rf "${3}/puppet/util/methodhelper.rb" -/bin/rm -Rf "${3}/puppet/rails/source_file.rb" +/bin/rm -Rf "${3}/puppet/util/metric.rb" -/bin/rm -Rf "${3}/puppet/rails/fact_value.rb" +/bin/rm -Rf "${3}/puppet/util/monkey_patches.rb" -/bin/rm -Rf "${3}/puppet/rails/benchmark.rb" +/bin/rm -Rf "${3}/puppet/util/monkey_patches/lines.rb" -/bin/rm -Rf "${3}/puppet/rails/inventory_node.rb" +/bin/rm -Rf "${3}/puppet/util/nagios_maker.rb" -/bin/rm -Rf "${3}/puppet/rails/param_name.rb" +/bin/rm -Rf "${3}/puppet/util/network_device.rb" -/bin/rm -Rf "${3}/puppet/external/nagios.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/base.rb" -/bin/rm -Rf "${3}/puppet/external/dot.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/cisco.rb" -/bin/rm -Rf "${3}/puppet/external/nagios/grammar.ry" +/bin/rm -Rf "${3}/puppet/util/network_device/cisco/device.rb" -/bin/rm -Rf "${3}/puppet/external/nagios/parser.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/cisco/facts.rb" -/bin/rm -Rf "${3}/puppet/external/nagios/base.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/cisco/interface.rb" -/bin/rm -Rf "${3}/puppet/external/nagios/makefile" +/bin/rm -Rf "${3}/puppet/util/network_device/config.rb" -/bin/rm -Rf "${3}/puppet/external/lock.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/ipcalc.rb" -/bin/rm -Rf "${3}/puppet/external/pson/version.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/transport.rb" -/bin/rm -Rf "${3}/puppet/external/pson/pure.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/transport/base.rb" -/bin/rm -Rf "${3}/puppet/external/pson/pure/generator.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/transport/ssh.rb" -/bin/rm -Rf "${3}/puppet/external/pson/pure/parser.rb" +/bin/rm -Rf "${3}/puppet/util/network_device/transport/telnet.rb" -/bin/rm -Rf "${3}/puppet/external/pson/common.rb" +/bin/rm -Rf "${3}/puppet/util/package.rb" -/bin/rm -Rf "${3}/puppet/external/base64.rb" +/bin/rm -Rf "${3}/puppet/util/pidlock.rb" -/bin/rm -Rf "${3}/puppet/settings/base_setting.rb" +/bin/rm -Rf "${3}/puppet/util/platform.rb" -/bin/rm -Rf "${3}/puppet/settings/config_file.rb" +/bin/rm -Rf "${3}/puppet/util/plugins.rb" -/bin/rm -Rf "${3}/puppet/settings/value_translator.rb" +/bin/rm -Rf "${3}/puppet/util/posix.rb" -/bin/rm -Rf "${3}/puppet/settings/boolean_setting.rb" +/bin/rm -Rf "${3}/puppet/util/profiler.rb" -/bin/rm -Rf "${3}/puppet/settings/errors.rb" +/bin/rm -Rf "${3}/puppet/util/profiler/logging.rb" -/bin/rm -Rf "${3}/puppet/settings/file_setting.rb" +/bin/rm -Rf "${3}/puppet/util/profiler/none.rb" -/bin/rm -Rf "${3}/puppet/settings/path_setting.rb" +/bin/rm -Rf "${3}/puppet/util/profiler/object_counts.rb" -/bin/rm -Rf "${3}/puppet/settings/duration_setting.rb" +/bin/rm -Rf "${3}/puppet/util/profiler/wall_clock.rb" -/bin/rm -Rf "${3}/puppet/settings/directory_setting.rb" +/bin/rm -Rf "${3}/puppet/util/provider_features.rb" -/bin/rm -Rf "${3}/puppet/settings/terminus_setting.rb" +/bin/rm -Rf "${3}/puppet/util/pson.rb" -/bin/rm -Rf "${3}/puppet/settings/string_setting.rb" +/bin/rm -Rf "${3}/puppet/util/queue.rb" -/bin/rm -Rf "${3}/puppet/parameter/value_collection.rb" +/bin/rm -Rf "${3}/puppet/util/queue/stomp.rb" -/bin/rm -Rf "${3}/puppet/parameter/package_options.rb" +/bin/rm -Rf "${3}/puppet/util/rails/cache_accumulator.rb" -/bin/rm -Rf "${3}/puppet/parameter/value.rb" +/bin/rm -Rf "${3}/puppet/util/rails/collection_merger.rb" -/bin/rm -Rf "${3}/puppet/parameter/path.rb" +/bin/rm -Rf "${3}/puppet/util/rails/reference_serializer.rb" -/bin/rm -Rf "${3}/puppet/reports.rb" +/bin/rm -Rf "${3}/puppet/util/rdoc.rb" -/bin/rm -Rf "${3}/puppet/interface.rb" +/bin/rm -Rf "${3}/puppet/util/rdoc/code_objects.rb" -/bin/rm -Rf "${3}/puppet/network/format.rb" +/bin/rm -Rf "${3}/puppet/util/rdoc/generators/puppet_generator.rb" -/bin/rm -Rf "${3}/puppet/network/authstore.rb" +/bin/rm -Rf "${3}/puppet/util/rdoc/generators/template/puppet/puppet.rb" -/bin/rm -Rf "${3}/puppet/network/authorization.rb" +/bin/rm -Rf "${3}/puppet/util/rdoc/parser.rb" -/bin/rm -Rf "${3}/puppet/network/resolver.rb" +/bin/rm -Rf "${3}/puppet/util/reference.rb" -/bin/rm -Rf "${3}/puppet/network/http_pool.rb" +/bin/rm -Rf "${3}/puppet/util/resource_template.rb" -/bin/rm -Rf "${3}/puppet/network/rest_controller.rb" +/bin/rm -Rf "${3}/puppet/util/retryaction.rb" -/bin/rm -Rf "${3}/puppet/network/auth_config_parser.rb" +/bin/rm -Rf "${3}/puppet/util/rubygems.rb" -/bin/rm -Rf "${3}/puppet/network/client_request.rb" +/bin/rm -Rf "${3}/puppet/util/run_mode.rb" -/bin/rm -Rf "${3}/puppet/network/server.rb" +/bin/rm -Rf "${3}/puppet/util/selinux.rb" -/bin/rm -Rf "${3}/puppet/network/formats.rb" +/bin/rm -Rf "${3}/puppet/util/ssl.rb" -/bin/rm -Rf "${3}/puppet/network/http/webrick/rest.rb" +/bin/rm -Rf "${3}/puppet/util/storage.rb" -/bin/rm -Rf "${3}/puppet/network/http/connection.rb" +/bin/rm -Rf "${3}/puppet/util/subclass_loader.rb" -/bin/rm -Rf "${3}/puppet/network/http/handler.rb" +/bin/rm -Rf "${3}/puppet/util/suidmanager.rb" -/bin/rm -Rf "${3}/puppet/network/http/compression.rb" +/bin/rm -Rf "${3}/puppet/util/symbolic_file_mode.rb" -/bin/rm -Rf "${3}/puppet/network/http/api/v1.rb" +/bin/rm -Rf "${3}/puppet/util/tagging.rb" -/bin/rm -Rf "${3}/puppet/network/http/api.rb" +/bin/rm -Rf "${3}/puppet/util/terminal.rb" -/bin/rm -Rf "${3}/puppet/network/http/rack/httphandler.rb" +/bin/rm -Rf "${3}/puppet/util/user_attr.rb" -/bin/rm -Rf "${3}/puppet/network/http/rack/rest.rb" +/bin/rm -Rf "${3}/puppet/util/warnings.rb" -/bin/rm -Rf "${3}/puppet/network/http/webrick.rb" +/bin/rm -Rf "${3}/puppet/util/windows.rb" -/bin/rm -Rf "${3}/puppet/network/http/rack.rb" +/bin/rm -Rf "${3}/puppet/util/windows/error.rb" -/bin/rm -Rf "${3}/puppet/network/authconfig.rb" +/bin/rm -Rf "${3}/puppet/util/windows/file.rb" -/bin/rm -Rf "${3}/puppet/network/format_handler.rb" +/bin/rm -Rf "${3}/puppet/util/windows/process.rb" -/bin/rm -Rf "${3}/puppet/network/rights.rb" +/bin/rm -Rf "${3}/puppet/util/windows/registry.rb" -/bin/rm -Rf "${3}/puppet/network/authentication.rb" +/bin/rm -Rf "${3}/puppet/util/windows/root_certs.rb" -/bin/rm -Rf "${3}/puppet/network/http.rb" +/bin/rm -Rf "${3}/puppet/util/windows/security.rb" -/bin/rm -Rf "${3}/puppet/rails.rb" +/bin/rm -Rf "${3}/puppet/util/windows/sid.rb" -/bin/rm -Rf "${3}/puppet/metatype/manager.rb" +/bin/rm -Rf "${3}/puppet/util/windows/user.rb" -/bin/rm -Rf "${3}/puppet/provider.rb" +/bin/rm -Rf "${3}/puppet/util/zaml.rb" -/bin/rm -Rf "${3}/hiera/backend/puppet_backend.rb" +/bin/rm -Rf "${3}/puppet/version.rb" -/bin/rm -Rf "${3}/hiera/scope.rb" +/bin/rm -Rf "${3}/semver.rb" -/bin/rm -Rf "${3}/puppet.rb" +/bin/rm -Rf "${3}/extlookup2hiera" /bin/rm -Rf "${3}/puppet" -/bin/rm -Rf "${3}/extlookup2hiera" - # remove old doc files diff --git a/ext/osx/prototype.plist b/ext/osx/prototype.plist index fb7fe46bd..4e02fba22 100644 --- a/ext/osx/prototype.plist +++ b/ext/osx/prototype.plist @@ -5,7 +5,7 @@ <key>CFBundleIdentifier</key> <string></string> <key>CFBundleShortVersionString</key> - <string>3.1.1</string> + <string>3.2.1</string> <key>IFMajorVersion</key> <integer></integer> <key>IFMinorVersion</key> diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 33f6b7444..dcac8e715 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -17,6 +17,7 @@ gem_forge_project: 'puppet' gem_runtime_dependencies: facter: '~> 1.6' hiera: '~> 1.0' + rgen: '~> 0.6' gem_rdoc_options: - --title - "Puppet - Configuration Management" diff --git a/ext/puppet-nm-dispatcher b/ext/puppet-nm-dispatcher new file mode 100755 index 000000000..be27b0cfc --- /dev/null +++ b/ext/puppet-nm-dispatcher @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Restart puppet on network changes to pickup changes to /etc/resolv.conf +# +# https://projects.puppetlabs.com/issues/2776 +# https://bugzilla.redhat.com/532085 + + +if [ -f "/bin/systemctl" ] ; then + [[ $2 =~ ^(up|down)$ ]] && /bin/systemctl try-restart puppet.service || : +else + [[ $2 =~ ^(up|down)$ ]] && /sbin/service puppet condrestart || : +fi diff --git a/ext/redhat/puppet.spec b/ext/redhat/puppet.spec index fde5fcb00..2c7069d66 100644 --- a/ext/redhat/puppet.spec +++ b/ext/redhat/puppet.spec @@ -16,8 +16,8 @@ %endif # VERSION is subbed out during rake srpm process -%global realversion 3.1.1 -%global rpmversion 3.1.1 +%global realversion 3.2.1 +%global rpmversion 3.2.1 %global confdir ext/redhat @@ -56,6 +56,7 @@ Requires: facter >= 1.6.11 # Ruby 1.8.7 available for el5 at: yum.puppetlabs.com/el/5/devel/$ARCH Requires: ruby >= 1.8.7 Requires: hiera >= 1.0.0 +Requires: ruby-rgen Obsoletes: hiera-puppet < 1.0.0 Provides: hiera-puppet >= 1.0.0 %{!?_without_augeas:Requires: ruby-augeas} @@ -168,12 +169,22 @@ echo "D /var/run/%{name} 0755 %{name} %{name} -" > \ # Create puppet modules directory for puppet module tool mkdir -p %{buildroot}%{_sysconfdir}/%{name}/modules + +# Install a NetworkManager dispatcher script to pickup changes to +# # /etc/resolv.conf and such (https://bugzilla.redhat.com/532085). +mkdir -p %{buildroot}%{_sysconfdir}/NetworkManager/dispatcher.d +cp -pr ext/puppet-nm-dispatcher \ + %{buildroot}%{_sysconfdir}/NetworkManager/dispatcher.d/98-%{name} + %files %defattr(-, root, root, 0755) %doc LICENSE README.md examples %{_bindir}/puppet %{_bindir}/extlookup2hiera %{puppet_libdir}/* +%dir %{_sysconfdir}/NetworkManager +%dir %{_sysconfdir}/NetworkManager/dispatcher.d +%{_sysconfdir}/NetworkManager/dispatcher.d/98-puppet %if 0%{?_with_systemd} %{_unitdir}/puppetagent.service %else @@ -377,8 +388,11 @@ fi rm -rf %{buildroot} %changelog -* Fri Mar 08 2013 Puppet Labs Release <info@puppetlabs.com> - 3.1.1-1 -- Build for 3.1.1 +* Wed May 22 2013 Puppet Labs Release <info@puppetlabs.com> - 3.2.1-1 +- Build for 3.2.1 + +* Fri Apr 12 2013 Matthaus Owens <matthaus@puppetlabs.com> - 3.2.0-0.1rc0 +- Add requires on ruby-rgen for new parser in Puppet 3.2 * Fri Jan 25 2013 Matthaus Owens <matthaus@puppetlabs.com> - 3.1.0-0.1rc1 - Add extlookup2hiera.8.gz to the files list @@ -386,6 +400,9 @@ rm -rf %{buildroot} * Wed Jan 9 2013 Ryan Uber <ru@ryanuber.com> - 3.1.0-0.1rc1 - Work-around for RH Bugzilla 681540 +* Fri Dec 28 2012 Michael Stahnke <stahnma@puppetlabs.com> - 3.0.2-2 +- Added a script for Network Manager for bug https://bugzilla.redhat.com/532085 + * Tue Dec 18 2012 Matthaus Owens <matthaus@puppetlabs.com> - Remove for loop on examples/ code which no longer exists. Add --no-run-if-empty to xargs invocations. diff --git a/ext/suse/client.init b/ext/suse/client.init index 71f562d99..e54f462f4 100644 --- a/ext/suse/client.init +++ b/ext/suse/client.init @@ -39,7 +39,7 @@ puppetd=${PUPPETD-/usr/bin/puppet} RETVAL=0 PUPPET_OPTS="agent" -[ -n "${PUPPET_SERVER}" ] && PUPPET_OPTS="--server=${PUPPET_SERVER}" +[ -n "${PUPPET_SERVER}" ] && PUPPET_OPTS="${PUPPET_OPTS} --server=${PUPPET_SERVER}" [ -n "$PUPPET_LOG" ] && PUPPET_OPTS="${PUPPET_OPTS} --logdest=${PUPPET_LOG}" [ -n "$PUPPET_PORT" ] && PUPPET_OPTS="${PUPPET_OPTS} --port=${PUPPET_PORT}" diff --git a/ext/systemd/puppetagent.service b/ext/systemd/puppetagent.service index adefa8272..6312a0509 100644 --- a/ext/systemd/puppetagent.service +++ b/ext/systemd/puppetagent.service @@ -5,9 +5,9 @@ After=basic.target network.target [Service] Type=forking +EnvironmentFile=-/etc/sysconfig/puppetagent PIDFile=/run/puppet/agent.pid -ExecStartPre=/usr/bin/install -d -o puppet -m 755 /run/puppet -ExecStart=/usr/bin/puppet agent +ExecStart=/usr/bin/puppet agent $PUPPET_EXTRA_OPTS [Install] WantedBy=multi-user.target diff --git a/ext/systemd/puppetmaster.service b/ext/systemd/puppetmaster.service index 334b1e084..054e51846 100644 --- a/ext/systemd/puppetmaster.service +++ b/ext/systemd/puppetmaster.service @@ -5,9 +5,9 @@ After=basic.target network.target [Service] Type=forking +EnvironmentFile=-/etc/sysconfig/puppetmaster PIDFile=/run/puppet/master.pid -ExecStartPre=/usr/bin/install -d -o puppet -m 755 /run/puppet -ExecStart=/usr/bin/puppet master +ExecStart=/usr/bin/puppet master $PUPPETMASTER_EXTRA_OPTS [Install] WantedBy=multi-user.target diff --git a/lib/hiera/scope.rb b/lib/hiera/scope.rb index 6329a78bd..781c1eb3a 100644 --- a/lib/hiera/scope.rb +++ b/lib/hiera/scope.rb @@ -1,5 +1,9 @@ class Hiera class Scope + CALLING_CLASS = "calling_class" + CALLING_MODULE = "calling_module" + MODULE_NAME = "module_name" + attr_reader :real def initialize(real) @@ -7,23 +11,27 @@ class Hiera end def [](key) - if key == "calling_class" - ans = @real.resource.name.to_s.downcase - elsif key == "calling_module" - ans = @real.resource.name.to_s.downcase.split("::").first + if key == CALLING_CLASS + ans = find_hostclass(@real) + elsif key == CALLING_MODULE + ans = @real.lookupvar(MODULE_NAME) else ans = @real.lookupvar(key) end - # damn you puppet visual basic style variables. - return nil if ans == "" - return ans + if ans.nil? or ans == "" + nil + else + ans + end end def include?(key) - return true if ["calling_class", "calling_module"].include?(key) - - return @real.lookupvar(key) != "" + if key == CALLING_CLASS or key == CALLING_MODULE + true + else + @real.lookupvar(key) != "" + end end def catalog @@ -37,6 +45,16 @@ class Hiera def compiler @real.compiler end + + def find_hostclass(scope) + if scope.source and scope.source.type == :hostclass + return scope.source.name.downcase + elsif scope.parent + return find_hostclass(scope.parent) + else + return nil + end + end + private :find_hostclass end end - diff --git a/lib/hiera_puppet.rb b/lib/hiera_puppet.rb index 3368c64cd..0e6a572a8 100644 --- a/lib/hiera_puppet.rb +++ b/lib/hiera_puppet.rb @@ -6,9 +6,7 @@ module HieraPuppet module_function def lookup(key, default, scope, override, resolution_type) - unless scope.respond_to?("[]") - scope = Hiera::Scope.new(scope) - end + scope = Hiera::Scope.new(scope) answer = hiera.lookup(key, default, scope, override, resolution_type) diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 6681ad4bb..678fdb5d0 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -37,7 +37,7 @@ class Puppet::Agent result = nil block_run = Puppet::Application.controlled_run do - splay + splay client_options.fetch :splay, Puppet[:splay] result = run_in_fork(should_fork) do with_client do |client| begin @@ -66,8 +66,8 @@ class Puppet::Agent end # Sleep when splay is enabled; else just return. - def splay - return unless Puppet[:splay] + def splay(do_splay = Puppet[:splay]) + return unless do_splay return if splayed? time = rand(Puppet[:splaylimit] + 1) diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index 110ababae..82820dbcb 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -376,8 +376,8 @@ class Application setup_logs end - def setup_logs - if options[:debug] or options[:verbose] + def setup_logs(is_daemon = false) + if options[:debug] or options[:verbose] or is_daemon Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index e919242f7..6ed3242c0 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -166,16 +166,16 @@ when signing certificates). OPTIONS ------- -Note that any configuration parameter that's valid in the configuration -file is also a valid long argument. For example, 'server' is a valid -configuration parameter, so you can specify '--server <servername>' as -an argument. + +Note that any Puppet setting that's valid in the configuration file is also a +valid long argument. For example, 'server' is a valid setting, so you can +specify '--server <servername>' as an argument. Boolean settings translate into +'--setting' and '--no-setting' pairs. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the -full list of acceptable parameters. A commented list of all -configuration options can also be generated by running puppet agent with -'--genconfig'. +full list of acceptable settings. A commented list of all settings can also be +generated by running puppet agent with '--genconfig'. * --certname: Set the certname (unique ID) of the client. The master reads this @@ -183,12 +183,17 @@ configuration options can also be generated by running puppet agent with fully-qualified domain name, to determine which configurations the node will receive. Use this option to debug setup problems or implement unusual node identification schemes. + (This is a Puppet setting, and can go in puppet.conf.) * --daemonize: Send the process into the background. This is the default. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) * --no-daemonize: Do not send the process into the background. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) * --debug: Enable full debugging. @@ -240,26 +245,34 @@ configuration options can also be generated by running puppet agent with file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. +* --masterport: + The port on which to contact the puppet master. + (This is a Puppet setting, and can go in puppet.conf.) + * --no-client: - Do not create a config client. This will cause the daemon to start - but not check configuration unless it is triggered with `puppet - kick`. This only makes sense when puppet agent is being run with + Do not create a config client. This will cause the daemon to start + but not check configuration unless it is triggered with `puppet + kick`. This only makes sense when puppet agent is being run with listen = true in puppet.conf or was started with the `--listen` option. * --noop: Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) * --onetime: Run the configuration once. Runs a single (normally daemonized) Puppet run. Useful for interactively running puppet agent when used in conjunction with the --no-daemonize option. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', - 'detailed-exit-codes', 'no-splay', and 'show_diff'. + 'detailed-exitcodes', 'no-splay', and 'show_diff'. * --verbose: Turn on verbose reporting. @@ -274,6 +287,8 @@ configuration options can also be generated by running puppet agent with it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) EXAMPLE @@ -430,7 +445,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License def setup setup_test if options[:test] - setup_logs + setup_logs(true) exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index 2a86950d4..8fa01b179 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -12,6 +12,8 @@ class Puppet::Application::Apply < Puppet::Application option("--use-nodes") option("--detailed-exitcodes") + option("--write-catalog-summary") + option("--catalog catalog", "-c catalog") do |arg| options[:catalog] = arg end @@ -25,7 +27,7 @@ class Puppet::Application::Apply < Puppet::Application end end - option("--parseonly") do + option("--parseonly") do |args| puts "--parseonly has been removed. Please use 'puppet parser validate <manifest>'" exit 1 end @@ -45,7 +47,7 @@ USAGE ----- puppet apply [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-e|--execute] [--detailed-exitcodes] [-l|--logdest <file>] [--noop] - [--catalog <catalog>] <file> + [--catalog <catalog>] [--write-catalog-summary] <file> DESCRIPTION @@ -113,6 +115,9 @@ configuration options can also be generated by running puppet with Apply a JSON catalog (such as one generated with 'puppet master --compile'). You can either specify a JSON file or pipe in JSON from standard input. +* --write-catalog-summary + After compiling the catalog saves the resource list and classes list to the node + in the state directory named classes.txt and resources.txt EXAMPLE ------- @@ -210,6 +215,11 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License catalog.retrieval_duration = Time.now - starttime + if options[:write_catalog_summary] + catalog.write_class_file + catalog.write_resource_file + end + exit_status = apply_catalog(catalog) if not exit_status diff --git a/lib/puppet/application/describe.rb b/lib/puppet/application/describe.rb index a84d92227..9d8cf35d6 100644 --- a/lib/puppet/application/describe.rb +++ b/lib/puppet/application/describe.rb @@ -70,7 +70,7 @@ class TypeDoc }.each do |name| type = @types[name] s = type.doc.gsub(/\s+/, " ") - n = s.index(".") + n = s.index(". ") if n.nil? s = ".. no documentation .." elsif n > 45 diff --git a/lib/puppet/application/doc.rb b/lib/puppet/application/doc.rb index eb2e77ed5..32a378e6a 100644 --- a/lib/puppet/application/doc.rb +++ b/lib/puppet/application/doc.rb @@ -235,6 +235,8 @@ HELP else setup_reference end + + setup_logging end def setup_reference @@ -261,16 +263,18 @@ HELP Puppet.settings.handlearg(option[:opt], option[:arg]) end end + end - # Handle the logging settings. - if options[:debug] or options[:verbose] - if options[:debug] - Puppet::Util::Log.level = :debug - else - Puppet::Util::Log.level = :info - end - - Puppet::Util::Log.newdestination(:console) + def setup_logging + # Handle the logging settings. + if options[:debug] + Puppet::Util::Log.level = :debug + elsif options[:verbose] + Puppet::Util::Log.level = :info + else + Puppet::Util::Log.level = :warning end + + Puppet::Util::Log.newdestination(:console) end end diff --git a/lib/puppet/application/filebucket.rb b/lib/puppet/application/filebucket.rb index 4bb7b375a..389e6e4ca 100644 --- a/lib/puppet/application/filebucket.rb +++ b/lib/puppet/application/filebucket.rb @@ -180,6 +180,5 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License exit(1) end end - end diff --git a/lib/puppet/application/kick.rb b/lib/puppet/application/kick.rb index 605f81902..6720aecf0 100644 --- a/lib/puppet/application/kick.rb +++ b/lib/puppet/application/kick.rb @@ -9,6 +9,7 @@ class Puppet::Application::Kick < Puppet::Application option("--debug","-d") option("--ping","-P") option("--test") + option("--ignoreschedules") option("--host HOST") do |arg| @hosts << arg diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index 69909beb3..80d044c19 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -23,7 +23,7 @@ class Puppet::Application::Master < Puppet::Application end end - option("--parseonly") do + option("--parseonly") do |args| puts "--parseonly has been removed. Please use 'puppet parser validate <manifest>'" exit 1 end @@ -57,22 +57,26 @@ executable is not used. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration -file is also a valid long argument. For example, 'ssldir' is a valid -configuration parameter, so you can specify '--ssldir <directory>' as an -argument. + +Note that any Puppet setting that's valid in the configuration file is also a +valid long argument. For example, 'server' is a valid setting, so you can +specify '--server <servername>' as an argument. Boolean settings translate into +'--setting' and '--no-setting' pairs. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the -full list of acceptable parameters. A commented list of all -configuration options can also be generated by running puppet master -with '--genconfig'. +full list of acceptable settings. A commented list of all settings can also be +generated by running puppet master with '--genconfig'. * --daemonize: Send the process into the background. This is the default. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) * --no-daemonize: Do not send the process into the background. + (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' + prefix for boolean settings on the command line.) * --debug: Enable full debugging. @@ -85,6 +89,10 @@ with '--genconfig'. file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. +* --masterport: + The port on which to listen for traffic. + (This is a Puppet setting, and can go in puppet.conf.) + * --verbose: Enable verbosity. @@ -165,7 +173,7 @@ Copyright (c) 2012 Puppet Labs, LLC Licensed under the Apache 2.0 License raise "Could not compile catalog for #{options[:node]}" end - jj catalog.to_resource + puts PSON::pretty_generate(catalog.to_resource, :allow_nan => true, :max_nesting => false) rescue => detail $stderr.puts detail exit(30) diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index fa64146bb..c67bf53b1 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,6 +1,7 @@ require 'puppet' require 'puppet/util/pidlock' require 'puppet/application' +require 'puppet/scheduler' # A module that handles operations common to all daemons. This is included # into the Server and Client base classes. @@ -79,7 +80,7 @@ class Puppet::Daemon return end - agent.run + agent.run({:splay => false}) end # Remove the pid file for our daemon. @@ -143,76 +144,32 @@ class Puppet::Daemon # Finally, loop forever running events - or, at least, until we exit. run_event_loop + + server.wait_for_shutdown if server end def run_event_loop - # Now, we loop waiting for either the configuration file to change, or the - # next agent run to be due. Fun times. - # - # We want to trigger the reparse if 15 seconds passed since the previous - # wakeup, and the agent run if Puppet[:runinterval] seconds have passed - # since the previous wakeup. - # - # We always want to run the agent on startup, so it was always before now. - # Because 0 means "continuously run", `to_i` does the right thing when the - # input is strange or badly formed by returning 0. Integer will raise, - # which we don't want, and we want to protect against -1 or below. - next_agent_run = 0 - agent_run_interval = [Puppet[:runinterval], 0].max - - # We may not want to reparse; that can be disable. Fun times. - next_reparse = 0 - reparse_interval = Puppet[:filetimeout] - - loop do - now = Time.now.to_i - - # We set a default wakeup of "one hour from now", which will - # recheck everything at a minimum every hour. Just in case something in - # the math messes up or something; it should be inexpensive enough to - # wake once an hour, then go back to sleep after doing nothing, if - # someone only wants listen mode. - next_event = now + 60 * 60 - - # Handle reparsing of configuration files, if desired and required. - # `reparse` will just check if the action is required, and would be - # better named `reparse_if_changed` instead. - if reparse_interval > 0 and now >= next_reparse - Puppet.settings.reparse_config_files - - # The time to the next reparse might have changed, so recalculate - # now. That way we react dynamically to reconfiguration. - reparse_interval = Puppet[:filetimeout] - - # Set up the next reparse check based on the new reparse_interval. - if reparse_interval > 0 - next_reparse = now + reparse_interval - next_event > next_reparse and next_event = next_reparse - end - - # We should also recalculate the agent run interval, and adjust the - # next time it is scheduled to run, just in case. In the event that - # we made no change the result will be a zero second adjustment. - new_run_interval = [Puppet[:runinterval], 0].max - next_agent_run += agent_run_interval - new_run_interval - agent_run_interval = new_run_interval + agent_run = Puppet::Scheduler.create_job(Puppet[:runinterval], Puppet[:splay], Puppet[:splaylimit]) do + # Splay for the daemon is handled in the scheduler + agent.run(:splay => false) + end + + reparse_run = Puppet::Scheduler.create_job(Puppet[:filetimeout]) do + Puppet.settings.reparse_config_files + agent_run.run_interval = Puppet[:runinterval] + if Puppet[:filetimeout] == 0 + reparse_run.disable + else + reparse_run.run_interval = Puppet[:filetimeout] end + end - # Handle triggering another agent run. This will block the next check - # for configuration reparsing, which is a desired and deliberate - # behaviour. You should not change that. --daniel 2012-02-21 - if agent and now >= next_agent_run - agent.run + reparse_run.disable if Puppet[:filetimeout] == 0 + agent_run.disable unless agent - # Set up the next agent run time - next_agent_run = now + agent_run_interval - next_event > next_agent_run and next_event = next_agent_run - end + scheduler = Puppet::Scheduler::Scheduler.new([reparse_run, agent_run]) - # Finally, an interruptable able sleep until the next scheduled event. - how_long = next_event - now - how_long > 0 and select([], [], [], how_long) - end + scheduler.run_loop end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index a83fc4bcf..24552b60c 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -49,6 +49,11 @@ module Puppet :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, @@ -214,7 +219,7 @@ module Puppet :node_cache_terminus => { :type => :terminus, :default => nil, - :desc => "How to store cached nodes. + :desc => "How to store cached nodes. Valid values are (none), 'json', 'yaml' or write only yaml ('write_only_yaml'). The master application defaults to 'write_only_yaml', all others to none.", }, @@ -225,7 +230,7 @@ module Puppet }, :hiera_config => { :default => "$confdir/hiera.yaml", - :desc => "The hiera configuration file", + :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, }, :catalog_terminus => { @@ -772,7 +777,9 @@ EOT }, :masterport => { :default => 8140, - :desc => "Which port puppet master listens on.", + :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", @@ -813,13 +820,26 @@ EOT :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`).", + 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.", + 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). @@ -1075,6 +1095,13 @@ EOT 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, @@ -1505,7 +1532,43 @@ 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 'curent' 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 fatures 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 value 1. 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 errors have been detected. +A value of 0 is the same as value 1. 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 errors have been detected. +A value of 0 is the same as value 1. The count is per manifest. +EOT } + ) define_settings(:puppetdoc, :document_all => { diff --git a/lib/puppet/error.rb b/lib/puppet/error.rb index dceeea38e..2e498636c 100644 --- a/lib/puppet/error.rb +++ b/lib/puppet/error.rb @@ -12,18 +12,29 @@ module Puppet # 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 + attr_accessor :line, :file, :pos - def initialize(message, file=nil, line=nil, original=nil) + # 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 @line = line + @pos = pos end - def to_s msg = super - if @file and @line + 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 diff --git a/lib/puppet/external/nagios/grammar.ry b/lib/puppet/external/nagios/grammar.ry index dc203be5c..82f0a907c 100644 --- a/lib/puppet/external/nagios/grammar.ry +++ b/lib/puppet/external/nagios/grammar.ry @@ -40,7 +40,7 @@ vars: var } ; -var: PARAM VALUE icomment returns { result = {val[0],val[1]} } +var: PARAM VALUE icomment returns { result = {val[0] => val[1]} } ; returns: RETURN diff --git a/lib/puppet/external/nagios/makefile b/lib/puppet/external/nagios/makefile index fc14564b7..a3c1ba150 100644 --- a/lib/puppet/external/nagios/makefile +++ b/lib/puppet/external/nagios/makefile @@ -3,7 +3,7 @@ all: parser.rb debug: parser.rb setdebug parser.rb: grammar.ry - racc -E -oparser.rb grammar.ry + racc -oparser.rb grammar.ry setdebug: perl -pi -e 's{\@yydebug =.*$$}{\@yydebug = true}' parser.rb diff --git a/lib/puppet/external/nagios/parser.rb b/lib/puppet/external/nagios/parser.rb index 17db5e307..e89192b3a 100644 --- a/lib/puppet/external/nagios/parser.rb +++ b/lib/puppet/external/nagios/parser.rb @@ -1,581 +1,175 @@ # # DO NOT MODIFY!!!! -# This file is automatically generated by racc 1.4.5 -# from racc grammer file "grammar.ry". +# This file is automatically generated by Racc 1.4.9 +# from Racc grammer file "". # -# -# parser.rb: generated by racc (runtime embedded) -# -###### racc/parser.rb begin -unless $LOADED_FEATURES.index 'racc/parser.rb' -$LOADED_FEATURES.push 'racc/parser.rb' - -self.class.module_eval <<'..end racc/parser.rb modeval..id5256434e8a', 'racc/parser.rb', 1 -# -# $Id: parser.rb,v 1.7 2005/11/20 17:31:32 aamine Exp $ -# -# Copyright (c) 1999-2005 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. -# -# As a special exception, when this code is copied by Racc -# into a Racc output file, you may use that output file -# without restriction. -# - -NotImplementedError = NotImplementError unless defined?(NotImplementedError) - -module Racc - class ParseError < StandardError; end -end -ParseError = Racc::ParseError unless defined?(::ParseError) - -module Racc - - Racc_No_Extentions = false unless defined?(Racc_No_Extentions) - - class Parser - - Racc_Runtime_Version = '1.4.5' - Racc_Runtime_Revision = '$Revision: 1.7 $'.split[1] - - Racc_Runtime_Core_Version_R = '1.4.5' - Racc_Runtime_Core_Revision_R = '$Revision: 1.7 $'.split[1] - begin - require 'racc/cparse' - # Racc_Runtime_Core_Version_C = (defined in extention) - Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2] - raise LoadError, 'old cparse.so' unless new.respond_to?(:_racc_do_parse_c, true) - raise LoadError, 'selecting ruby version of racc runtime core' if Racc_No_Extentions - - Racc_Main_Parsing_Routine = :_racc_do_parse_c - Racc_YY_Parse_Method = :_racc_yyparse_c - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C - Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C - Racc_Runtime_Type = 'c' - rescue LoadError - Racc_Main_Parsing_Routine = :_racc_do_parse_rb - Racc_YY_Parse_Method = :_racc_yyparse_rb - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R - Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R - Racc_Runtime_Type = 'ruby' - end - - def Parser.racc_runtime_type - Racc_Runtime_Type - end - - private - - def _racc_setup - @yydebug = false unless self.class::Racc_debug_parser - @yydebug ||= false - if @yydebug - @racc_debug_out ||= $stderr - @racc_debug_out ||= $stderr - end - arg = self.class::Racc_arg - arg[13] = true if arg.size < 14 - arg - end - - def _racc_init_sysvars - @racc_state = [0] - @racc_tstack = [] - @racc_vstack = [] - - @racc_t = nil - @racc_val = nil - - @racc_read_next = true - - @racc_user_yyerror = false - @racc_error_status = 0 - end - - ### - ### do_parse - ### - - def do_parse - __send__(Racc_Main_Parsing_Routine, _racc_setup, false) - end - - def next_token - raise NotImplementedError, "#{self.class}\#next_token is not defined" - end - - def _racc_do_parse_rb(arg, in_debug) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - - _racc_init_sysvars - tok = act = i = nil - nerr = 0 - - catch(:racc_end_parse) { - while true - if i = action_pointer[@racc_state[-1]] - if @racc_read_next - if @racc_t != 0 # not EOF - tok, @racc_val = next_token - unless tok # EOF - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - racc_read_token(@racc_t, tok, @racc_val) if @yydebug - @racc_read_next = false - end - end - i += @racc_t - unless i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - else - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - end - } - end - - ### - ### yyparse - ### - - def yyparse(recv, mid) - __send__(Racc_YY_Parse_Method, recv, mid, _racc_setup, true) - end - - def _racc_yyparse_rb(recv, mid, arg, c_debug) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - - _racc_init_sysvars - tok = nil - act = nil - i = nil - nerr = 0 - - catch(:racc_end_parse) { - until i = action_pointer[@racc_state[-1]] - while act = _racc_evalact(action_default[@racc_state[-1]], arg) - ; - end - end - recv.__send__(mid) do |tok, val| - unless tok - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - @racc_val = val - @racc_read_next = false - - i += @racc_t - unless i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - - while not (i = action_pointer[@racc_state[-1]]) or - not @racc_read_next or - @racc_t == 0 # $ - unless i and i += @racc_t and - i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - end - end - } - end - - ### - ### common - ### - - def _racc_evalact(act, arg) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - nerr = 0 # tmp - - if act > 0 and act < shift_n - # - # shift - # - if @racc_error_status > 0 - @racc_error_status -= 1 unless @racc_t == 1 # error token - end - @racc_vstack.push @racc_val - @racc_state.push act - @racc_read_next = true - if @yydebug - @racc_tstack.push @racc_t - racc_shift @racc_t, @racc_tstack, @racc_vstack - end - - elsif act < 0 and act > -reduce_n - # - # reduce - # - code = catch(:racc_jump) { - @racc_state.push _racc_do_reduce(arg, act) - false - } - if code - case code - when 1 # yyerror - @racc_user_yyerror = true # user_yyerror - return -reduce_n - when 2 # yyaccept - return shift_n - else - raise '[Racc Bug] unknown jump code' - end - end - - elsif act == shift_n - # - # accept - # - racc_accept if @yydebug - throw :racc_end_parse, @racc_vstack[0] - - elsif act == -reduce_n - # - # error - # - case @racc_error_status - when 0 - unless arg[21] # user_yyerror - nerr += 1 - on_error @racc_t, @racc_val, @racc_vstack - end - when 3 - if @racc_t == 0 # is $ - throw :racc_end_parse, nil - end - @racc_read_next = true - end - @racc_user_yyerror = false - @racc_error_status = 3 - while true - if i = action_pointer[@racc_state[-1]] - i += 1 # error token - if i >= 0 and - (act = action_table[i]) and - action_check[i] == @racc_state[-1] - break - end - end - throw :racc_end_parse, nil if @racc_state.size <= 1 - @racc_state.pop - @racc_vstack.pop - if @yydebug - @racc_tstack.pop - racc_e_pop @racc_state, @racc_tstack, @racc_vstack - end - end - return act - - else - raise "[Racc Bug] unknown action #{act.inspect}" - end - - racc_next_state(@racc_state[-1], @racc_state) if @yydebug - - nil - end - - def _racc_do_reduce(arg, act) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - state = @racc_state - vstack = @racc_vstack - tstack = @racc_tstack - - i = act * -3 - len = reduce_table[i] - reduce_to = reduce_table[i+1] - method_id = reduce_table[i+2] - void_array = [] - - tmp_t = tstack[-len, len] if @yydebug - tmp_v = vstack[-len, len] - tstack[-len, len] = void_array if @yydebug - vstack[-len, len] = void_array - state[-len, len] = void_array - - # tstack must be updated AFTER method call - if use_result - vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) - else - vstack.push __send__(method_id, tmp_v, vstack) - end - tstack.push reduce_to - - racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug - - k1 = reduce_to - nt_base - if i = goto_pointer[k1] - i += state[-1] - if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 - return curstate - end - end - goto_default[k1] - end - - def on_error(t, val, vstack) - raise ParseError, sprintf("\nparse error on value %s (%s)", val.inspect, token_to_str(t) || '?') - end - - def yyerror - throw :racc_jump, 1 - end - - def yyaccept - throw :racc_jump, 2 - end - - def yyerrok - @racc_error_status = 0 - end - - # - # for debugging output - # - - def racc_read_token(t, tok, val) - @racc_debug_out.print 'read ' - @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') ' - @racc_debug_out.puts val.inspect - @racc_debug_out.puts - end - - def racc_shift(tok, tstack, vstack) - @racc_debug_out.puts "shift #{racc_token2str tok}" - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_reduce(toks, sim, tstack, vstack) - out = @racc_debug_out - out.print 'reduce ' - if toks.empty? - out.print ' <none>' - else - toks.each {|t| out.print ' ', racc_token2str(t) } - end - out.puts " --> #{racc_token2str(sim)}" - - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_accept - @racc_debug_out.puts 'accept' - @racc_debug_out.puts - end - - def racc_e_pop(state, tstack, vstack) - @racc_debug_out.puts 'error recovering mode: pop token' - racc_print_states state - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_next_state(curstate, state) - @racc_debug_out.puts "goto #{curstate}" - racc_print_states state - @racc_debug_out.puts - end - - def racc_print_stacks(t, v) - out = @racc_debug_out - out.print ' [' - t.each_index do |i| - out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' - end - out.puts ' ]' - end - - def racc_print_states(s) - out = @racc_debug_out - out.print ' [' - s.each {|st| out.print ' ', st } - out.puts ' ]' - end - - def racc_token2str(tok) - self.class::Racc_token_to_s_table[tok] or - raise "[Racc Bug] can't convert token #{tok} to string" - end - - def token_to_str(t) - self.class::Racc_token_to_s_table[t] - end - - end - -end -..end racc/parser.rb modeval..id5256434e8a -end -###### racc/parser.rb end - +require 'racc/parser.rb' module Nagios - class Parser < Racc::Parser -module_eval <<'..end grammar.ry modeval..idcb2ea30b34', 'grammar.ry', 57 +module_eval(<<'...end grammar.ry/module_eval...', 'grammar.ry', 57) class ::Nagios::Parser::SyntaxError < RuntimeError; end def parse(src) - @src = src + @src = src - # state variables - @invar = false - @inobject = false - @done = false + # state variables + @invar = false + @inobject = false + @done = false - @line = 0 - @yydebug = true + @line = 0 + @yydebug = true - do_parse + do_parse end # The lexer. Very simple. def token - @src.sub!(/\A\n/,'') - if $MATCH - @line += 1 - return [ :RETURN, "\n" ] + @src.sub!(/\A\n/,'') + if $& + @line += 1 + return [ :RETURN, "\n" ] end - return nil if @done + if @done + return nil + end yytext = String.new # remove comments from this line @src.sub!(/\A[ \t]*;.*\n/,"\n") - return [:INLINECOMMENT, ""] if $MATCH + if $& + return [:INLINECOMMENT, ""] + end @src.sub!(/\A#.*\n/,"\n") - return [:COMMENT, ""] if $MATCH + if $& + return [:COMMENT, ""] + end @src.sub!(/#.*/,'') if @src.length == 0 - @done = true - return [false, '$'] + @done = true + return [false, '$'] end if @invar - @src.sub!(/\A[ \t]+/,'') - @src.sub!(/\A([^;\n]+)(\n|;)/,'\2') - if $1 - yytext += $1 + @src.sub!(/\A[ \t]+/,'') + @src.sub!(/\A([^;\n]+)(\n|;)/,'\2') + if $1 + yytext += $1 end @invar = false return [:VALUE, yytext] else - @src.sub!(/\A[\t ]*(\S+)([\t ]*|$)/,'') - if $1 - yytext = $1 - case yytext - when 'define' - #puts "got define" - return [:DEFINE, yytext] - when '{' - #puts "got {" - @inobject = true - return [:LCURLY, yytext] - else - unless @inobject - #puts "got type: #{yytext}" - if yytext =~ /\W/ - giveback = yytext.dup - giveback.sub!(/^\w+/,'') - #puts "giveback #{giveback}" - #puts "yytext #{yytext}" - yytext.sub!(/\W.*$/,'') - #puts "yytext #{yytext}" - #puts "all [#{giveback} #{yytext} #{orig}]" - @src = giveback + @src - end - return [:NAME, yytext] - else - if yytext == '}' - #puts "got closure: #{yytext}" - @inobject = false - return [:RCURLY, '}'] - end - - unless @invar - @invar = true - return [:PARAM, $1] - else - end + @src.sub!(/\A[\t ]*(\S+)([\t ]*|$)/,'') + if $1 + yytext = $1 + case yytext + when 'define' + #puts "got define" + return [:DEFINE, yytext] + when '{' + #puts "got {" + @inobject = true + return [:LCURLY, yytext] + else + unless @inobject + #puts "got type: #{yytext}" + if yytext =~ /\W/ + giveback = yytext.dup + giveback.sub!(/^\w+/,'') + #puts "giveback " + giveback + #puts "yytext " + yytext + yytext.sub!(/\W.*$/,'') + #puts "yytext " + yytext + #puts "all [#{giveback} #{yytext} #{orig}]" + @src = giveback + @src + end + return [:NAME, yytext] + else + if yytext == '}' + #puts "got closure: #{yytext}" + @inobject = false + return [:RCURLY, '}'] + end + + unless @invar + @invar = true + return [:PARAM, $1] + else + end + end + end end - end - end end end def next_token - token + token end def yydebug - 1 + 1 end def yywrap - 0 + 0 end def on_error(token, value, vstack ) - msg = "" - unless value.nil? - msg = "line #{@line}: syntax error at '#{value}'" + msg = "" + unless value.nil? + msg = "line #{@line}: syntax error at '#{value}'" else - msg = "line #{@line}: syntax error at '#{token}'" + msg = "line #{@line}: syntax error at '#{token}'" + end + unless @src.size > 0 + msg = "line #{@line}: Unexpected end of file" end - msg = "line #{@line}: Unexpected end of file" unless @src.size > 0 if token == '$end'.intern - puts "okay, this is silly" + puts "okay, this is silly" else - raise ::Nagios::Parser::SyntaxError, msg + raise ::Nagios::Parser::SyntaxError, msg end end -..end grammar.ry modeval..idcb2ea30b34 +...end grammar.ry/module_eval... +##### State transition tables begin ### + +racc_action_table = [ + 8, 17, 7, 18, 7, 14, 12, 13, 11, 4, + 6, 4, 6, 17, 10, 20, 22, 24, 25 ] + +racc_action_check = [ + 1, 15, 1, 15, 0, 13, 8, 11, 7, 1, + 1, 0, 0, 14, 6, 17, 20, 21, 23 ] + +racc_action_pointer = [ + 2, 0, nil, nil, nil, nil, 5, 5, 6, nil, + nil, 1, nil, -4, 8, -4, nil, 7, nil, nil, + 5, 8, nil, 9, nil, nil ] + +racc_action_default = [ + -15, -15, -1, -3, -4, -5, -15, -15, -15, -2, + -6, -15, 26, -15, -15, -15, -8, -15, -7, -9, + -13, -15, -14, -10, -11, -12 ] + +racc_goto_table = [ + 16, 19, 2, 9, 1, 15, 21, 23 ] -##### racc 1.4.5 generates ### +racc_goto_check = [ + 6, 6, 2, 2, 1, 5, 7, 8 ] + +racc_goto_pointer = [ + nil, 4, 2, nil, nil, -9, -14, -14, -14 ] + +racc_goto_default = [ + nil, nil, nil, 3, 5, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, @@ -598,35 +192,9 @@ racc_reduce_n = 15 racc_shift_n = 26 -racc_action_table = [ - 9, 15, 1, 20, 1, 14, 12, 13, 11, 6, - 7, 6, 7, 15, 18, 8, 21, 23, 25 ] - -racc_action_check = [ - 2, 16, 2, 16, 0, 12, 8, 9, 7, 2, - 2, 0, 0, 14, 15, 1, 18, 22, 24 ] - -racc_action_pointer = [ - 2, 12, 0, nil, nil, nil, nil, -1, 0, 7, - nil, nil, -4, nil, 8, 6, -4, nil, 5, nil, - nil, nil, 8, nil, 9, nil ] - -racc_action_default = [ - -15, -15, -15, -1, -3, -5, -4, -15, -15, -15, - -2, -6, -15, 26, -15, -15, -15, -8, -13, -9, - -7, -14, -15, -11, -10, -12 ] - -racc_goto_table = [ 17, 3, 19, 10, 2, 16, 22, 24 ] - -racc_goto_check = [ 6, 2, 6, 2, 1, 5, 7, 8 ] - -racc_goto_pointer = [ nil, 4, 1, nil, nil, -9, -14, -12, -15 ] - -racc_goto_default = [ nil, nil, nil, 4, 5, nil, nil, nil, nil ] - racc_token_table = { false => 0, - Object.new => 1, + :error => 1, :DEFINE => 2, :NAME => 3, :STRING => 4, @@ -638,10 +206,10 @@ racc_token_table = { :COMMENT => 10, :INLINECOMMENT => 11 } -racc_use_result_var = true - racc_nt_base = 12 +racc_use_result_var = true + Racc_arg = [ racc_action_table, racc_action_check, @@ -659,103 +227,103 @@ Racc_arg = [ racc_use_result_var ] Racc_token_to_s_table = [ -'$end', -'error', -'DEFINE', -'NAME', -'STRING', -'PARAM', -'LCURLY', -'RCURLY', -'VALUE', -'RETURN', -'COMMENT', -'INLINECOMMENT', -'$start', -'decls', -'decl', -'object', -'comment', -'vars', -'var', -'icomment', -'returns'] + "$end", + "error", + "DEFINE", + "NAME", + "STRING", + "PARAM", + "LCURLY", + "RCURLY", + "VALUE", + "RETURN", + "COMMENT", + "INLINECOMMENT", + "$start", + "decls", + "decl", + "object", + "comment", + "vars", + "var", + "icomment", + "returns" ] Racc_debug_parser = false -##### racc system variables end ##### +##### State transition tables end ##### # reduce 0 omitted -module_eval <<'.,.,', 'grammar.ry', 6 - def _reduce_1( val, _values, result ) -return val[0] if val[0] - result -end +module_eval(<<'.,.,', 'grammar.ry', 6) + def _reduce_1(val, _values, result) + return val[0] if val[0] + result + end .,., -module_eval <<'.,.,', 'grammar.ry', 18 - def _reduce_2( val, _values, result ) +module_eval(<<'.,.,', 'grammar.ry', 8) + def _reduce_2(val, _values, result) if val[1].nil? result = val[0] - else - if val[0].nil? - result = val[1] - else - result = [ val[0], val[1] ].flatten - end - end - result + else + if val[0].nil? + result = val[1] + else + result = [ val[0], val[1] ].flatten + end + end + result end .,., -module_eval <<'.,.,', 'grammar.ry', 20 - def _reduce_3( val, _values, result ) -result = [val[0]] - result -end +module_eval(<<'.,.,', 'grammar.ry', 20) + def _reduce_3(val, _values, result) + result = [val[0]] + result + end .,., -module_eval <<'.,.,', 'grammar.ry', 21 - def _reduce_4( val, _values, result ) -result = nil - result -end +module_eval(<<'.,.,', 'grammar.ry', 21) + def _reduce_4(val, _values, result) + result = nil + result + end .,., # reduce 5 omitted -module_eval <<'.,.,', 'grammar.ry', 25 - def _reduce_6( val, _values, result ) -result = nil - result -end +module_eval(<<'.,.,', 'grammar.ry', 25) + def _reduce_6(val, _values, result) + result = nil + result + end .,., -module_eval <<'.,.,', 'grammar.ry', 31 - def _reduce_7( val, _values, result ) +module_eval(<<'.,.,', 'grammar.ry', 29) + def _reduce_7(val, _values, result) result = Nagios::Base.create(val[1],val[4]) - result + result end .,., # reduce 8 omitted -module_eval <<'.,.,', 'grammar.ry', 40 - def _reduce_9( val, _values, result ) +module_eval(<<'.,.,', 'grammar.ry', 35) + def _reduce_9(val, _values, result) val[1].each {|p,v| val[0][p] = v - } - result = val[0] - result + } + result = val[0] + result end .,., -module_eval <<'.,.,', 'grammar.ry', 42 - def _reduce_10( val, _values, result ) -result = {val[0] => val[1]} - result -end +module_eval(<<'.,.,', 'grammar.ry', 42) + def _reduce_10(val, _values, result) + result = {val[0] => val[1]} + result + end .,., # reduce 11 omitted @@ -766,10 +334,9 @@ end # reduce 14 omitted -def _reduce_none( val, _values, result ) - result +def _reduce_none(val, _values, result) + val[0] end - end - -end + end # class Parser + end # module Nagios diff --git a/lib/puppet/external/pson/pure/parser.rb b/lib/puppet/external/pson/pure/parser.rb index 679e3fbfe..e8fbc98f4 100644 --- a/lib/puppet/external/pson/pure/parser.rb +++ b/lib/puppet/external/pson/pure/parser.rb @@ -66,7 +66,8 @@ module PSON # * *object_class*: Defaults to Hash # * *array_class*: Defaults to Array def initialize(source, opts = {}) - super + source = convert_encoding source + super source if !opts.key?(:max_nesting) # defaults to 19 @max_nesting = 19 elsif opts[:max_nesting] @@ -111,6 +112,51 @@ module PSON private + def convert_encoding(source) + if source.respond_to?(:to_str) + source = source.to_str + else + raise TypeError, "#{source.inspect} is not like a string" + end + if defined?(::Encoding) + if source.encoding == ::Encoding::ASCII_8BIT + b = source[0, 4].bytes.to_a + source = + case + when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0 + source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8) + when b.size >= 4 && b[0] == 0 && b[2] == 0 + source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8) + when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0 + source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8) + when b.size >= 4 && b[1] == 0 && b[3] == 0 + source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8) + else + source.dup + end + else + source = source.encode(::Encoding::UTF_8) + end + source.force_encoding(::Encoding::ASCII_8BIT) + else + b = source + source = + case + when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0 + PSON.encode('utf-8', 'utf-32be', b) + when b.size >= 4 && b[0] == 0 && b[2] == 0 + PSON.encode('utf-8', 'utf-16be', b) + when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0 + PSON.encode('utf-8', 'utf-32le', b) + when b.size >= 4 && b[1] == 0 && b[3] == 0 + PSON.encode('utf-8', 'utf-16le', b) + else + b + end + end + source + end + # Unescape characters in strings. UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr } diff --git a/lib/puppet/face/certificate.rb b/lib/puppet/face/certificate.rb index 80d758318..7c93e5062 100644 --- a/lib/puppet/face/certificate.rb +++ b/lib/puppet/face/certificate.rb @@ -60,15 +60,15 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do when_invoked do |name, options| host = Puppet::SSL::Host.new(name) - + # We have a weird case where we have --dns_alt_names from Puppet, but # this option is --dns-alt-names. Until we can get rid of --dns-alt-names # or do a global tr('-', '_'), we have to support both. - # In supporting both, we'll use Puppet[:dns_alt_names] if specified on + # In supporting both, we'll use Puppet[:dns_alt_names] if specified on # command line. We'll use options[:dns_alt_names] if specified on # command line. If both specified, we'll fail. # jeffweiss 17 april 2012 - + global_setting_from_cli = Puppet.settings.set_by_cli?(:dns_alt_names) == true raise ArgumentError, "Can't specify both --dns_alt_names and --dns-alt-names" if options[:dns_alt_names] and global_setting_from_cli options[:dns_alt_names] = Puppet[:dns_alt_names] if global_setting_from_cli diff --git a/lib/puppet/face/module.rb b/lib/puppet/face/module.rb index 523982152..111be0a8b 100644 --- a/lib/puppet/face/module.rb +++ b/lib/puppet/face/module.rb @@ -14,6 +14,6 @@ Puppet::Face.define(:module, '1.0.0') do a repository of user-contributed Puppet code. It can also generate empty modules, and prepare locally developed modules for release on the Forge. EOT - + display_global_options "environment", "modulepath" end diff --git a/lib/puppet/face/module/changes.rb b/lib/puppet/face/module/changes.rb index 19f632e62..fdf7991b8 100644 --- a/lib/puppet/face/module/changes.rb +++ b/lib/puppet/face/module/changes.rb @@ -21,7 +21,9 @@ Puppet::Face.define(:module, '1.0.0') do when_invoked do |path, options| Puppet::ModuleTool.set_option_defaults options - root_path = Puppet::ModuleTool.find_module_root(path) + unless root_path = Puppet::ModuleTool.find_module_root(path) + raise ArgumentError, "Could not find a valid module at #{path.inspect}" + end Puppet::ModuleTool::Applications::Checksummer.run(root_path, options) end diff --git a/lib/puppet/face/module/uninstall.rb b/lib/puppet/face/module/uninstall.rb index 23d0196dd..60db2c2b7 100644 --- a/lib/puppet/face/module/uninstall.rb +++ b/lib/puppet/face/module/uninstall.rb @@ -50,7 +50,7 @@ Puppet::Face.define(:module, '1.0.0') do when_invoked do |name, options| name = name.gsub('/', '-') - + Puppet::ModuleTool.set_option_defaults options Puppet.notice "Preparing to uninstall '#{name}'" << (options[:version] ? " (#{colorize(:cyan, options[:version].sub(/^(?=\d)/, 'v'))})" : '') << " ..." Puppet::ModuleTool::Applications::Uninstaller.run(name, options) diff --git a/lib/puppet/face/node/clean.rb b/lib/puppet/face/node/clean.rb index e320dc228..20d454e8a 100644 --- a/lib/puppet/face/node/clean.rb +++ b/lib/puppet/face/node/clean.rb @@ -9,17 +9,17 @@ Puppet::Face.define(:node, '0.0.1') do description <<-'EOT' Clean up everything a puppet master knows about a node, including certificates and storeconfigs data. - + The full list of info cleaned by this action is: <Signed certificates> - ($vardir/ssl/ca/signed/node.domain.pem) - + <Cached facts> - ($vardir/yaml/facts/node.domain.yaml) - + <Cached node objects> - ($vardir/yaml/node/node.domain.yaml) - + <Reports> - ($vardir/reports/node.domain) - + <Stored configs> - (in database) The clean action can either remove all data from a host in your storeconfigs database, or, with the <--unexport> option, turn every exported resource supporting ensure to diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index 86d3a46fa..dcf454298 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -10,7 +10,7 @@ Puppet.features.add(:syslog, :libs => ["syslog"]) # We can use POSIX user functions Puppet.features.add(:posix) do require 'etc' - Etc.getpwuid(0) != nil && Puppet.features.syslog? + !Etc.getpwuid(0).nil? && Puppet.features.syslog? end # We can use Microsoft Windows functions @@ -68,3 +68,5 @@ Puppet.features.add(:sqlite, :libs => ["sqlite3"]) # We have Hiera Puppet.features.add(:hiera, :libs => ["hiera"]) + +Puppet.features.add(:minitar, :libs => ["archive/tar/minitar"]) diff --git a/lib/puppet/feature/libuser.rb b/lib/puppet/feature/libuser.rb new file mode 100644 index 000000000..bf34e20da --- /dev/null +++ b/lib/puppet/feature/libuser.rb @@ -0,0 +1,8 @@ +require 'puppet/util/feature' +require 'puppet/util/libuser' + +Puppet.features.add(:libuser) { + File.executable?("/usr/sbin/lgroupadd") and + File.executable?("/usr/sbin/luseradd") and + File.exists?(Puppet::Util::Libuser.getconf) +} diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb index f2ebf5a9d..1360a7974 100644 --- a/lib/puppet/file_serving/fileset.rb +++ b/lib/puppet/file_serving/fileset.rb @@ -24,36 +24,6 @@ class Puppet::FileServing::Fileset result end - # Return a list of all files in our fileset. This is different from the - # normal definition of find in that we support specific levels - # of recursion, which means we need to know when we're going another - # level deep, which Find doesn't do. - def files - files = perform_recursion - - # Now strip off the leading path, so each file becomes relative, and remove - # any slashes that might end up at the beginning of the path. - result = files.collect { |file| file.sub(%r{^#{Regexp.escape(@path)}/*}, '') } - - # And add the path itself. - result.unshift(".") - - result - end - - # Should we ignore this path? - def ignore?(path) - return false if @ignore == [nil] - - # 'detect' normally returns the found result, whereas we just want true/false. - ! @ignore.detect { |pattern| File.fnmatch?(pattern, path) }.nil? - end - - def ignore=(values) - values = [values] unless values.is_a?(Array) - @ignore = values - end - def initialize(path, options = {}) if Puppet.features.microsoft_windows? # REMIND: UNC path @@ -66,8 +36,8 @@ class Puppet::FileServing::Fileset @path = path # Set our defaults. - @ignore = [] - @links = :manage + self.ignore = [] + self.links = :manage @recurse = false @recurselimit = :infinite @@ -77,23 +47,40 @@ class Puppet::FileServing::Fileset initialize_from_hash(options) end - raise ArgumentError.new("Fileset paths must exist") unless stat = stat(path) + raise ArgumentError.new("Fileset paths must exist") unless valid?(path) raise ArgumentError.new("Fileset recurse parameter must not be a number anymore, please use recurselimit") if @recurse.is_a?(Integer) end + # Return a list of all files in our fileset. This is different from the + # normal definition of find in that we support specific levels + # of recursion, which means we need to know when we're going another + # level deep, which Find doesn't do. + def files + files = perform_recursion + + # Now strip off the leading path, so each file becomes relative, and remove + # any slashes that might end up at the beginning of the path. + result = files.collect { |file| file.sub(%r{^#{Regexp.escape(@path)}/*}, '') } + + # And add the path itself. + result.unshift(".") + + result + end + + def ignore=(values) + values = [values] unless values.is_a?(Array) + @ignore = values + end + def links=(links) links = links.to_sym raise(ArgumentError, "Invalid :links value '#{links}'") unless [:manage, :follow].include?(links) @links = links - @stat_method = links == :manage ? :lstat : :stat + @stat_method = File.method(@links == :manage ? :lstat : :stat) end - # Should we recurse further? This is basically a single - # place for all of the logic around recursion. - def recurse?(depth) - # recurse if told to, and infinite recursion or current depth not at the limit - self.recurse and (self.recurselimit == :infinite or depth <= self.recurselimit) - end + private def initialize_from_hash(options) options.each do |option, value| @@ -121,53 +108,65 @@ class Puppet::FileServing::Fileset end end - private + FileSetEntry = Struct.new(:depth, :path, :ignored, :stat_method) do + def down_level(to) + FileSetEntry.new(depth + 1, File.join(path, to), ignored, stat_method) + end - # Pull the recursion logic into one place. It's moderately hairy, and this - # allows us to keep the hairiness apart from what we do with the files. - def perform_recursion - # Start out with just our base directory. - current_dirs = [@path] + def basename + File.basename(path) + end - next_dirs = [] + def children + return [] unless directory? - depth = 1 + Dir.entries(path). + reject { |child| ignore?(child) }. + collect { |child| down_level(child) } + end - result = [] - return result unless recurse?(depth) + def ignore?(child) + return true if child == "." || child == ".." + return false if ignored == [nil] - while dir_path = current_dirs.shift or ((depth += 1) and recurse?(depth) and current_dirs = next_dirs and next_dirs = [] and dir_path = current_dirs.shift) - next unless stat = stat(dir_path) - next unless stat.directory? + ignored.any? { |pattern| File.fnmatch?(pattern, child) } + end - Dir.entries(dir_path).each do |file_path| - next if [".", ".."].include?(file_path) + def directory? + stat_method.call(path).directory? + rescue Errno::ENOENT, Errno::EACCES + false + end + end - # Note that this also causes matching directories not - # to be recursed into. - next if ignore?(file_path) + # Pull the recursion logic into one place. It's moderately hairy, and this + # allows us to keep the hairiness apart from what we do with the files. + def perform_recursion + current_dirs = [FileSetEntry.new(0, @path, @ignore, @stat_method)] - # Add it to our list of files to return - result << File.join(dir_path, file_path) + result = [] - # And to our list of files/directories to iterate over. - next_dirs << File.join(dir_path, file_path) + while entry = current_dirs.shift + if continue_recursion_at?(entry.depth + 1) + entry.children.each do |child| + result << child.path + current_dirs << child + end end end result end - public - # Stat a given file, using the links-appropriate method. - def stat(path) - @stat_method ||= self.links == :manage ? :lstat : :stat - - begin - return File.send(@stat_method, path) - rescue - # If this happens, it is almost surely because we're - # trying to manage a link to a file that does not exist. - return nil - end + + def valid?(path) + @stat_method.call(path) + true + rescue Errno::ENOENT, Errno::EACCES + false + end + + def continue_recursion_at?(depth) + # recurse if told to, and infinite recursion or current depth not at the limit + self.recurse && (self.recurselimit == :infinite || depth <= self.recurselimit) end end diff --git a/lib/puppet/forge.rb b/lib/puppet/forge.rb index 59abb94c2..94281d008 100644 --- a/lib/puppet/forge.rb +++ b/lib/puppet/forge.rb @@ -7,6 +7,8 @@ require 'puppet/forge/repository' require 'puppet/forge/errors' class Puppet::Forge + include Puppet::Forge::Errors + # +consumer_name+ is a name to be used for identifying the consumer of the # forge and +consumer_semver+ is a SemVer object to identify the version of # the consumer @@ -34,6 +36,14 @@ class Puppet::Forge # } # ] # + # @param term [String] search term + # @return [Array] modules found + # @raise [Puppet::Forge::Errors::CommunicationError] if there is a network + # related error + # @raise [Puppet::Forge::Errors::SSLVerifyError] if there is a problem + # verifying the remote SSL certificate + # @raise [Puppet::Forge::Errors::ResponseError] if the repository returns a + # bad HTTP response def search(term) server = Puppet.settings[:module_repository] Puppet.notice "Searching #{server} ..." @@ -43,12 +53,25 @@ class Puppet::Forge when "200" matches = PSON.parse(response.body) else - raise RuntimeError, "Could not execute search (HTTP #{response.code})" + raise ResponseError.new(:uri => uri.to_s, :input => term, :response => response) end matches end + # Return a list of module metadata hashes for the module requested and all + # of its dependencies. + # + # @param author [String] module's author name + # @param mod_name [String] module name + # @param version [String] optional module version number + # @return [Array] module and dependency metadata + # @raise [Puppet::Forge::Errors::CommunicationError] if there is a network + # related error + # @raise [Puppet::Forge::Errors::SSLVerifyError] if there is a problem + # verifying the remote SSL certificate + # @raise [Puppet::Forge::Errors::ResponseError] if the repository returns + # an error in its API response or a bad HTTP response def remote_dependency_info(author, mod_name, version) version_string = version ? "&version=#{version}" : '' response = repository.make_http_request("/api/v1/releases.json?module=#{author}/#{mod_name}#{version_string}") @@ -57,11 +80,11 @@ class Puppet::Forge when "200" return json else - error = json['error'] || '' - if error =~ /^Module #{author}\/#{mod_name} has no release/ + error = json['error'] + if error && error =~ /^Module #{author}\/#{mod_name} has no release/ return [] else - raise RuntimeError, "Could not find release information for this module (#{author}/#{mod_name}) (HTTP #{response.code})" + raise ResponseError.new(:uri => uri.to_s, :input => "#{author}/#{mod_name}", :message => error, :response => response) end end end @@ -74,7 +97,7 @@ class Puppet::Forge begin cache_path = repository.retrieve(file) rescue OpenURI::HTTPError => e - raise RuntimeError, "Could not download module: #{e.message}" + raise HttpResponseError.new(:uri => uri.to_s, :input => modname, :message => e.message) end else raise RuntimeError, "Malformed response from module repository." diff --git a/lib/puppet/forge/errors.rb b/lib/puppet/forge/errors.rb index 373d8ce06..2c7f40f13 100644 --- a/lib/puppet/forge/errors.rb +++ b/lib/puppet/forge/errors.rb @@ -66,4 +66,38 @@ Could not connect to #{@uri} end end + # This exception is raised when there is a bad HTTP response from the forge + # and optionally a message in the response. + class ResponseError < ForgeError + # @option options [String] :uri The URI that failed + # @option options [String] :input The user's input (e.g. module name) + # @option options [String] :message Error from the API response (optional) + # @option options [Net::HTTPResponse] :response The original HTTP response + def initialize(options) + @uri = options[:uri] + @input = options[:input] + @message = options[:message] + response = options[:response] + @response = "#{response.code} #{response.message}" + + message = "Could not execute operation for '#{@input}'. Detail: " + message << @message << " / " if @message + message << @response << "." + super(message, original) + end + + # Return a multiline version of the error message + # + # @return [String] the multiline version of the error message + def multiline + message = <<-EOS +Could not execute operation for '#{@input}' + The server being queried was #{@uri} + The HTTP response we received was '#{@response}' + EOS + message << " The message we received said '#{@message}'\n" if @message + message << " Check the author and module names are correct." + end + end + end diff --git a/lib/puppet/forge/repository.rb b/lib/puppet/forge/repository.rb index 7cdd12754..7badc4c28 100644 --- a/lib/puppet/forge/repository.rb +++ b/lib/puppet/forge/repository.rb @@ -75,7 +75,7 @@ class Puppet::Forge # Return a Net::HTTPResponse read for this +request_path+. def make_http_request(request_path) - request = Net::HTTP::Get.new(request_path, { "User-Agent" => user_agent }) + request = Net::HTTP::Get.new(URI.escape(request_path), { "User-Agent" => user_agent }) if ! @uri.user.nil? && ! @uri.password.nil? request.basic_auth(@uri.user, @uri.password) end diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 6de3d7221..b20550a43 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -40,9 +40,12 @@ module Puppet::Indirector include Puppet::Indirector::Envelope extend Puppet::Network::FormatHandler + # record the indirected class name for documentation purposes + options[:indirected_class] = name + # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) # & hook the instantiated Terminus into this class (Node: @indirection = terminus) - @indirection = Puppet::Indirector::Indirection.new(self, indirection, options) + @indirection = Puppet::Indirector::Indirection.new(self, indirection, options) end module ClassMethods diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb index 6fd324291..95d96d642 100644 --- a/lib/puppet/indirector/catalog/compiler.rb +++ b/lib/puppet/indirector/catalog/compiler.rb @@ -1,6 +1,7 @@ require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/indirector/code' +require 'puppet/util/profiler' require 'yaml' class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code @@ -16,20 +17,22 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code raise ArgumentError, "Facts but no fact format provided for #{request.key}" end - # If the facts were encoded as yaml, then the param reconstitution system - # in Network::HTTP::Handler will automagically deserialize the value. - if text_facts.is_a?(Puppet::Node::Facts) - facts = text_facts - else - facts = Puppet::Node::Facts.convert_from(format, text_facts) - end + Puppet::Util::Profiler.profile("Found facts") do + # If the facts were encoded as yaml, then the param reconstitution system + # in Network::HTTP::Handler will automagically deserialize the value. + if text_facts.is_a?(Puppet::Node::Facts) + facts = text_facts + else + facts = Puppet::Node::Facts.convert_from(format, text_facts) + end - unless facts.name == request.key - raise Puppet::Error, "Catalog for #{request.key.inspect} was requested with fact definition for the wrong node (#{facts.name.inspect})." - end + unless facts.name == request.key + raise Puppet::Error, "Catalog for #{request.key.inspect} was requested with fact definition for the wrong node (#{facts.name.inspect})." + end - facts.add_timestamp - Puppet::Node::Facts.indirection.save(facts) + facts.add_timestamp + Puppet::Node::Facts.indirection.save(facts) + end end # Compile a node's catalog. @@ -54,7 +57,9 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code end def initialize - set_server_facts + Puppet::Util::Profiler.profile("Setup server facts for compiling") do + set_server_facts + end end # Is our compiler part of a network, or are we just local? @@ -76,9 +81,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code str += " in environment #{node.environment}" if node.environment config = nil - loglevel = networked? ? :notice : :none - - benchmark(loglevel, str) do + Puppet::Util::Profiler.profile(str) do begin config = Puppet::Parser::Compiler.compile(node) rescue Puppet::Error => detail @@ -91,20 +94,24 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code end # Turn our host name into a node object. - def find_node(name, *args) - begin - return nil unless node = Puppet::Node.indirection.find(name, *args) - rescue => detail - message = "Failed when searching for node #{name}: #{detail}" - Puppet.log_exception(detail, message) - raise Puppet::Error, message - end - + def find_node(name, environment) + Puppet::Util::Profiler.profile("Found node information") do + node = nil + begin + node = Puppet::Node.indirection.find(name, :environment => environment) + rescue => detail + message = "Failed when searching for node #{name}: #{detail}" + Puppet.log_exception(detail, message) + raise Puppet::Error, message + end - # Add any external data to the node. - add_node_data(node) - node + # Add any external data to the node. + if node + add_node_data(node) + end + node + end end # Extract the node from the request, or use the request @@ -123,10 +130,10 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code # By default the REST authorization system makes sure only the connected node # can compile his catalog. # This allows for instance monitoring systems or puppet-load to check several - # node's catalog with only one certificate and a modification to auth.conf + # node's catalog with only one certificate and a modification to auth.conf # If no key is provided we can only compile the currently connected node. name = request.key || request.node - if node = find_node(name, :environment => request.environment) + if node = find_node(name, request.environment) return node end diff --git a/lib/puppet/indirector/catalog/static_compiler.rb b/lib/puppet/indirector/catalog/static_compiler.rb index 86bf0eddd..6e688bc4b 100644 --- a/lib/puppet/indirector/catalog/static_compiler.rb +++ b/lib/puppet/indirector/catalog/static_compiler.rb @@ -15,23 +15,25 @@ class Puppet::Resource::Catalog::StaticCompiler < Puppet::Indirector::Code This terminus works today, but cannot be used without additional configuration. Specifically: - You must create a `Filebucket['puppet']` resource in site.pp (or somewhere - else where it will be added to every node's catalog). The title of this - resource **MUST** be "puppet"; the static compiler treats this title as magical. - - # Note: the special $servername var always contains the master's FQDN, - # even if it was reached at a different name. - filebucket { puppet: - server => $servername, - path => false, - } - - You must set `catalog_terminus = static_compiler` in the puppet master's puppet.conf. - - If you are using multiple puppet masters, you must configure load balancer - affinity for agent nodes. This is because puppet masters other than the one - that compiled a given catalog may not have stored the required file contents - in their filebuckets.} + * You must create a special filebucket resource --- with the title `puppet` + and the `path` attribute set to `false` --- in site.pp or somewhere else + where it will be added to every node's catalog. Using `puppet` as the title + is mandatory; the static compiler treats this title as magical. + + filebucket { puppet: + path => false, + } + + * You must set `catalog_terminus = static_compiler` in the puppet + master's puppet.conf. + * The puppet master's auth.conf must allow authenticated nodes to access the + `file_bucket_file` endpoint. This is enabled by default (see the + `path /file` rule), but if you have made your auth.conf more restrictive, + you may need to re-enable it.) + * If you are using multiple puppet masters, you must configure load balancer + affinity for agent nodes. This is because puppet masters other than the one + that compiled a given catalog may not have stored the required file contents + in their filebuckets.} def compiler @compiler ||= indirection.terminus(:compiler) @@ -113,7 +115,7 @@ class Puppet::Resource::Catalog::StaticCompiler < Puppet::Indirector::Code result.each { |data| data.source = "#{source}/#{data.relative_path}" } break result if result and ! result.empty? and sourceselect == :first result - end.flatten + end.flatten.compact # This only happens if we have sourceselect == :all unless sourceselect == :first diff --git a/lib/puppet/indirector/facts/inventory_active_record.rb b/lib/puppet/indirector/facts/inventory_active_record.rb index 4add7f2be..b6c703bb5 100644 --- a/lib/puppet/indirector/facts/inventory_active_record.rb +++ b/lib/puppet/indirector/facts/inventory_active_record.rb @@ -11,6 +11,7 @@ class Puppet::Node::Facts::InventoryActiveRecord < Puppet::Indirector::ActiveRec and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" def initialize + raise Puppet::Error, "ActiveRecords-based inventory is unsupported with Ruby 2 and Rails 3.0" if RUBY_VERSION[0] == '2' Puppet.deprecation_warning "ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" super end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 720a7dfd3..6e128eaac 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -1,4 +1,5 @@ require 'puppet/util/docs' +require 'puppet/util/profiler' require 'puppet/util/methodhelper' require 'puppet/indirector/envelope' require 'puppet/indirector/request' @@ -86,8 +87,9 @@ class Puppet::Indirector::Indirection text += scrub(@doc) + "\n\n" if @doc + text << "* **Indirected Class**: `#{@indirected_class}`\n"; if s = terminus_setting - text += "* **Terminus Setting**: #{terminus_setting}" + text << "* **Terminus Setting**: #{terminus_setting}\n" end text @@ -104,6 +106,7 @@ class Puppet::Indirector::Indirection raise(ArgumentError, "Indirection #{@name} is already defined") if @@indirections.find { |i| i.name == @name } @@indirections << self + @indirected_class = options.delete(:indirected_class) if mod = options[:extend] extend(mod) options.delete(:extend) @@ -183,22 +186,32 @@ class Puppet::Indirector::Indirection request = request(:find, key, nil, options) terminus = prepare(request) - if result = find_in_cache(request) - return result - end - - # Otherwise, return the result from the terminus, caching if appropriate. - if ! request.ignore_terminus? and result = terminus.find(request) - result.expiration ||= self.expiration if result.respond_to?(:expiration) - if cache? and request.use_cache? - Puppet.info "Caching #{self.name} for #{request.key}" - cache.save request(:save, key, result, options) + result = find_in_cache(request) + if not result.nil? + result + elsif request.ignore_terminus? + nil + else + # Otherwise, return the result from the terminus, caching if + # appropriate. + result = terminus.find(request) + if not result.nil? + result.expiration ||= self.expiration if result.respond_to?(:expiration) + if cache? and request.use_cache? + Puppet.info "Caching #{self.name} for #{request.key}" + cache.save request(:save, key, result, options) + end + + filtered = result + if terminus.respond_to?(:filter) + Puppet::Util::Profiler.profile("Filtered result for #{self.name} #{request.key}") do + filtered = terminus.filter(result) + end + end + + filtered end - - return terminus.respond_to?(:filter) ? terminus.filter(result) : result end - - nil end # Search for an instance in the appropriate terminus, and return a diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index 59c025af1..6446008e5 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -1,6 +1,7 @@ require 'net/http' require 'uri' +require 'puppet/network/http' require 'puppet/network/http_pool' require 'puppet/network/http/api/v1' require 'puppet/network/http/compression' @@ -76,28 +77,35 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus add_accept_encoding({"Accept" => model.supported_formats.join(", ")}) end + def add_profiling_header(headers) + if (Puppet[:profile]) + headers[Puppet::Network::HTTP::HEADER_ENABLE_PROFILING] = "true" + end + headers + end + def network(request) Puppet::Network::HTTP::Connection.new(request.server || self.class.server, request.port || self.class.port) end - def http_get(request, *args) - http_request(:get, request, *args) + def http_get(request, path, headers = nil, *args) + http_request(:get, request, path, add_profiling_header(headers), *args) end - def http_post(request, *args) - http_request(:post, request, *args) + def http_post(request, path, data, headers = nil, *args) + http_request(:post, request, path, data, add_profiling_header(headers), *args) end - def http_head(request, *args) - http_request(:head, request, *args) + def http_head(request, path, headers = nil, *args) + http_request(:head, request, path, add_profiling_header(headers), *args) end - def http_delete(request, *args) - http_request(:delete, request, *args) + def http_delete(request, path, headers = nil, *args) + http_request(:delete, request, path, add_profiling_header(headers), *args) end - def http_put(request, *args) - http_request(:put, request, *args) + def http_put(request, path, data, headers = nil, *args) + http_request(:put, request, path, data, add_profiling_header(headers), *args) end def http_request(method, request, *args) diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index ec77b09f7..9ee1c800b 100644 --- a/lib/puppet/interface.rb +++ b/lib/puppet/interface.rb @@ -1,20 +1,27 @@ require 'puppet' require 'puppet/util/autoload' -require 'puppet/interface/documentation' require 'prettyprint' require 'semver' # @api public class Puppet::Interface - include FullDocs - + require 'puppet/interface/documentation' require 'puppet/interface/face_collection' + require 'puppet/interface/action' + require 'puppet/interface/action_builder' require 'puppet/interface/action_manager' + + require 'puppet/interface/option' + require 'puppet/interface/option_builder' + require 'puppet/interface/option_manager' + + + include FullDocs + include Puppet::Interface::ActionManager extend Puppet::Interface::ActionManager - require 'puppet/interface/option_manager' include Puppet::Interface::OptionManager extend Puppet::Interface::OptionManager diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb index 344de2a3b..01cd8456f 100644 --- a/lib/puppet/interface/action.rb +++ b/lib/puppet/interface/action.rb @@ -1,5 +1,3 @@ -require 'puppet/interface' -require 'puppet/interface/documentation' require 'puppet/util/methodhelper' require 'prettyprint' @@ -295,7 +293,7 @@ WRAPPER end def display_global_options(*args) - args ? add_display_global_options(args) : @display_global_options + @face.display_global_options + args ? add_display_global_options(args) : @display_global_options + @face.display_global_options end alias :display_global_option :display_global_options diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb index b667887bd..da1bdd0fe 100644 --- a/lib/puppet/interface/action_builder.rb +++ b/lib/puppet/interface/action_builder.rb @@ -1,6 +1,3 @@ -require 'puppet/interface' -require 'puppet/interface/action' - # This class is used to build {Puppet::Interface::Action actions}. # When an action is defined with # {Puppet::Interface::ActionManager#action} the block is evaluated diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb index 2be33516b..87906c577 100644 --- a/lib/puppet/interface/action_manager.rb +++ b/lib/puppet/interface/action_manager.rb @@ -1,6 +1,3 @@ -require 'puppet/interface/action' -require 'puppet/interface/action_builder' - # This class is not actually public API, but the method # {Puppet::Interface::ActionManager#action action} is public when used # as part of the Faces DSL (i.e. from within a diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb index 11056588c..0c95f50b3 100644 --- a/lib/puppet/interface/face_collection.rb +++ b/lib/puppet/interface/face_collection.rb @@ -1,5 +1,3 @@ -require 'puppet/interface' - module Puppet::Interface::FaceCollection @faces = Hash.new { |hash, key| hash[key] = {} } diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb index d646dde14..288b016f9 100644 --- a/lib/puppet/interface/option.rb +++ b/lib/puppet/interface/option.rb @@ -1,5 +1,3 @@ -require 'puppet/interface' - # This represents an option on an action or face (to be globally applied # to its actions). Options should be constructed by calling # {Puppet::Interface::OptionManager#option}, which is available on diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb index 4c7c6328b..f4d7c6cf0 100644 --- a/lib/puppet/interface/option_builder.rb +++ b/lib/puppet/interface/option_builder.rb @@ -1,5 +1,3 @@ -require 'puppet/interface/option' - # @api public class Puppet::Interface::OptionBuilder # The option under construction diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb index 224eb1dba..b68e38bac 100644 --- a/lib/puppet/interface/option_manager.rb +++ b/lib/puppet/interface/option_manager.rb @@ -1,5 +1,3 @@ -require 'puppet/interface/option_builder' - # This class is not actually public API, but the method # {Puppet::Interface::OptionManager#option option} is public when used # as part of the Faces DSL (i.e. from within a diff --git a/lib/puppet/metatype/manager.rb b/lib/puppet/metatype/manager.rb index 727b89034..af0187368 100644 --- a/lib/puppet/metatype/manager.rb +++ b/lib/puppet/metatype/manager.rb @@ -34,7 +34,7 @@ module Manager end end - # Loads all types. + # Loads all types. # @note Should only be used for purposes such as generating documentation as this is potentially a very # expensive operation. # @return [void] diff --git a/lib/puppet/module_tool.rb b/lib/puppet/module_tool.rb index 4ff4207ec..f19b4395b 100644 --- a/lib/puppet/module_tool.rb +++ b/lib/puppet/module_tool.rb @@ -6,6 +6,7 @@ require 'puppet/util/colors' module Puppet module ModuleTool + require 'puppet/module_tool/tar' extend Puppet::Util::Colors # Directory and names that should not be checksummed. diff --git a/lib/puppet/module_tool/applications/application.rb b/lib/puppet/module_tool/applications/application.rb index feb0777fd..88ee99aa3 100644 --- a/lib/puppet/module_tool/applications/application.rb +++ b/lib/puppet/module_tool/applications/application.rb @@ -14,9 +14,6 @@ module Puppet::ModuleTool attr_accessor :options def initialize(options = {}) - if Puppet.features.microsoft_windows? - raise Puppet::Error, "`puppet module` actions are currently not supported on Microsoft Windows" - end @options = options end diff --git a/lib/puppet/module_tool/applications/builder.rb b/lib/puppet/module_tool/applications/builder.rb index a0fa4304a..d454a7bd4 100644 --- a/lib/puppet/module_tool/applications/builder.rb +++ b/lib/puppet/module_tool/applications/builder.rb @@ -16,9 +16,8 @@ module Puppet::ModuleTool copy_contents add_metadata Puppet.notice "Building #{@path} for release" - tar - gzip - relative = Pathname.new(File.join(@pkg_path, filename('tar.gz'))).relative_path_from(Pathname.new(Dir.pwd)) + pack + relative = Pathname.new(archive_file).relative_path_from(Pathname.new(File.expand_path(Dir.pwd))) # Return the Pathname object representing the path to the release # archive just created. This return value is used by the module_tool @@ -34,27 +33,16 @@ module Puppet::ModuleTool private - def filename(ext) - ext.sub!(/^\./, '') - "#{metadata.release_name}.#{ext}" + def archive_file + File.join(@pkg_path, "#{metadata.release_name}.tar.gz") end - def tar - tar_name = filename('tar') - Dir.chdir(@pkg_path) do - FileUtils.rm tar_name rescue nil - unless system "tar -cf #{tar_name} #{metadata.release_name}" - raise RuntimeError, "Could not create #{tar_name}" - end - end - end + def pack + FileUtils.rm archive_file rescue nil - def gzip + tar = Puppet::ModuleTool::Tar.instance(metadata.full_module_name) Dir.chdir(@pkg_path) do - FileUtils.rm filename('tar.gz') rescue nil - unless system "gzip #{filename('tar')}" - raise RuntimeError, "Could not compress #{filename('tar')}" - end + tar.pack(metadata.release_name, archive_file) end end diff --git a/lib/puppet/module_tool/applications/checksummer.rb b/lib/puppet/module_tool/applications/checksummer.rb index 9f6814731..814b7e220 100644 --- a/lib/puppet/module_tool/applications/checksummer.rb +++ b/lib/puppet/module_tool/applications/checksummer.rb @@ -22,7 +22,7 @@ module Puppet::ModuleTool next if File.basename(child_path) == "metadata.json" path = @path + child_path - if canonical_checksum != sums.checksum(path) + unless path.exist? && canonical_checksum == sums.checksum(path) changes << child_path end end diff --git a/lib/puppet/module_tool/applications/installer.rb b/lib/puppet/module_tool/applications/installer.rb index 0e2c60c22..08f736a0a 100644 --- a/lib/puppet/module_tool/applications/installer.rb +++ b/lib/puppet/module_tool/applications/installer.rb @@ -27,6 +27,7 @@ module Puppet::ModuleTool end def run + results = {} begin if is_module_package?(@name) @source = :filesystem diff --git a/lib/puppet/module_tool/applications/unpacker.rb b/lib/puppet/module_tool/applications/unpacker.rb index 5ef6e3384..0a92ae947 100644 --- a/lib/puppet/module_tool/applications/unpacker.rb +++ b/lib/puppet/module_tool/applications/unpacker.rb @@ -8,6 +8,7 @@ module Puppet::ModuleTool def initialize(filename, options = {}) @filename = Pathname.new(filename) parsed = parse_filename(filename) + @module_name = parsed[:module_name] super(options) @module_dir = Pathname.new(options[:target_dir]) + parsed[:dir_name] end @@ -29,23 +30,14 @@ module Puppet::ModuleTool end private + def extract_module_to_install_dir delete_existing_installation_or_abort! build_dir.mkpath begin begin - if Facter.value('osfamily') == "Solaris" - # Solaris tar is not as safe and works differently, so we prefer - # gnutar instead. - if Puppet::Util.which('gtar') - Puppet::Util::Execution.execute("gtar xzf #{@filename} -C #{build_dir}") - else - raise RuntimeError, "Cannot find the command 'gtar'. Make sure GNU tar is installed, and is in your PATH." - end - else - Puppet::Util::Execution.execute("tar xzf #{@filename} -C #{build_dir}") - end + Puppet::ModuleTool::Tar.instance(@module_name).unpack(@filename.to_s, build_dir.to_s) rescue Puppet::ExecutionFailure => e raise RuntimeError, "Could not extract contents of module archive: #{e.message}" end diff --git a/lib/puppet/module_tool/checksums.rb b/lib/puppet/module_tool/checksums.rb index f2c6971b2..0985b71e4 100644 --- a/lib/puppet/module_tool/checksums.rb +++ b/lib/puppet/module_tool/checksums.rb @@ -16,7 +16,7 @@ module Puppet::ModuleTool # Return checksum for the +Pathname+. def checksum(pathname) - return Digest::MD5.hexdigest(pathname.read) + return Digest::MD5.hexdigest(IO.binread(pathname)) end # Return checksums for object's +Pathname+, generate if it's needed. diff --git a/lib/puppet/module_tool/errors/installer.rb b/lib/puppet/module_tool/errors/installer.rb index 332679a44..a2fbb0c30 100644 --- a/lib/puppet/module_tool/errors/installer.rb +++ b/lib/puppet/module_tool/errors/installer.rb @@ -46,7 +46,7 @@ module Puppet::ModuleTool::Errors message << " Currently, '#{@metadata[:name]}' (#{v(@metadata[:version])}) is installed to that directory" end - message << " Use `puppet module install --dir <DIR>` to install modules elsewhere" + message << " Use `puppet module install --target-dir <DIR>` to install modules elsewhere" if @dependency message << " Use `puppet module install --ignore-dependencies` to install only this module" @@ -107,4 +107,21 @@ Could not install module '#{@requested_module}' (#{@requested_version}) MSG end end + + class InvalidPathInPackageError < InstallError + def initialize(options) + @requested_package = options[:requested_package] + @entry_path = options[:entry_path] + @directory = options[:directory] + super "Attempt to install file into #{@entry_path.inspect} under #{@directory.inspect}" + end + + def multiline + <<-MSG.strip +Could not install package #{@requested_package} + Package #{@requested_package} attempted to install file into + #{@entry_path.inspect} under #{@directory.inspect}. + MSG + end + end end diff --git a/lib/puppet/module_tool/modulefile.rb b/lib/puppet/module_tool/modulefile.rb index 4b50f1160..df68f3246 100644 --- a/lib/puppet/module_tool/modulefile.rb +++ b/lib/puppet/module_tool/modulefile.rb @@ -30,13 +30,13 @@ module Puppet::ModuleTool @metadata.full_module_name = name end - # Set the module +version+ (e.g., "0.0.1"). Required. + # Set the module +version+ (e.g., "0.1.0"). Required. def version(version) @metadata.version = version end # Add a dependency with the full_module_name +name+ (e.g. "myuser-mymodule"), an - # optional +version_requirement+ (e.g. "0.0.1") and +repository+ (a URL + # optional +version_requirement+ (e.g. "0.1.0") and +repository+ (a URL # string). Optional. Can be called multiple times to add many dependencies. def dependency(name, version_requirement = nil, repository = nil) @metadata.dependencies << Dependency.new(name, version_requirement, repository) diff --git a/lib/puppet/module_tool/skeleton/templates/generator/Modulefile.erb b/lib/puppet/module_tool/skeleton/templates/generator/Modulefile.erb index 845102d6e..506cef49c 100644 --- a/lib/puppet/module_tool/skeleton/templates/generator/Modulefile.erb +++ b/lib/puppet/module_tool/skeleton/templates/generator/Modulefile.erb @@ -1,5 +1,5 @@ name '<%= metadata.full_module_name %>' -version '0.0.1' +version '0.1.0' source '<%= metadata.source %>' author '<%= metadata.author %>' license '<%= metadata.license %>' diff --git a/lib/puppet/module_tool/tar.rb b/lib/puppet/module_tool/tar.rb new file mode 100644 index 000000000..4f9f87ed2 --- /dev/null +++ b/lib/puppet/module_tool/tar.rb @@ -0,0 +1,17 @@ +module Puppet::ModuleTool::Tar + require 'puppet/module_tool/tar/gnu' + require 'puppet/module_tool/tar/solaris' + require 'puppet/module_tool/tar/mini' + + def self.instance(module_name) + if Facter.value('osfamily') == 'Solaris' && Puppet::Util.which('gtar') && ! Puppet::Util::Platform.windows? + Solaris.new + elsif Puppet::Util.which('tar') && ! Puppet::Util::Platform.windows? + Gnu.new + elsif Puppet.features.minitar? && Puppet.features.zlib? + Mini.new(module_name) + else + raise RuntimeError, 'No suitable tar implementation found' + end + end +end diff --git a/lib/puppet/module_tool/tar/gnu.rb b/lib/puppet/module_tool/tar/gnu.rb new file mode 100644 index 000000000..e9823e55f --- /dev/null +++ b/lib/puppet/module_tool/tar/gnu.rb @@ -0,0 +1,9 @@ +class Puppet::ModuleTool::Tar::Gnu + def unpack(sourcefile, destdir) + Puppet::Util::Execution.execute("tar xzf #{sourcefile} -C #{destdir}") + end + + def pack(sourcedir, destfile) + Puppet::Util::Execution.execute("tar cf - #{sourcedir} | gzip -c > #{destfile}") + end +end diff --git a/lib/puppet/module_tool/tar/mini.rb b/lib/puppet/module_tool/tar/mini.rb new file mode 100644 index 000000000..34f905ff5 --- /dev/null +++ b/lib/puppet/module_tool/tar/mini.rb @@ -0,0 +1,39 @@ +class Puppet::ModuleTool::Tar::Mini + def initialize(module_name) + @module_name = module_name + end + + def unpack(sourcefile, destdir) + Zlib::GzipReader.open(sourcefile) do |reader| + Archive::Tar::Minitar.unpack(reader, destdir) do |action, name, stats| + case action + when :dir, :file_start + validate_entry(destdir, name) + Puppet.debug("extracting #{destdir}/#{name}") + end + end + end + end + + def pack(sourcedir, destfile) + Zlib::GzipWriter.open(destfile) do |writer| + Archive::Tar::Minitar.pack(sourcedir, writer) + end + end + + private + + def validate_entry(destdir, path) + if Pathname.new(path).absolute? + raise Puppet::ModuleTool::Errors::InvalidPathInPackageError, + :requested_package => @module_name, :entry_path => path, :directory => destdir + end + + path = File.expand_path File.join(destdir, path) + + if path !~ /\A#{Regexp.escape destdir}/ + raise Puppet::ModuleTool::Errors::InvalidPathInPackageError, + :requested_package => @module_name, :entry_path => path, :directory => destdir + end + end +end diff --git a/lib/puppet/module_tool/tar/solaris.rb b/lib/puppet/module_tool/tar/solaris.rb new file mode 100644 index 000000000..81de63e95 --- /dev/null +++ b/lib/puppet/module_tool/tar/solaris.rb @@ -0,0 +1,5 @@ +class Puppet::ModuleTool::Tar::Solaris < Puppet::ModuleTool::Tar::Gnu + def unpack(sourcefile, destdir) + Puppet::Util::Execution.execute("gtar xzf #{sourcefile} -C #{destdir}") + end +end diff --git a/lib/puppet/network/http.rb b/lib/puppet/network/http.rb index 6fd041ee3..93f23eb8a 100644 --- a/lib/puppet/network/http.rb +++ b/lib/puppet/network/http.rb @@ -1,2 +1,3 @@ module Puppet::Network::HTTP + HEADER_ENABLE_PROFILING = "X-Puppet-Profiling" end diff --git a/lib/puppet/network/http/connection.rb b/lib/puppet/network/http/connection.rb index 54979ec09..aab13b05c 100644 --- a/lib/puppet/network/http/connection.rb +++ b/lib/puppet/network/http/connection.rb @@ -1,6 +1,7 @@ require 'net/https' require 'puppet/ssl/host' require 'puppet/ssl/configuration' +require 'puppet/ssl/validator' require 'puppet/network/authentication' module Puppet::Network::HTTP @@ -44,38 +45,23 @@ module Puppet::Network::HTTP end def request(method, *args) - peer_certs = [] - verify_errors = [] - - connection.verify_callback = proc do |preverify_ok, ssl_context| - # We use the callback to collect the certificates for use in - # constructing the error message if the verification failed. - # This is necessary since we don't have direct access to the - # cert that we expected the connection to use otherwise. - peer_certs << Puppet::SSL::Certificate.from_instance(ssl_context.current_cert) - # And also keep the detailed verification error if such an error occurs - if ssl_context.error_string and not preverify_ok - verify_errors << "#{ssl_context.error_string} for #{ssl_context.current_cert.subject}" - end - preverify_ok - end - + ssl_validator = Puppet::SSL::Validator.new(:ssl_configuration => ssl_configuration) + # Perform our own validation of the SSL connection in addition to OpenSSL + ssl_validator.register_verify_callback(connection) response = connection.send(method, *args) - - # Now that the request completed successfully, lets check the involved - # certificates for approaching expiration dates - warn_if_near_expiration(*peer_certs) + # Check the peer certs and warn if they're nearing expiration. + warn_if_near_expiration(*ssl_validator.peer_certs) response rescue OpenSSL::SSL::SSLError => error if error.message.include? "certificate verify failed" msg = error.message - msg << ": [" + verify_errors.join('; ') + "]" + msg << ": [" + ssl_validator.verify_errors.join('; ') + "]" raise Puppet::Error, msg - elsif error.message =~ /hostname (was )?not match/ - raise unless cert = peer_certs.find { |c| c.name !~ /^puppet ca/i } + elsif error.message =~ /hostname (\w+ )?not match/ + leaf_ssl_cert = ssl_validator.peer_certs.last - valid_certnames = [cert.name, *cert.subject_alt_names].uniq + valid_certnames = [leaf_ssl_cert.name, *leaf_ssl_cert.subject_alt_names].uniq msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first raise Puppet::Error, "Server hostname '#{connection.address}' did not match server certificate; expected #{msg}" diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 14243ced7..ee07639b2 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -1,10 +1,12 @@ module Puppet::Network::HTTP end +require 'puppet/network/http' require 'puppet/network/http/api/v1' require 'puppet/network/authorization' require 'puppet/network/authentication' require 'puppet/network/rights' +require 'puppet/util/profiler' require 'resolv' module Puppet::Network::HTTP::Handler @@ -14,6 +16,13 @@ module Puppet::Network::HTTP::Handler attr_reader :server, :handler + + # Retrieve all headers from the http request, as a hash with the header names + # (lower-cased) as the keys + def headers(request) + raise NotImplementedError + end + # Retrieve the accept header from the http request. def accept_header(request) raise NotImplementedError @@ -63,12 +72,21 @@ module Puppet::Network::HTTP::Handler # handle an HTTP request def process(request, response) - indirection, method, key, params = uri2indirection(http_method(request), path(request), params(request)) + request_headers = headers(request) + request_params = params(request) + request_method = http_method(request) + request_path = path(request) - check_authorization(indirection, method, key, params) - warn_if_near_expiration(client_cert(request)) + configure_profiler(request_headers, request_params) - send("do_#{method}", indirection, key, params, request, response) + Puppet::Util::Profiler.profile("Processed request #{request_method} #{request_path}") do + indirection, method, key, params = uri2indirection(request_method, request_path, request_params) + + check_authorization(indirection, method, key, params) + warn_if_near_expiration(client_cert(request)) + + send("do_#{method}", indirection, key, params, request, response) + end rescue SystemExit,NoMemoryError raise rescue Exception => e @@ -118,10 +136,15 @@ module Puppet::Network::HTTP::Handler format = format_to_use(request) set_content_type(response, format) + rendered_result = result if result.respond_to?(:render) - set_response(response, result.render(format)) - else - set_response(response, result) + Puppet::Util::Profiler.profile("Rendered result in #{format}") do + rendered_result = result.render(format) + end + end + + Puppet::Util::Profiler.profile("Sent response") do + set_response(response, rendered_result) end end @@ -253,4 +276,12 @@ module Puppet::Network::HTTP::Handler result end end + + def configure_profiler(request_headers, request_params) + if (request_headers.has_key?(Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase) or Puppet[:profile]) + Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(Puppet.method(:debug), request_params.object_id) + else + Puppet::Util::Profiler.current = Puppet::Util::Profiler::NONE + end + end end diff --git a/lib/puppet/network/http/rack/rest.rb b/lib/puppet/network/http/rack/rest.rb index 0ccd95592..5a830674c 100644 --- a/lib/puppet/network/http/rack/rest.rb +++ b/lib/puppet/network/http/rack/rest.rb @@ -1,5 +1,7 @@ +require 'openssl' require 'puppet/network/http/handler' require 'puppet/network/http/rack/httphandler' +require 'puppet/util/ssl' class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler @@ -46,6 +48,14 @@ class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler end end + # Retrieve all headers from the http request, as a map. + def headers(request) + request.env.select {|k,v| k.start_with? 'HTTP_'}.inject({}) do |m, (k,v)| + m[k.sub(/^HTTP_/, '').downcase] = v + m + end + end + # Retrieve the accept header from the http request. def accept_header(request) request.env[HEADER_ACCEPT] @@ -98,11 +108,14 @@ class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler result = {} result[:ip] = request.ip - # if we find SSL info in the headers, use them to get a hostname. + # if we find SSL info in the headers, use them to get a hostname from the CN. # try this with :ssl_client_header, which defaults should work for # Apache with StdEnvVars. - if dn = request.env[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) - result[:node] = dn_matchdata[1].to_str + subj_str = request.env[Puppet[:ssl_client_header]] + subject = Puppet::Util::SSL.subject_from_dn(subj_str || "") + + if cn = Puppet::Util::SSL.cn_from_subject(subject) + result[:node] = cn result[:authenticated] = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS') else result[:node] = resolve_node(result) diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index 59f7ae115..123b493c0 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -14,10 +14,12 @@ class Puppet::Network::HTTP::WEBrick end def listen(address, port) - arguments = {:BindAddress => address, :Port => port} + arguments = {:BindAddress => address, :Port => port, :DoNotReverseLookup => true} arguments.merge!(setup_logger) arguments.merge!(setup_ssl) + BasicSocket.do_not_reverse_lookup = true + @server = WEBrick::HTTPServer.new(arguments) @server.listeners.each { |l| l.start_immediately = false } @@ -41,7 +43,7 @@ class Puppet::Network::HTTP::WEBrick @mutex.synchronize do raise "WEBrick server is not listening" unless @listening @server.shutdown - @thread.join + wait_for_shutdown @server = nil @listening = false end @@ -53,6 +55,10 @@ class Puppet::Network::HTTP::WEBrick end end + def wait_for_shutdown + @thread.join + end + # Configure our http log file. def setup_logger # Make sure the settings are all ready for us. diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb index dccf3031a..72d3905be 100644 --- a/lib/puppet/network/http/webrick/rest.rb +++ b/lib/puppet/network/http/webrick/rest.rb @@ -1,6 +1,7 @@ require 'puppet/network/http/handler' require 'resolv' require 'webrick' +require 'puppet/util/ssl' class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet @@ -24,6 +25,14 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet process(request, response) end + def headers(request) + result = {} + request.each do |k, v| + result[k.downcase] = v + end + result + end + def accept_header(request) request["accept"] end @@ -73,8 +82,8 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet # then we get the hostname from the cert, instead of via IP # info result[:authenticated] = false - if cert = request.client_cert and nameary = cert.subject.to_a.find { |ary| ary[0] == "CN" } - result[:node] = nameary[1] + if cert = request.client_cert and cn = Puppet::Util::SSL.cn_from_subject(cert.subject) + result[:node] = cn result[:authenticated] = true else result[:node] = resolve_node(result) diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index f018f6889..c58e3608a 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -108,4 +108,8 @@ class Puppet::Network::Server unlisten remove_pidfile end + + def wait_for_shutdown + @http_server.wait_for_shutdown + end end diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index 25e429a64..087cd5ccc 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,6 +1,7 @@ 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 @@ -217,7 +218,8 @@ class Puppet::Node::Environment def perform_initial_import return empty_parse_result if Puppet.settings[:ignoreimport] - parser = Puppet::Parser::Parser.new(self) +# parser = Puppet::Parser::Parser.new(self) + parser = Puppet::Parser::ParserFactory.parser(self) if code = Puppet.settings.uninterpolated_value(:code, name.to_s) and code != "" parser.string = code else diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb index d515c5e24..fe26c17c0 100644 --- a/lib/puppet/parameter.rb +++ b/lib/puppet/parameter.rb @@ -18,7 +18,7 @@ require 'puppet/util/docs' # @see Puppet::Type # @see Puppet::Property # @api public -# +# class Puppet::Parameter include Puppet::Util include Puppet::Util::Errors @@ -36,7 +36,7 @@ class Puppet::Parameter # which seems to works fine without this attribute declaration. # @api private # - attr_reader :validater + attr_reader :validater # Unused? # @todo The term "munger" only appears in this location in the Puppet code base. There is munge and unmunge @@ -44,14 +44,14 @@ class Puppet::Parameter # @api private # attr_reader :munger - + # @return [Symbol] The parameter name as given when it was created. attr_reader :name - + # @return [Object] The default value of the parameter as determined by the {defaultto} method, or nil if no # default has been set. attr_reader :default - + # @comment This somewhat odd documentation construct is because the getter and setter are not # orthogonal; the setter uses varargs and this confuses yard. To overcome the problem both the # getter and the setter are documented here. If this issues is fixed, a todo will be displayed @@ -65,24 +65,24 @@ class Puppet::Parameter # @overload required_features # Returns the required _provider features_ as an array of lower case symbols # @overload required_features=(*args) - # @param *args [Symbol] one or more names of required provider features + # @param *args [Symbol] one or more names of required provider features # Sets the required_provider_features_ from one or more values, or array. The given arguments # are flattened, and internalized. # @api public # @dsl type # attr_reader :required_features - + # @return [Puppet::Parameter::ValueCollection] The set of valid values (or an empty set that accepts any value). # @api private # attr_reader :value_collection - + # @return [Boolean] Flag indicating whether this parameter is a meta-parameter or not. attr_accessor :metaparam # Defines how the `default` value of a parameter is computed. - # The computation of the parameter's default value is defined by providing a value or a block. + # The computation of the parameter's default value is defined by providing a value or a block. # A default of `nil` can not be used. # @overload defaultto(value) # Defines the default value with a literal value @@ -95,7 +95,7 @@ class Puppet::Parameter # @see Parameter.default # @dsl type # @api public - # + # def defaultto(value = nil, &block) if block define_method(:default, &block) @@ -108,7 +108,7 @@ class Puppet::Parameter end end - # Produces a documentation string. + # Produces a documentation string. # If an enumeration of _valid values_ has been defined, it is appended to the documentation # for this parameter specified with the {desc} method. # @return [String] Returns a documentation string. @@ -243,6 +243,9 @@ class Puppet::Parameter # @overload validate {|| ... } # Defines an optional method that is used to validate the parameter's value. # Validation should raise appropriate exceptions, the return value of the given block is ignored. + # The easiest way to raise an appropriate exception is to call the method {Puppet::Util::Errors.fail} with + # the message as an argument. + # # @return [void] # @dsl type # @api public @@ -256,7 +259,7 @@ class Puppet::Parameter # regular expression patterns. # @note Each call to this method adds to the set of valid values # @param names [Symbol, Regexp] The set of valid literal values and/or patterns for the parameter. - # @return [void] + # @return [void] # @dsl type # @api public # @@ -318,14 +321,14 @@ class Puppet::Parameter # Initializes the parameter with a required resource reference and optional attribute settings. # The option `:resource` must be specified or an exception is raised. Any additional options passed - # are used to initialize the attributes of this parameter by treating each key in the `options` hash as + # are used to initialize the attributes of this parameter by treating each key in the `options` hash as # the name of the attribute to set, and the value as the value to set. # @param options [Hash{Symbol => Object]] Options, where `resource` is required # @option options [Puppet::Resource] :resource The resource this parameter holds a value for. Required. # @raise [Puppet::DevError] If resource is not specified in the options hash. # @api public # @note A parameter should be created via the DSL method {Puppet::Type::newparam} - # + # def initialize(options = {}) options = symbolize_options(options) if resource = options[:resource] @@ -338,7 +341,7 @@ class Puppet::Parameter set_options(options) end - # Writes the given `msg` to the log with the loglevel indicated by the associated resource's + # Writes the given `msg` to the log with the loglevel indicated by the associated resource's # `loglevel` parameter. # @todo is loglevel a metaparameter? it is looked up with `resource[:loglevel]` # @return [void] @@ -354,7 +357,7 @@ class Puppet::Parameter # @!attribute [r] name # @return [Symbol] The parameter's name as given when it was created. - # @note Since a Parameter defines the name at the class level, each Parameter class must be + # @note Since a Parameter defines the name at the class level, each Parameter class must be # unique within a type's inheritance chain. # @comment each parameter class must define the name method, and parameter # instances do not change that name this implicitly means that a given @@ -366,7 +369,7 @@ class Puppet::Parameter # @return [Boolean] Returns true if this parameter, the associated resource, or overall puppet mode is `noop`. # @todo How is noop mode set for a parameter? Is this of value in DSL to inhibit a parameter? - # + # def noop @noop ||= false tmp = @noop || self.resource.noop || Puppet[:noop] || false @@ -377,7 +380,7 @@ class Puppet::Parameter # @todo Original comment = _return the full path to us, for logging and rollback; not currently # used_ This is difficult to figure out (if it is used or not as calls are certainly made to "pathbuilder" # method is several places, not just sure if it is this implementation or not. - # + # # @api private def pathbuilder if @resource @@ -424,7 +427,7 @@ class Puppet::Parameter ret end - # This is the default implementation of `validate` that may be overridden by the DSL method {validate}. + # This is the default implementation of `validate` that may be overridden by the DSL method {validate}. # If no valid values have been defined, the given value is accepted, else it is validated against # the literal values (enumerator) and/or patterns defined by calling {newvalues}. # @@ -432,7 +435,7 @@ class Puppet::Parameter # @raise [ArgumentError] if the value is not valid # @return [void] # @api private - # + # def unsafe_validate(value) self.class.value_collection.validate(value) end @@ -441,7 +444,7 @@ class Puppet::Parameter # @return [void] # @todo Better description of when the various exceptions are raised.ArgumentError is rescued and # changed into Puppet::Error. - # @raise [ArgumentError, TypeError, Puppet::DevError, Puppet::Error] under various conditions + # @raise [ArgumentError, TypeError, Puppet::DevError, Puppet::Error] under various conditions # A protected validation method that only ever raises useful exceptions. # @api public # @@ -475,7 +478,7 @@ class Puppet::Parameter # late-binding (e.g., users might not exist when the value is assigned # but might when it is asked for)."_ does not seem to be correct, the implementation # calls both validate an munge on the given value, so no late binding. - # + # # The given value is validated and then munged (if munging has been specified). The result is store # as the value of this arameter. # @return [Object] The given `value` after munging. @@ -492,7 +495,7 @@ class Puppet::Parameter # Some types don't have providers, in which case we return the resource object itself."_ # This does not seem to be true, the default implementation that sets this value may be # {Puppet::Type.provider=} which always gets either the name of a provider or an instance of one. - # + # def provider @resource.provider end @@ -504,7 +507,7 @@ class Puppet::Parameter # @todo The original comment says = _"The properties need to return tags so that logs correctly # collect them."_ what if anything of that is of interest to document. Should tags and their relationship # to logs be described. This is a more general concept. - # + # def tags unless defined?(@tags) @tags = [] @@ -522,20 +525,20 @@ class Puppet::Parameter # Produces a String with the value formatted for display to a human. # When the parameter value is a: - # + # # * **single valued parameter value** the result is produced on the - # form `'value'` where _value_ is the string form of the parameter's value. + # form `'value'` where _value_ is the string form of the parameter's value. # - # * **Array** the list of values is enclosed in `[]`, and + # * **Array** the list of values is enclosed in `[]`, and # each produced value is separated by a comma. - # + # # * **Hash** value is output with keys in sorted order enclosed in `{}` with each entry formatted # on the form `'k' => v` where # `k` is the key in string form and _v_ is the value of the key. Entries are comma separated. # # For both Array and Hash this method is called recursively to format contained values. # @note this method does not protect against infinite structures. - # + # # @return [String] The formatted value in string form. # def self.format_value_for_display(value) diff --git a/lib/puppet/parameter/package_options.rb b/lib/puppet/parameter/package_options.rb index 60b3c06c3..b063ae4db 100644 --- a/lib/puppet/parameter/package_options.rb +++ b/lib/puppet/parameter/package_options.rb @@ -3,7 +3,7 @@ require 'puppet/parameter' # This specialized {Puppet::Parameter} handles munging of package options. # Package options are passed as an array of key value pairs. Special munging is # required as the keys and values needs to be quoted in a safe way. -# +# class Puppet::Parameter::PackageOptions < Puppet::Parameter def unsafe_munge(values) values = [values] unless values.is_a? Array diff --git a/lib/puppet/parameter/path.rb b/lib/puppet/parameter/path.rb index 430aca1de..e82a16701 100644 --- a/lib/puppet/parameter/path.rb +++ b/lib/puppet/parameter/path.rb @@ -21,7 +21,7 @@ class Puppet::Parameter::Path < Puppet::Parameter # @raise [Puppet::Error] if this property is configured for single paths and an array is given # @raise [Puppet::Error] if a path is not an absolute path # @return [Array<String>] the given paths - # + # def validate_path(paths) if paths.is_a?(Array) and ! self.class.arrays? then fail "#{name} only accepts a single path, not an array of paths" diff --git a/lib/puppet/parameter/value.rb b/lib/puppet/parameter/value.rb index c45c40e5e..35921fc31 100644 --- a/lib/puppet/parameter/value.rb +++ b/lib/puppet/parameter/value.rb @@ -7,7 +7,7 @@ require 'puppet/parameter/value_collection' # class Puppet::Parameter::Value attr_reader :name, :options, :event - attr_accessor :block, :call, :method, :required_features + attr_accessor :block, :call, :method, :required_features, :invalidate_refreshes # Adds an alias for this value. # Makes the given _name_ be an alias for this acceptable value. diff --git a/lib/puppet/parameter/value_collection.rb b/lib/puppet/parameter/value_collection.rb index c048fbdaa..144f8ba74 100644 --- a/lib/puppet/parameter/value_collection.rb +++ b/lib/puppet/parameter/value_collection.rb @@ -2,7 +2,7 @@ require 'puppet/parameter/value' # A collection of values and regular expressions, used for specifying allowed values # in a given parameter. -# @note This class is considered part of the internal implementation of {Puppet::Parameter}, and +# @note This class is considered part of the internal implementation of {Puppet::Parameter}, and # {Puppet::Property} and the functionality provided by this class should be used via their interfaces. # @comment This class probably have several problems when trying to use it with a combination of # regular expressions and aliases as it finds an acceptable value holder vi "name" which may be @@ -88,7 +88,7 @@ class Puppet::Parameter::ValueCollection end # Munges the value if it is valid, else produces the same value. - # @param value [Object] the value to munge + # @param value [Object] the value to munge # @return [Object] the munged value, or the given value # @todo This method does not seem to do any munging. It just returns the value if it matches the # regexp, or the (most likely Symbolic) allowed value if it matches (which is more of a replacement @@ -123,6 +123,10 @@ class Puppet::Parameter::ValueCollection # was possible to specify a value of `:before` or `:after` for the purpose of calling # both the block and the provider. Use of these deprecated options will now raise an exception later # in the process when the _is_ value is set (see Puppet::Property#set). + # @option options [Symbol] :invalidate_refreshes True if a change on this property should invalidate and + # remove any scheduled refreshes (from notify or subscribe) targeted at the same resource. For example, if + # a change in this property takes into account any changes that a scheduled refresh would have performed, + # then the scheduled refresh would be deleted. # @option options [Object] _any_ Any other option is treated as a call to a setter having the given # option name (e.g. `:required_features` calls `required_features=` with the option's value as an # argument). @@ -187,7 +191,7 @@ class Puppet::Parameter::ValueCollection # Returns a valid value matcher (a literal or regular expression) # @todo This looks odd, asking for an instance that matches a symbol, or a instance that has # a regexp. What is the intention here? Marking as api private... - # + # # @return [Puppet::Parameter::Value] a valid valud matcher # @api private # diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index 3cc1a8e47..438cb49b4 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -14,7 +14,7 @@ class Puppet::Parser::AST include Puppet::Util::MethodHelper include Puppet::Util::Docs - attr_accessor :parent, :scope, :file, :line + attr_accessor :parent, :scope, :file, :line, :pos def inspect "( #{self.class} #{self.to_s} #{@children.inspect} )" @@ -110,8 +110,10 @@ require 'puppet/parser/ast/function' require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/in_operator' +require 'puppet/parser/ast/lambda' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' +require 'puppet/parser/ast/method_call' require 'puppet/parser/ast/minus' require 'puppet/parser/ast/node' require 'puppet/parser/ast/nop' diff --git a/lib/puppet/parser/ast/arithmetic_operator.rb b/lib/puppet/parser/ast/arithmetic_operator.rb index 33352d727..9b4fe769d 100644 --- a/lib/puppet/parser/ast/arithmetic_operator.rb +++ b/lib/puppet/parser/ast/arithmetic_operator.rb @@ -11,29 +11,73 @@ class Puppet::Parser::AST [@lval,@rval,@operator].each { |child| yield child } end - # Returns a boolean which is the result of the boolean operation - # of lval and rval operands + # Produces an object which is the result of the applying the operator to the of lval and rval operands. + # * Supports +, -, *, /, %, and <<, >> on numeric strings. + # * Supports + on arrays (concatenate), and hashes (merge) + # * Supports << on arrays (append) + # def evaluate(scope) # evaluate the operands, should return a boolean value - lval = @lval.safeevaluate(scope) - lval = Puppet::Parser::Scope.number?(lval) - if lval == nil - raise ArgumentError, "left operand of #{@operator} is not a number" + left = @lval.safeevaluate(scope) + right = @rval.safeevaluate(scope) + + if left.is_a?(Array) || right.is_a?(Array) + eval_array(left, right) + elsif left.is_a?(Hash) || right.is_a?(Hash) + eval_hash(left, right) + else + eval_numeric(left, right) end - rval = @rval.safeevaluate(scope) - rval = Puppet::Parser::Scope.number?(rval) - if rval == nil - raise ArgumentError, "right operand of #{@operator} is not a number" + end + + # Concatenates (+) two arrays, or appends (<<) any object to a newly created array. + # + def eval_array(left, right) + assert_concatenation_supported() + + raise ArgumentError, "operator #{@operator} is not applicable when one of the operands is an Array." unless %w{+ <<}.include?(@operator) + raise ArgumentError, "left operand of #{@operator} must be an Array" unless left.is_a?(Array) + if @operator == '+' + raise ArgumentError, "right operand of #{@operator} must be an Array when left is an Array." unless right.is_a?(Array) + return left + right end + # only append case remains, left asserted to be an array, and right may be any object + # wrapping right in an array and adding it ensures a new copy (operator << mutates). + # + left + [right] + end + + # Merges two hashes. + # + def eval_hash(left, right) + assert_concatenation_supported() + + raise ArgumentError, "operator #{@operator} is not applicable when one of the operands is an Hash." unless @operator == '+' + raise ArgumentError, "left operand of #{@operator} must be an Hash" unless left.is_a?(Hash) + raise ArgumentError, "right operand of #{@operator} must be an Hash" unless right.is_a?(Hash) + # merge produces a merged copy + left.merge(right) + end + + def eval_numeric(left, right) + left = Puppet::Parser::Scope.number?(left) + right = Puppet::Parser::Scope.number?(right) + raise ArgumentError, "left operand of #{@operator} is not a number" unless left != nil + raise ArgumentError, "right operand of #{@operator} is not a number" unless right != nil # compute result - lval.send(@operator, rval) + left.send(@operator, right) + end + + def assert_concatenation_supported + return if Puppet[:parser] == 'future' + raise ParseError.new("Unsupported Operation: Array concatenation available with '--parser future' setting only.") end def initialize(hash) super - raise ArgumentError, "Invalid arithmetic operator #{@operator}" unless %w{+ - * / << >>}.include?(@operator) + raise ArgumentError, "Invalid arithmetic operator #{@operator}" unless %w{+ - * / % << >>}.include?(@operator) end end end diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb index 7283a1f6c..42a47a1fd 100644 --- a/lib/puppet/parser/ast/astarray.rb +++ b/lib/puppet/parser/ast/astarray.rb @@ -9,7 +9,7 @@ class Puppet::Parser::AST class ASTArray < Branch include Enumerable - # Return a child by index. Probably never used. + # Return a child by index. Used (at least) by tests. def [](index) @children[index] end diff --git a/lib/puppet/parser/ast/block_expression.rb b/lib/puppet/parser/ast/block_expression.rb new file mode 100644 index 000000000..7f75ceb0b --- /dev/null +++ b/lib/puppet/parser/ast/block_expression.rb @@ -0,0 +1,41 @@ +require 'puppet/parser/ast/branch' + +class Puppet::Parser::AST + class BlockExpression < Branch + include Enumerable + + # Evaluate contained expressions, produce result of the last + def evaluate(scope) + result = nil + @children.each do |child| + # Skip things that respond to :instantiate (classes, nodes, + # and definitions), because they have already been + # instantiated. + if !child.respond_to?(:instantiate) + result = child.safeevaluate(scope) + end + end + result + end + + # Return a child by index. + def [](index) + @children[index] + end + + def push(*ary) + ary.each { |child| + #Puppet.debug "adding %s(%s) of type %s to %s" % + # [child, child.object_id, child.class.to_s.sub(/.+::/,''), + # self.object_id] + @children.push(child) + } + + self + end + + def to_s + "[" + @children.collect { |c| c.to_s }.join(', ') + "]" + end + end +end diff --git a/lib/puppet/parser/ast/function.rb b/lib/puppet/parser/ast/function.rb index f6916b755..5d2dc6f3d 100644 --- a/lib/puppet/parser/ast/function.rb +++ b/lib/puppet/parser/ast/function.rb @@ -6,7 +6,7 @@ class Puppet::Parser::AST associates_doc - attr_accessor :name, :arguments + attr_accessor :name, :arguments, :pblock def evaluate(scope) # Make sure it's a defined function @@ -16,8 +16,16 @@ class Puppet::Parser::AST case @ftype when :rvalue raise Puppet::ParseError, "Function '#{@name}' does not return a value" unless Puppet::Parser::Functions.rvalue?(@name) + when :statement - if Puppet::Parser::Functions.rvalue?(@name) + # It is harmless to produce an ignored rvalue, the alternative is to mark functions + # as appropriate for both rvalue and statements + # Keeping the old behavior when a pblock is not present. This since it is not known + # if the lambda contains a statement or not (at least not without a costly search). + # The purpose of the check is to protect a user for producing a meaningless rvalue where the + # operation has no side effects. + # + if !pblock && Puppet::Parser::Functions.rvalue?(@name) raise Puppet::ParseError, "Function '#{@name}' must be the value of a statement" end @@ -28,6 +36,9 @@ class Puppet::Parser::AST # We don't need to evaluate the name, because it's plaintext args = @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x } + # append a puppet lambda (unevaluated) if it is defined + args << pblock if pblock + scope.send("function_#{@name}", args) end diff --git a/lib/puppet/parser/ast/lambda.rb b/lib/puppet/parser/ast/lambda.rb new file mode 100644 index 000000000..00c0e860a --- /dev/null +++ b/lib/puppet/parser/ast/lambda.rb @@ -0,0 +1,107 @@ +require 'puppet/parser/ast/block_expression' + +class Puppet::Parser::AST + # A block of statements/expressions with additional parameters + # Requires scope to contain the values for the defined parameters when evaluated + # If evaluated without a prepared scope, the lambda will behave like its super class. + # + class Lambda < AST::BlockExpression + + # The lambda parameters. + # These are encoded as an array where each entry is an array of one or two object. The first + # is the parameter name, and the optional second object is the value expression (that will + # be evaluated when bound to a scope). + # The value expression is the default value for the parameter. All default values must be + # at the end of the parameter list. + # + # @return [Array<Array<String,String>>] list of parameter names with optional value expression + attr_accessor :parameters + # Evaluates each expression/statement and produce the last expression evaluation result + # @return [Object] what the last expression evaluated to + def evaluate(scope) + if @children.is_a? Puppet::Parser::AST::ASTArray + result = nil + @children.each {|expr| result = expr.evaluate(scope) } + result + else + @children.evaluate(scope) + end + end + + # Calls the lambda. + # Assigns argument values in a nested local scope that should be used to evaluate the lambda + # and then evaluates the lambda. + # @param scope [Puppet::Scope] the calling scope + # @return [Object] the result of evaluating the expression(s) in the lambda + # + def call(scope, *args) + raise Puppet::ParseError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size + merged = parameters.zip(args) + missing = merged.select { |e| !e[1] && e[0].size == 1 } + unless missing.empty? + optional = parameters.count { |p| p.size == 2 } + raise Puppet::ParseError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}" + end + + evaluated = merged.collect do |m| + # Ruby 1.8.7 zip seems to produce a different result than Ruby 1.9.3 in some situations + n = m[0].is_a?(Array) ? m[0][0] : m[0] + v = m[1] || (m[0][1]).safeevaluate(scope) # given value or default expression value + [n, v] + end + + # Store the evaluated name => value associations in a new inner/local/ephemeral scope + # (This is made complicated due to the fact that the implementation of scope is overloaded with + # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope + # on a scope "stack"). + begin + elevel = scope.ephemeral_level + scope.ephemeral_from(Hash[evaluated], file, line) + result = safeevaluate(scope) + ensure + scope.unset_ephemeral_var(elevel) + result ||= nil + end + result + end + + # Validates the lambda. + # Validation checks if parameters with default values are at the end of the list. (It is illegal + # to have a parameter with default value followed by one without). + # + # @raise [Puppet::ParseError] if a parameter with a default comes before a parameter without default value + # + def validate + params = parameters || [] + defaults = params.drop_while {|p| p.size < 2 } + trailing = defaults.drop_while {|p| p.size == 2 } + raise Puppet::ParseError, "Lambda parameters with default values must be placed last" unless trailing.empty? + end + + # Returns the number of parameters (required and optional) + # @return [Integer] the total number of accepted parameters + def parameter_count + @parameters.size + end + + # Returns the number of optional parameters. + # @return [Integer] the number of optional accepted parameters + def optional_parameter_count + @parameters.count {|p| p.size == 2 } + end + + def initialize(options) + super(options) + # ensure there is an empty parameters structure if not given by creator + @parameters = [] unless options[:parameters] + validate + end + + def to_s + result = ["{|"] + result += @parameters.collect {|p| "#{p[0]}" + (p.size == 2 && p[1]) ? p[1].to_s() : '' }.join(', ') + result << "| ... }" + result.join('') + end + end +end diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index 2371ae723..ac4d2c26a 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -140,7 +140,7 @@ class Puppet::Parser::AST def array_index_or_key(object, key) if object.is_a?(Array) - raise Puppet::ParserError, "#{key} is not an integer, but is used as an index of an array" unless key = Puppet::Parser::Scope.number?(key) + raise Puppet::ParseError, "#{key} is not an integer, but is used as an index of an array" unless key = Puppet::Parser::Scope.number?(key) end key end @@ -148,7 +148,6 @@ class Puppet::Parser::AST def evaluate(scope) object = evaluate_container(scope) accesskey = evaluate_key(scope) - raise Puppet::ParseError, "#{variable} is not an hash or array when accessing it with #{accesskey}" unless object.is_a?(Hash) or object.is_a?(Array) object[array_index_or_key(object, accesskey)] || :undef diff --git a/lib/puppet/parser/ast/method_call.rb b/lib/puppet/parser/ast/method_call.rb new file mode 100644 index 000000000..d613674ad --- /dev/null +++ b/lib/puppet/parser/ast/method_call.rb @@ -0,0 +1,77 @@ +require 'puppet/parser/ast/branch' +require 'puppet/parser/methods' + +class Puppet::Parser::AST + # An AST object to call a method + class MethodCall < AST::Branch + + associates_doc + + # An AST that evaluates to the object the method is applied to + # @return [Puppet::Parser::AST] + attr_accessor :receiver + + # The name of the method + # @return [String] + attr_accessor :name + + # The arguments to evaluate as arguments to the method. + # @return [Array<Puppet::Parser::AST>] + attr_accessor :arguments + + # An optional lambda/block that will be yielded to by the called method (if it supports this) + # @return [Puppet::Parser::AST::Lambda] + attr_accessor :lambda + + # Evaluates the method call and returns what the called method/function returns. + # The evaluation evaluates all arguments in the calling scope and then delegates + # to a "method" instance produced by Puppet::Parser::Methods for this method call. + # @see Puppet::Parser::Methods + # @return [Object] what the called method/function returns + def evaluate(scope) + # Make sure it's a defined method for the receiver + r = @receiver.evaluate(scope) + raise Puppet::ParseError, "No object to apply method #{@name} to" unless r + m = Puppet::Parser::Methods.find_method(scope, r, @name) + raise Puppet::ParseError, "Unknown method #{@name} for #{r}" unless m + + # Now check if rvalue is required (in expressions) + case @ftype + when :rvalue + raise Puppet::ParseError, "Method '#{@name}' does not return a value" unless m.is_rvalue? + when :statement + # When used as a statement, ignore if it produces a rvalue (it is simply not used) + else + raise Puppet::DevError, "Invalid method type #{@ftype.inspect}" + end + + # Evaluate arguments + args = @arguments ? @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x } : [] + + # There is no need to evaluate the name, since it is a literal ruby string + + # call the method (it is already bound to the receiver and name) + m.invoke(scope, args, @lambda) + end + + def initialize(hash) + @ftype = hash[:ftype] || :rvalue + hash.delete(:ftype) if hash.include? :ftype + + super(hash) + + # Lastly, check the parity + end + + # Sets this method call in statement mode where a produced rvalue is ignored. + # @return [void] + def ignore_rvalue + @ftype = :statement + end + + def to_s + args = arguments.is_a?(ASTArray) ? arguments.to_s.gsub(/\[(.*)\]/,'\1') : arguments + "#{@receiver.to_s}.#{name} (#{args})" + (@lambda ? " #{@lambda.to_s}" : '') + end + end +end diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb index 1374c7342..07999f316 100644 --- a/lib/puppet/parser/ast/vardef.rb +++ b/lib/puppet/parser/ast/vardef.rb @@ -21,6 +21,13 @@ class Puppet::Parser::AST scope.setvar(name,value, :file => file, :line => line, :append => @append) end end + if @append + # Produce resulting value from append operation + scope[name] + else + # Produce assigned value + value + end end def each diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 381b46f25..cd80ac663 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -90,18 +90,19 @@ class Puppet::Parser::Compiler # This is the main entry into our catalog. def compile # Set the client's parameters into the top scope. - set_node_parameters - create_settings_scope + Puppet::Util::Profiler.profile("Compile: Set node parameters") { set_node_parameters } - evaluate_main + Puppet::Util::Profiler.profile("Compile: Created settings scope") { create_settings_scope } - evaluate_ast_node + Puppet::Util::Profiler.profile("Compile: Evaluated main") { evaluate_main } - evaluate_node_classes + Puppet::Util::Profiler.profile("Compile: Evaluated AST node") { evaluate_ast_node } - evaluate_generators + Puppet::Util::Profiler.profile("Compile: Evaluated node classes") { evaluate_node_classes } - finish + Puppet::Util::Profiler.profile("Compile: Evaluated generators") { evaluate_generators } + + Puppet::Util::Profiler.profile("Compile: Finished catalog") { finish } fail_on_unevaluated @@ -226,17 +227,18 @@ class Puppet::Parser::Compiler def evaluate_collections return false if @collections.empty? - found_something = false exceptwrap do # We have to iterate over a dup of the array because # collections can delete themselves from the list, which # changes its length and causes some collections to get missed. - @collections.dup.each do |collection| - found_something = true if collection.evaluate + Puppet::Util::Profiler.profile("Evaluated collections") do + found_something = false + @collections.dup.each do |collection| + found_something = true if collection.evaluate + end + found_something end end - - found_something end # Make sure all of our resources have been evaluated into native resources. @@ -244,7 +246,13 @@ class Puppet::Parser::Compiler # evaluate_generators loop. def evaluate_definitions exceptwrap do - !unevaluated_resources.each { |resource| resource.evaluate }.empty? + Puppet::Util::Profiler.profile("Evaluated definitions") do + !unevaluated_resources.each do |resource| + Puppet::Util::Profiler.profile("Evaluated resource #{resource}") do + resource.evaluate + end + end.empty? + end end end @@ -257,9 +265,12 @@ class Puppet::Parser::Compiler loop do done = true - # Call collections first, then definitions. - done = false if evaluate_collections - done = false if evaluate_definitions + Puppet::Util::Profiler.profile("Iterated (#{count + 1}) on generators") do + # Call collections first, then definitions. + done = false if evaluate_collections + done = false if evaluate_definitions + end + break if done count += 1 diff --git a/lib/puppet/parser/e_parser_adapter.rb b/lib/puppet/parser/e_parser_adapter.rb new file mode 100644 index 000000000..beec14752 --- /dev/null +++ b/lib/puppet/parser/e_parser_adapter.rb @@ -0,0 +1,120 @@ +require 'puppet/pops' + +module Puppet; module Parser; end; end; +# Adapts an egrammar/eparser to respond to the public API of the classic parser +# +class Puppet::Parser::EParserAdapter + + def initialize(classic_parser) + @classic_parser = classic_parser + @file = '' + @string = '' + @use = :undefined + end + + def file=(file) + @classic_parser.file = file + @file = file + @use = :file + end + + def parse(string = nil) + if @file =~ /\.rb$/ + return parse_ruby_file + else + self.string= string if string + parser = Puppet::Pops::Parser::Parser.new() + parse_result = if @use == :string + parser.parse_string(@string) + else + parser.parse_file(@file) + end + # Compute the source_file to set in created AST objects (it was either given, or it may be unknown + # if caller did not set a file and the present a string. + # + source_file = @file || "unknown-source-location" + + # Validate + validate(parse_result) + + # Transform the result, but only if not nil + parse_result = Puppet::Pops::Model::AstTransformer.new(source_file, @classic_parser).transform(parse_result) if parse_result + if parse_result && !parse_result.is_a?(Puppet::Parser::AST::BlockExpression) + # Need to transform again, if result is not wrapped in something iterable when handed off to + # a new Hostclass as its code. + parse_result = Puppet::Parser::AST::BlockExpression.new(:children => [parse_result]) if parse_result + end + end + + Puppet::Parser::AST::Hostclass.new('', :code => parse_result) + end + + def validate(parse_result) + # TODO: This is too many hoops to jump through... ugly API + # could reference a ValidatorFactory.validator_3_1(acceptor) instead. + # and let the factory abstract the rest. + # + return unless parse_result + + acceptor = Puppet::Pops::Validation::Acceptor.new + validator = Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) + validator.validate(parse_result) + + max_errors = Puppet[:max_errors] + max_warnings = Puppet[:max_warnings] + 1 + max_deprecations = Puppet[:max_deprecations] + 1 + + # If there are warnings output them + warnings = acceptor.warnings + if warnings.size > 0 + formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new + emitted_w = 0 + emitted_dw = 0 + acceptor.warnings.each {|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 + emitted_dw += 1 + else + Puppet.warning(formatter.format(w)) if emitted_w < max_warnings + emitted_w += 1 + end + break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then + } + end + + # If there were errors, report the first found. Use a puppet style formatter. + errors = acceptor.errors + if errors.size > 0 + formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new + if errors.size == 1 || max_errors <= 1 + # raise immediately + raise Puppet::ParseError.new(formatter.format(errors[0])) + end + emitted = 0 + errors.each do |e| + Puppet.err(formatter.format(e)) + emitted += 1 + break if emitted >= max_errors + end + warnings_message = warnings.size > 0 ? ", and #{warnings.size} warnings" : "" + giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up" + exception = Puppet::ParseError.new(giving_up_message) + exception.file = errors[0].file + raise exception + end + end + + def string=(string) + @classic_parser.string = string + @string = string + @use = :string + end + + def parse_ruby_file + @classic_parser.parse + end +end diff --git a/lib/puppet/parser/files.rb b/lib/puppet/parser/files.rb index 78fdf0314..b0239f2f1 100644 --- a/lib/puppet/parser/files.rb +++ b/lib/puppet/parser/files.rb @@ -1,11 +1,12 @@ require 'puppet/module' -require 'puppet/parser/parser' +#require 'puppet/parser/parser' # This is a silly central module for finding # different kinds of files while parsing. This code # doesn't really belong in the Puppet::Module class, # but it doesn't really belong anywhere else, either. -module Puppet::Parser::Files +module Puppet; module Parser; module Files + module_function # Return a list of manifests (as absolute filenames) that match +pat+ @@ -28,9 +29,9 @@ module Puppet::Parser::Files end # Find the concrete file denoted by +file+. If +file+ is absolute, - # return it directly. Otherwise try to find it as a template in a - # module. If that fails, return it relative to the +templatedir+ config - # param. + # return it directly. Otherwise try to find relative to the +templatedir+ + # config param. If that fails try to find it as a template in a + # module. # In all cases, an absolute path is returned, which does not # necessarily refer to an existing file def find_template(template, environment = nil) @@ -85,4 +86,4 @@ module Puppet::Parser::Files path.split(File::SEPARATOR, 2) unless path == "" or Puppet::Util.absolute_path?(path) end -end +end; end; end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 484b4ea22..7015ec4d9 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -137,15 +137,17 @@ module Puppet::Parser::Functions fname = "function_#{name}" environment_module.send(:define_method, fname) do |*args| - if args[0].is_a? Array - if arity >= 0 and args[0].size != arity - raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for #{arity})" - elsif arity < 0 and args[0].size < (arity+1).abs - raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for minimum #{(arity+1).abs})" + Puppet::Util::Profiler.profile("Called #{name}") do + if args[0].is_a? Array + if arity >= 0 and args[0].size != arity + raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for #{arity})" + elsif arity < 0 and args[0].size < (arity+1).abs + raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for minimum #{(arity+1).abs})" + end + self.send(real_fname, args[0]) + else + raise ArgumentError, "custom functions must be called with a single array that contains the arguments. For example, function_example([1]) instead of function_example(1)" end - self.send(real_fname, args[0]) - else - raise ArgumentError, "custom functions must be called with a single array that contains the arguments. For example, function_example([1]) instead of function_example(1)" end end diff --git a/lib/puppet/parser/functions/collect.rb b/lib/puppet/parser/functions/collect.rb new file mode 100644 index 000000000..c4756d8c7 --- /dev/null +++ b/lib/puppet/parser/functions/collect.rb @@ -0,0 +1,43 @@ +require 'puppet/parser/ast/lambda' + +Puppet::Parser::Functions::newfunction( +:collect, +:type => :rvalue, +:arity => 2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of entries from the first + argument and returns an array with the result of each invocation of the parameterized block. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + a parameterized block as produced by the puppet syntax: + + $a.collect |$x| { ... } + + When the first argument is an Array, the block is called with each entry in turn. When the first argument + is a hash the entry is an array with `[key, value]`. + + *Examples* + + # Turns hash into array of values + $a.collect |$x|{ $x[1] } + + # Turns hash into array of keys + $a.collect |$x| { $x[0] } + + Since 3.2 + ENDHEREDOC + + receiver = args[0] + pblock = args[1] + + raise ArgumentError, ("collect(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + + case receiver + when Array + when Hash + else + raise ArgumentError, ("collect(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") + end + + receiver.to_a.collect {|x| pblock.call(self, x) } +end diff --git a/lib/puppet/parser/functions/each.rb b/lib/puppet/parser/functions/each.rb new file mode 100644 index 000000000..1c089e541 --- /dev/null +++ b/lib/puppet/parser/functions/each.rb @@ -0,0 +1,96 @@ +Puppet::Parser::Functions::newfunction( +:each, +:type => :rvalue, +:arity => 2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of selected entries from the first + argument and returns the first argument. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + a parameterized block as produced by the puppet syntax: + + $a.each {|$x| ... } + + When the first argument is an Array, the parameterized block should define one or two block parameters. + For each application of the block, the next element from the array is selected, and it is passed to + the block if the block has one parameter. If the block has two parameters, the first is the elements + index, and the second the value. The index starts from 0. + + $a.each {|$index, $value| ... } + + When the first argument is a Hash, the parameterized block should define one or two parameters. + When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, + and when two parameters are defined the iteration is performed with key and value. + + $a.each {|$entry| ..."key ${$entry[0]}, value ${$entry[1]}" } + $a.each {|$key, $value| ..."key ${key}, value ${value}" } + + Since 3.2 + ENDHEREDOC + require 'puppet/parser/ast/lambda' + + def foreach_Array(o, scope, pblock) + return nil unless pblock + + serving_size = pblock.parameter_count + if serving_size == 0 + raise ArgumentError, "Block must define at least one parameter; value." + end + if serving_size > 2 + raise ArgumentError, "Block must define at most two parameters; index, value" + end + enumerator = o.each + index = 0 + result = nil + if serving_size == 1 + (o.size).times do + pblock.call(scope, enumerator.next) + end + else + (o.size).times do + pblock.call(scope, index, enumerator.next) + index = index +1 + end + end + o + end + + def foreach_Hash(o, scope, pblock) + return nil unless pblock + serving_size = pblock.parameter_count + case serving_size + when 0 + raise ArgumentError, "Block must define at least one parameter (for hash entry key)." + when 1 + when 2 + else + raise ArgumentError, "Block must define at most two parameters (for hash entry key and value)." + end + enumerator = o.each_pair + result = nil + if serving_size == 1 + (o.size).times do + pblock.call(scope, enumerator.next) + end + else + (o.size).times do + pblock.call(scope, *enumerator.next) + end + end + o + end + + raise ArgumentError, ("each(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2 + receiver = args[0] + pblock = args[1] + raise ArgumentError, ("each(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + + case receiver + when Array + foreach_Array(receiver, self, pblock) + when Hash + foreach_Hash(receiver, self, pblock) + else + raise ArgumentError, ("each(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") + end +end diff --git a/lib/puppet/parser/functions/foreach.rb b/lib/puppet/parser/functions/foreach.rb new file mode 100644 index 000000000..130720946 --- /dev/null +++ b/lib/puppet/parser/functions/foreach.rb @@ -0,0 +1,96 @@ +Puppet::Parser::Functions::newfunction( +:foreach, +:type => :rvalue, +:arity => 2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of selected entries from the first + argument and returns the first argument. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + a parameterized block as produced by the puppet syntax: + + $a.foreach {|$x| ... } + + When the first argument is an Array, the parameterized block should define one or two block parameters. + For each application of the block, the next element from the array is selected, and it is passed to + the block if the block has one parameter. If the block has two parameters, the first is the elements + index, and the second the value. The index starts from 0. + + $a.foreach {|$index, $value| ... } + + When the first argument is a Hash, the parameterized block should define one or two parameters. + When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, + and when two parameters are defined the iteration is performed with key and value. + + $a.foreach {|$entry| ..."key ${$entry[0]}, value ${$entry[1]}" } + $a.foreach {|$key, $value| ..."key ${key}, value ${value}" } + + Since 3.2 + ENDHEREDOC + require 'puppet/parser/ast/lambda' + + def foreach_Array(o, scope, pblock) + return nil unless pblock + + serving_size = pblock.parameter_count + if serving_size == 0 + raise ArgumentError, "Block must define at least one parameter; value." + end + if serving_size > 2 + raise ArgumentError, "Block must define at most two parameters; index, value" + end + enumerator = o.each + index = 0 + result = nil + if serving_size == 1 + (o.size).times do + pblock.call(scope, enumerator.next) + end + else + (o.size).times do + pblock.call(scope, index, enumerator.next) + index = index +1 + end + end + o + end + + def foreach_Hash(o, scope, pblock) + return nil unless pblock + serving_size = pblock.parameter_count + case serving_size + when 0 + raise ArgumentError, "Block must define at least one parameter (for hash entry key)." + when 1 + when 2 + else + raise ArgumentError, "Block must define at most two parameters (for hash entry key and value)." + end + enumerator = o.each_pair + result = nil + if serving_size == 1 + (o.size).times do + pblock.call(scope, enumerator.next) + end + else + (o.size).times do + pblock.call(scope, *enumerator.next) + end + end + o + end + + raise ArgumentError, ("foreach(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2 + receiver = args[0] + pblock = args[1] + raise ArgumentError, ("foreach(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + + case receiver + when Array + foreach_Array(receiver, self, pblock) + when Hash + foreach_Hash(receiver, self, pblock) + else + raise ArgumentError, ("foreach(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") + end +end diff --git a/lib/puppet/parser/functions/fqdn_rand.rb b/lib/puppet/parser/functions/fqdn_rand.rb index b39c0bb77..f9bd2d23e 100644 --- a/lib/puppet/parser/functions/fqdn_rand.rb +++ b/lib/puppet/parser/functions/fqdn_rand.rb @@ -8,6 +8,6 @@ Puppet::Parser::Functions::newfunction(:fqdn_rand, :arity => -2, :type => :rvalu $random_number = fqdn_rand(30) $random_number_seed = fqdn_rand(30,30)") do |args| max = args.shift.to_i - srand(Digest::MD5.hexdigest([self['::fqdn'],args].join(':')).hex) - rand(max).to_s + seed = Digest::MD5.hexdigest([self['::fqdn'],args].join(':')).hex + Puppet::Util.deterministic_rand(seed,max) end diff --git a/lib/puppet/parser/functions/hiera.rb b/lib/puppet/parser/functions/hiera.rb index 9de4f70f1..4c7b06d07 100644 --- a/lib/puppet/parser/functions/hiera.rb +++ b/lib/puppet/parser/functions/hiera.rb @@ -1,6 +1,24 @@ +require 'hiera_puppet' + module Puppet::Parser::Functions - newfunction(:hiera, :type => :rvalue, :arity => -2) do |*args| - require 'hiera_puppet' + newfunction(:hiera, :type => :rvalue, :arity => -2, :doc => "Performs a + standard priority lookup and returns the most specific value for a given key. + The returned value can be data of any type (strings, arrays, or hashes). + + In addition to the required `key` argument, `hiera` accepts two additional + arguments: + + - a `default` argument in the second position, providing a value to be + returned in the absence of matches to the `key` argument + - an `override` argument in the third position, providing a data source + to consult for matching values, even if it would not ordinarily be + part of the matched hierarchy. If Hiera doesn't find a matching key + in the named override data source, it will continue to search through the + rest of the hierarchy. + + More thorough examples of `hiera` are available at: + <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> + ") do |*args| key, default, override = HieraPuppet.parse_args(args) HieraPuppet.lookup(key, default, self, override, :priority) end diff --git a/lib/puppet/parser/functions/hiera_array.rb b/lib/puppet/parser/functions/hiera_array.rb index 61ad5e6aa..ce11dc907 100644 --- a/lib/puppet/parser/functions/hiera_array.rb +++ b/lib/puppet/parser/functions/hiera_array.rb @@ -1,6 +1,25 @@ +require 'hiera_puppet' + module Puppet::Parser::Functions - newfunction(:hiera_array, :type => :rvalue, :arity => -2) do |*args| - require 'hiera_puppet' + newfunction(:hiera_array, :type => :rvalue, :arity => -2,:doc => "Returns all + matches throughout the hierarchy --- not just the first match --- as a flattened array of unique values. + If any of the matched values are arrays, they're flattened and included in the results. + + In addition to the required `key` argument, `hiera_array` accepts two additional + arguments: + + - a `default` argument in the second position, providing a string or array to be returned + in the absence of matches to the `key` argument + - an `override` argument in the third position, providing a data source to consult for + matching values, even if it would not ordinarily be part of the matched hierarchy. + If Hiera doesn't find a matching key in the named override data source, it will + continue to search through the rest of the hierarchy. + + If any matched value is a hash, puppet will raise a type mismatch error. + + More thorough examples of `hiera` are available at: + <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> + ") do |*args| key, default, override = HieraPuppet.parse_args(args) HieraPuppet.lookup(key, default, self, override, :array) end diff --git a/lib/puppet/parser/functions/hiera_hash.rb b/lib/puppet/parser/functions/hiera_hash.rb index 7f26a0924..dacc301ee 100644 --- a/lib/puppet/parser/functions/hiera_hash.rb +++ b/lib/puppet/parser/functions/hiera_hash.rb @@ -1,6 +1,27 @@ +require 'hiera_puppet' + module Puppet::Parser::Functions - newfunction(:hiera_hash, :type => :rvalue, :arity => -2) do |*args| - require 'hiera_puppet' + newfunction(:hiera_hash, :type => :rvalue, :arity => -2, :doc => + "Returns a merged hash of matches from throughout the hierarchy. In cases where two or + more hashes share keys, the hierarchy order determines which key/value pair will be + used in the returned hash, with the pair in the highest priority data source winning. + + In addition to the required `key` argument, `hiera_hash` accepts two additional + arguments: + + - a `default` argument in the second position, providing a hash to be returned in the + absence of any matches for the `key` argument + - an `override` argument in the third position, providing a data source to insert at + the top of the hierarchy, even if it would not ordinarily match during a Hiera data + source lookup. If Hiera doesn't find a match in the named override data source, it will + continue to search through the rest of the hierarchy. + + `hiera_hash` expects that all values returned will be hashes. If any of the values + found in the data sources are strings or arrays, puppet will raise a type mismatch error. + + More thorough examples of `hiera_hash` are available at: + <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> + ") do |*args| key, default, override = HieraPuppet.parse_args(args) HieraPuppet.lookup(key, default, self, override, :hash) end diff --git a/lib/puppet/parser/functions/hiera_include.rb b/lib/puppet/parser/functions/hiera_include.rb index bb77c151c..5fa7994fb 100644 --- a/lib/puppet/parser/functions/hiera_include.rb +++ b/lib/puppet/parser/functions/hiera_include.rb @@ -1,6 +1,37 @@ +require 'hiera_puppet' + module Puppet::Parser::Functions - newfunction(:hiera_include, :arity => -2) do |*args| - require 'hiera_puppet' + newfunction(:hiera_include, :arity => -2, :doc => "Assigns classes to a node + using an array merge lookup that retrieves the value for a user-specified key + from a Hiera data source. + + To use `hiera_include`, the following configuration is required: + + - A key name to use for classes, e.g. `classes`. + - A line in the puppet `sites.pp` file (e.g. `/etc/puppet/manifests/sites.pp`) + reading `hiera_include('classes')`. Note that this line must be outside any node + definition and below any top-scope variables in use for Hiera lookups. + - Class keys in the appropriate data sources. In a data source keyed to a node's role, + one might have: + + --- + classes: + - apache + - apache::passenger + + In addition to the required `key` argument, `hiera_include` accepts two additional + arguments: + + - a `default` argument in the second position, providing an array to be returned + in the absence of matches to the `key` argument + - an `override` argument in the third position, providing a data source to consult + for matching values, even if it would not ordinarily be part of the matched hierarchy. + If Hiera doesn't find a matching key in the named override data source, it will continue + to search through the rest of the hierarchy. + + More thorough examples of `hiera_include` are available at: + <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> + ") do |*args| key, default, override = HieraPuppet.parse_args(args) if answer = HieraPuppet.lookup(key, default, self, override, :array) method = Puppet::Parser::Functions.function(:include) diff --git a/lib/puppet/parser/functions/inline_template.rb b/lib/puppet/parser/functions/inline_template.rb index d86024154..03a3a3879 100644 --- a/lib/puppet/parser/functions/inline_template.rb +++ b/lib/puppet/parser/functions/inline_template.rb @@ -1,9 +1,9 @@ Puppet::Parser::Functions::newfunction(:inline_template, :type => :rvalue, :arity => -2, :doc => - "Evaluate a template string and return its value. See - [the templating docs](http://docs.puppetlabs.com/guides/templating.html) for - more information. Note that if multiple template strings are specified, their + "Evaluate a template string and return its value. See + [the templating docs](http://docs.puppetlabs.com/guides/templating.html) for + more information. Note that if multiple template strings are specified, their output is all concatenated and returned as the output of the function.") do |vals| - + require 'erb' vals.collect do |string| diff --git a/lib/puppet/parser/functions/reduce.rb b/lib/puppet/parser/functions/reduce.rb new file mode 100644 index 000000000..0cc22bbd4 --- /dev/null +++ b/lib/puppet/parser/functions/reduce.rb @@ -0,0 +1,74 @@ +Puppet::Parser::Functions::newfunction( +:reduce, +:type => :rvalue, +:arity => -2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of entries from the first + argument (_the collection_) and returns the last result of the invocation of the parameterized block. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the last + a parameterized block as produced by the puppet syntax: + + $a.reduce |$memo, $x| { ... } + + When the first argument is an Array, the block is called with each entry in turn. When the first argument + is a hash each entry is converted to an array with `[key, value]` before being fed to the block. An optional + 'start memo' value may be supplied as an argument between the array/hash and mandatory block. + + If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second + elements of the collection, and if the collection has fewer than 2 elements, the first + element is produced as the result of the reduction without invocation of the block. + + On each subsequent invocations, the produced value of the invoked parameterized block is given as the memo in the + next invocation. + + *Examples* + + # Reduce an array + $a = [1,2,3] + $a.reduce |$memo, $entry| { $memo + $entry } + #=> 6 + + # Reduce hash values + $a = {a => 1, b => 2, c => 3} + $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } + #=> [sum, 6] + + It is possible to provide a starting 'memo' as an argument. + + *Examples* + # Reduce an array + $a = [1,2,3] + $a.reduce(4) |$memo, $entry| { $memo + $entry } + #=> 10 + + # Reduce hash values + $a = {a => 1, b => 2, c => 3} + $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } + #=> [sum, 10] + + Since 3.2 + ENDHEREDOC + + require 'puppet/parser/ast/lambda' + case args.length + when 2 + pblock = args[1] + when 3 + pblock = args[2] + else + raise ArgumentError, ("reduce(): wrong number of arguments (#{args.length}; must be 2 or 3)") + end + unless pblock.is_a? Puppet::Parser::AST::Lambda + raise ArgumentError, ("reduce(): wrong argument type (#{args[1].class}; must be a parameterized block.") + end + receiver = args[0] + unless [Array, Hash].include?(receiver.class) + raise ArgumentError, ("collect(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") + end + if args.length == 3 + receiver.reduce(args[1]) {|memo, x| pblock.call(self, memo, x) } + else + receiver.reduce {|memo, x| pblock.call(self, memo, x) } + end +end diff --git a/lib/puppet/parser/functions/reject.rb b/lib/puppet/parser/functions/reject.rb new file mode 100644 index 000000000..2e4f2d50e --- /dev/null +++ b/lib/puppet/parser/functions/reject.rb @@ -0,0 +1,46 @@ +require 'puppet/parser/ast/lambda' + +Puppet::Parser::Functions::newfunction( +:reject, +:type => :rvalue, +:arity => 2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of entries from the first + argument and returns an array with the entires for which the block did *not* evaluate to true. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + a parameterized block as produced by the puppet syntax: + + $a.reject |$x| { ... } + + When the first argument is an Array, the block is called with each entry in turn. When the first argument + is a hash the entry is an array with `[key, value]`. + + The returned filtered object is of the same type as the receiver. + + *Examples* + + # selects all that does not end with berry + $a = ["rasberry", "blueberry", "orange"] + $a.reject |$x| { $x =~ /berry$/ } + + Since 3.2 + ENDHEREDOC + + receiver = args[0] + pblock = args[1] + + raise ArgumentError, ("reject(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + + case receiver + when Array + receiver.reject {|x| pblock.call(self, x) } + when Hash + result = receiver.reject {|x, y| pblock.call(self, [x, y]) } + # Ruby 1.8.7 returns Array + result = Hash[result] unless result.is_a? Hash + result + else + raise ArgumentError, ("reject(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") + end +end diff --git a/lib/puppet/parser/functions/select.rb b/lib/puppet/parser/functions/select.rb new file mode 100644 index 000000000..187aeb270 --- /dev/null +++ b/lib/puppet/parser/functions/select.rb @@ -0,0 +1,46 @@ +require 'puppet/parser/ast/lambda' + +Puppet::Parser::Functions::newfunction( +:select, +:type => :rvalue, +:arity => 2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of entries from the first + argument and returns an array with the entires for which the block evaluates to true. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + a parameterized block as produced by the puppet syntax: + + $a.select |$x| { ... } + + When the first argument is an Array, the block is called with each entry in turn. When the first argument + is a hash the entry is an array with `[key, value]`. + + The returned filtered object is of the same type as the receiver. + + *Examples* + + # selects all that end with berry + $a = ["raspberry", "blueberry", "orange"] + $a.select |$x| { $x =~ /berry$/ } + + Since 3.2 + ENDHEREDOC + + receiver = args[0] + pblock = args[1] + + raise ArgumentError, ("select(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + + case receiver + when Array + receiver.select {|x| pblock.call(self, x) } + when Hash + result = receiver.select {|x, y| pblock.call(self, [x, y]) } + # Ruby 1.8.7 returns Array + result = Hash[result] unless result.is_a? Hash + result + else + raise ArgumentError, ("select(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") + end +end diff --git a/lib/puppet/parser/functions/slice.rb b/lib/puppet/parser/functions/slice.rb new file mode 100644 index 000000000..41b693dd3 --- /dev/null +++ b/lib/puppet/parser/functions/slice.rb @@ -0,0 +1,96 @@ +Puppet::Parser::Functions::newfunction( +:slice, +:type => :rvalue, +:arity => -2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first + argument and returns the first argument, or if no block is given returns a new array with a concatenation of + the slices. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + the number of elements to include in each slice. The optional third argument should be a + a parameterized block as produced by the puppet syntax: + + |$x| { ... } + + The parameterized block should have either one parameter (receiving an array with the slice), or the same number + of parameters as specified by the slice size (each parameter receiving its part of the slice). + In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining + elements. When the block has multiple parameters, excess parameters are set to :undef for an array, and to + empty arrays for a Hash. + + $a.slice(2) |$first, $second| { ... } + + When the first argument is a Hash, each key,value entry is counted as one, e.g, a slice size of 2 will produce + an array of two arrays with key, value. + + $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } + $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } + + When called without a block, the function produces a concatenated result of the slices. + + slice($[1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]] + + Since 3.2 + ENDHEREDOC + require 'puppet/parser/ast/lambda' + require 'puppet/parser/scope' + + def each_Common(o, slice_size, filler, scope, pblock) + serving_size = pblock ? pblock.parameter_count : 1 + if serving_size == 0 + raise ArgumentError, "Block must define at least one parameter." + end + unless serving_size == 1 || serving_size == slice_size + raise ArgumentError, "Block must define one parameter, or the same number of parameters as the given size of the slice (#{slice_size})." + end + enumerator = o.each_slice(slice_size) + result = [] + if serving_size == 1 + ((o.size.to_f / slice_size).ceil).times do + if pblock + pblock.call(scope, enumerator.next) + else + result << enumerator.next + end + end + else + ((o.size.to_f / slice_size).ceil).times do + a = enumerator.next + if a.size < serving_size + a = a.dup.fill(filler, a.length...serving_size) + end + pblock.call(scope, *a) + end + end + if pblock + o + else + result + end + end + raise ArgumentError, ("slice(): wrong number of arguments (#{args.length}; must be 2 or 3)") unless args.length == 2 || args.length == 3 + if args.length >= 2 + begin + slice_size = Puppet::Parser::Scope.number?(args[1]) + rescue + raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") + end + end + raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") unless slice_size + raise ArgumentError, ("slice(): wrong argument value: #{slice_size}; is not an positive integer number > 0") unless slice_size.is_a?(Fixnum) && slice_size > 0 + receiver = args[0] + + # the block is optional, ok if nil, function then produces an array + pblock = args[2] + raise ArgumentError, ("slice(): wrong argument type (#{args[2].class}; must be a parameterized block.") unless pblock.is_a?(Puppet::Parser::AST::Lambda) || args.length == 2 + + case receiver + when Array + each_Common(receiver, slice_size, :undef, self, pblock) + when Hash + each_Common(receiver, slice_size, [], self, pblock) + else + raise ArgumentError, ("slice(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") + end +end diff --git a/lib/puppet/parser/functions/template.rb b/lib/puppet/parser/functions/template.rb index d9b48408e..0bd5a5424 100644 --- a/lib/puppet/parser/functions/template.rb +++ b/lib/puppet/parser/functions/template.rb @@ -1,8 +1,8 @@ Puppet::Parser::Functions::newfunction(:template, :type => :rvalue, :arity => -2, :doc => "Evaluate a template and return its value. See - [the templating docs](http://docs.puppetlabs.com/guides/templating.html) for + [the templating docs](http://docs.puppetlabs.com/guides/templating.html) for more information. - + Note that if multiple templates are specified, their output is all concatenated and returned as the output of the function.") do |vals| vals.collect do |file| diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index f9f1779c7..509f36fb3 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -12,13 +12,13 @@ token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT 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 +token IN UNLESS MODULO prechigh right NOT nonassoc UMINUS left IN MATCH NOMATCH - left TIMES DIV + left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL @@ -32,7 +32,7 @@ program: statements_and_declarations | nil statements_and_declarations: statement_or_declaration { - result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : []) + result = ast AST::BlockExpression, :children => (val[0] ? [val[0]] : []) } | statements_and_declarations statement_or_declaration { if val[1] @@ -489,6 +489,9 @@ expression: rvalue | expression TIMES expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } + | expression MODULO expression { + result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] +} | expression LSHIFT expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } @@ -550,7 +553,7 @@ caseopt: casevalues COLON LBRACE statements RBRACE { AST::CaseOpt, :value => val[0], - :statements => ast(AST::ASTArray) + :statements => ast(AST::BlockExpression) ) } diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index abb860ca6..cfaa34d7d 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -20,6 +20,15 @@ class Puppet::Parser::Lexer attr_accessor :line, :indefine alias :indefine? :indefine + # Returns the position on the line. + # This implementation always returns nil. It is here for API reasons in Puppet::Error + # which needs to support both --parser current, and --parser future. + # + def pos + # Make the lexer comply with newer API. It does not produce a pos... + nil + end + def lex_error msg raise Puppet::LexError.new(msg) end @@ -155,6 +164,7 @@ class Puppet::Parser::Lexer '-' => :MINUS, '/' => :DIV, '*' => :TIMES, + '%' => :MODULO, '<<' => :LSHIFT, '>>' => :RSHIFT, '=~' => :MATCH, diff --git a/lib/puppet/parser/methods.rb b/lib/puppet/parser/methods.rb new file mode 100644 index 000000000..af19b2a03 --- /dev/null +++ b/lib/puppet/parser/methods.rb @@ -0,0 +1,69 @@ +require 'puppet/util/autoload' +require 'puppet/parser/scope' +require 'puppet/parser/functions' +require 'monitor' + +# A module for handling finding and invoking methods (functions invokable as a method). +# A method call on the form: +# +# $a.meth(1,2,3] {|...| ...} +# +# will lookup a function called 'meth' and call it with the arguments ($a, 1, 2, 3, <lambda>) +# +# @see Puppet::Parser::AST::Lambda +# @see Puppet::Parser::AST::MethodCall +# +# @api public +# @since 3.2 +# +module Puppet::Parser::Methods + Environment = Puppet::Node::Environment + # Represents an invokable method configured to be invoked for a given object. + # + class Method + def initialize(receiver, obj, method_name, rvalue) + @receiver = receiver + @o = obj + @method_name = method_name + @rvalue = rvalue + end + + # Invoke this method's function in the given scope with the given arguments and parameterized block. + # A method call on the form: + # + # $a.meth(1,2,3) {|...| ...} + # + # results in the equivalent: + # + # meth($a, 1, 2, 3, {|...| ... }) + # + # @param scope [Puppet::Parser::Scope] the scope the call takes place in + # @param args [Array<Object>] arguments 1..n to pass to the function + # @param pblock [Puppet::Parser::AST::Lambda] optional parameterized block to pass as the last argument + # to the called function + # + def invoke(scope, args=[], pblock=nil) + arguments = [@o] + args + arguments << pblock if pblock + @receiver.send(@method_name, arguments) + end + + # @return [Boolean] whether the method function produces an rvalue or not. + def is_rvalue? + @rvalue + end + end + + class << self + include Puppet::Util + end + + # Finds a function and returns an instance of Method configured to perform invocation. + # @return [Method, nil] configured method or nil if method not found + def self.find_method(scope, receiver, name) + fname = Puppet::Parser::Functions.function(name) + rvalue = Puppet::Parser::Functions.rvalue?(name) + return Method.new(scope, receiver, fname, rvalue) if fname + nil + end +end diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index 3c620d627..4a472c991 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -1,6 +1,6 @@ # # DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.8 +# This file is automatically generated by Racc 1.4.9 # from Racc grammer file "". # @@ -21,7 +21,7 @@ module Puppet module Parser class Parser < Racc::Parser -module_eval(<<'...end grammar.ra/module_eval...', 'grammar.ra', 794) +module_eval(<<'...end grammar.ra/module_eval...', 'grammar.ra', 797) # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' @@ -34,101 +34,109 @@ require 'puppet/parser/parser_support' ##### State transition tables begin ### clist = [ -'9,13,167,168,103,155,370,103,282,105,173,281,293,53,371,142,146,54,274', -'313,292,-177,106,154,137,139,143,145,40,94,48,1,314,10,12,174,21,29', -'35,357,44,49,2,9,13,15,102,138,141,34,317,148,149,132,133,135,136,-176', -'140,144,33,9,13,305,302,134,8,98,274,299,99,40,350,48,1,367,10,12,366', -'21,29,35,337,44,49,2,9,13,15,33,296,297,34,396,64,326,296,297,29,222', -'9,13,49,33,9,13,15,161,72,8,34,53,-112,325,40,54,48,1,343,10,12,33,21', -'29,35,154,44,49,2,9,13,15,155,119,-196,34,394,64,160,90,179,29,75,-123', -'94,49,33,-183,154,15,154,178,8,34,9,13,183,40,309,48,1,340,10,12,33', -'21,29,35,308,44,49,2,9,13,15,179,277,322,34,392,274,275,98,306,86,99', -'178,142,146,33,304,183,160,84,85,8,137,139,143,145,40,94,48,1,33,10', -'12,53,21,29,35,54,44,49,2,9,13,15,15,138,141,34,302,148,149,132,133', -'135,136,334,140,144,33,9,13,132,133,134,8,98,140,144,99,40,291,48,1', -'134,10,12,286,21,29,35,-130,44,49,2,9,13,15,33,335,53,34,271,64,54,179', -'179,29,222,274,360,49,33,140,144,15,178,178,8,34,134,183,183,40,367', -'48,1,366,10,12,33,21,29,35,283,44,49,2,9,13,15,-178,72,33,34,33,-130', -'-130,-130,-130,249,148,149,132,133,33,9,13,140,144,295,8,342,296,297', -'134,205,206,208,189,191,64,195,197,200,239,243,248,207,247,232,9,13', -'15,225,199,202,244,347,64,132,133,188,29,222,140,144,49,33,9,13,15,134', -'190,194,34,140,144,105,40,172,48,1,134,10,12,33,21,29,35,306,44,49,2', -'9,13,15,123,354,315,34,117,64,274,275,-182,29,222,-177,-180,49,33,9', -'13,15,300,72,8,34,274,299,-178,40,332,48,1,363,10,12,33,21,29,35,-122', -'44,49,2,9,13,15,-181,364,338,34,381,64,274,299,331,29,75,-179,-176,49', -'33,372,-183,15,86,375,8,34,376,123,249,40,379,48,1,121,10,12,33,21,29', -'35,119,44,49,2,9,13,15,330,382,117,34,329,-178,383,9,13,-179,109,113', -'-227,-196,33,-40,-40,-40,-40,107,8,59,60,61,57,40,385,48,1,112,10,12', -'-129,21,29,35,92,44,49,2,64,388,15,-184,29,75,34,390,49,9,13,302,15', -'72,117,78,34,33,-96,9,13,-179,56,8,397,398,,33,,9,13,65,,72,,78,-44', -'-44,-44,-44,,,64,,,,29,75,,,49,65,64,53,15,68,29,129,34,44,49,83,64', -',15,,29,75,34,33,49,,,,15,68,9,13,34,33,72,83,78,,,,9,13,,33,72,,78', -',,,9,13,65,,72,,78,-38,-38,-38,-38,,65,64,,,,29,75,,,49,65,64,,15,68', -'29,75,34,,49,83,64,,15,68,29,75,34,33,49,83,,,15,68,9,13,34,33,72,83', -'78,,284,148,149,132,133,33,,,140,144,142,146,,,65,134,,,,137,139,143', -'145,,,64,,,,29,75,,,49,,9,13,15,68,72,,34,138,141,83,,148,149,132,133', -'135,136,33,140,144,9,13,,,72,134,78,,,,9,13,,64,72,,78,29,222,,,49,65', -',,15,,,,34,,,65,64,,,,29,75,,33,49,,64,,15,68,29,75,34,,49,83,,,15,68', -'9,13,34,33,72,83,78,,,,9,13,,33,72,,78,59,60,61,57,,65,,,,,,,,,,65,64', -',,,29,75,,,49,,64,,15,68,29,75,34,,49,83,,,15,68,9,13,34,33,72,83,78', -',,,9,13,,33,72,152,78,,,,9,13,65,278,,,,369,,,,,65,64,,,,29,75,,,49', -',64,,15,68,29,75,34,,49,83,64,,15,68,29,222,34,33,49,83,,,15,,9,13,34', -'33,72,,78,,,,9,13,,33,72,,78,,,,9,13,65,,72,,78,,,,,,65,64,,,,29,75', -',,49,65,64,,15,68,29,75,34,,49,83,64,,15,68,29,75,34,33,49,83,,,15,68', -'9,13,34,33,72,83,78,,,,9,13,,33,72,,78,,,,9,13,65,,72,,78,,,,,,65,64', -',,,29,75,,,49,65,64,,15,68,29,75,34,,49,83,64,,15,68,29,75,34,33,49', -'83,,,15,68,9,13,34,33,72,83,78,,,,9,13,,33,72,,78,,,,9,13,65,,72,,78', -',,,,,65,64,,,,29,75,,,49,65,64,,15,68,29,75,34,,49,83,64,,15,68,29,75', -'34,33,49,83,,,15,68,9,13,34,33,72,83,78,,,,9,13,,33,72,,78,,,,9,13,65', -',72,,78,,,,,,65,64,,,,29,75,,,49,65,64,,15,68,29,75,34,,49,83,64,,15', -'68,29,75,34,33,49,83,,,15,68,9,13,34,33,72,83,78,,,,9,13,,33,,,,,,,9', -'13,65,,72,,78,,,,,,,64,,,,29,75,,,49,65,64,53,15,68,29,129,34,44,49', -'83,64,,15,,29,75,34,33,49,,,,15,68,9,13,34,33,72,83,78,,,,9,13,,33,72', -',78,,,,9,13,65,,72,,78,,,,,,65,64,,,,29,75,,,49,65,64,,15,68,29,75,34', -',49,83,64,,15,68,29,75,34,33,49,83,,,15,68,9,13,34,33,72,83,78,,,,9', -'13,,33,72,,78,,,,9,13,65,,72,,78,,,,,,65,64,,,,29,75,,,49,65,64,,15', -'68,29,75,34,,49,83,64,,15,68,29,75,34,33,49,83,,,15,68,9,13,34,33,72', -'83,78,,,,9,13,,33,72,,78,,,,9,13,65,,,,223,,,,,,65,64,,,,29,75,,,49', -',64,,15,68,29,75,34,,49,83,64,,15,68,29,222,34,33,49,83,,,15,,9,13,34', -'33,72,,78,,,,,,,33,,,,9,13,,,72,65,78,,,,,,,,,,64,,,,29,75,65,226,49', -',,,15,68,,,34,64,,83,,29,75,,,49,,33,,15,68,,,34,9,13,83,,72,152,78', -',,,33,,,,,,,9,13,,,72,65,78,,,,,,,,,,64,,,,29,75,65,,49,,,,15,68,,,34', -'64,,83,,29,75,,,49,,33,,15,68,9,13,34,,72,83,78,,,,9,13,,33,72,,78,', -',,9,13,65,,72,,78,,,,,,65,64,,,,29,75,,,49,65,64,,15,68,29,75,34,,49', -'83,64,,15,68,29,75,34,33,49,83,,,15,68,9,13,34,33,72,83,78,,,230,,,', -'33,,,,,142,146,,,65,,,,,137,139,143,145,,,64,,,,29,75,,,49,,,,15,68', -',,34,138,141,83,,148,149,132,133,135,136,33,140,144,147,,,,,134,,,,142', -'146,,,,,229,,,137,139,143,145,,,142,146,,,,,,,,137,139,143,145,,,,,138', -'141,,,148,149,132,133,135,136,,140,144,,,138,141,,134,148,149,132,133', -'135,136,,140,144,142,146,,,,134,,,,137,139,143,145,,,142,146,,,,,,,253', -'137,139,143,145,,,,,138,141,,,148,149,132,133,135,136,,140,144,,,138', -'141,,134,148,149,132,133,135,136,,140,144,142,146,,,,134,,,,137,139', -'143,145,,,142,146,,,,,,,,137,139,143,145,,,,,,141,,,148,149,132,133', -'135,136,,140,144,,,138,141,,134,148,149,132,133,135,136,,140,144,142', -'146,,,,134,,,,137,139,143,145,,,142,146,,,,,,,,137,139,143,145,,,,,138', -'141,,,148,149,132,133,135,136,,140,144,,,,,,134,148,149,132,133,135', -'136,,140,144,142,146,,,,134,,,,137,139,143,145,,,142,146,,,,,,,,137', -'139,143,145,,,,,,,,,148,149,132,133,135,136,,140,144,,,138,141,,134', -'148,149,132,133,135,136,,140,144,142,146,,,,134,,,,137,139,143,145,', -',142,146,,,,,,,,137,139,143,145,,,,,138,141,,,148,149,132,133,135,136', -',140,144,,,138,141,,134,148,149,132,133,135,136,,140,144,142,146,,,', -'134,,,,137,139,143,145,,,142,146,,,,,,,,137,139,143,145,,,,,138,141', -',,148,149,132,133,135,136,,140,144,,,138,141,,134,148,149,132,133,135', -'136,,140,144,142,146,,,,134,,,,137,139,143,145,,,142,146,,,,,,,,137', -'139,143,145,,,,,138,141,146,,148,149,132,133,135,136,137,140,144,,,', -',146,134,148,149,132,133,135,136,137,140,144,,,,,,134,,146,,148,149', -'132,133,135,136,137,140,144,,,,,146,134,148,149,132,133,135,136,137', -'140,144,,,,,,134,,,,148,149,132,133,135,136,,140,144,,,,,,134,148,149', -'132,133,135,136,,140,144,205,206,208,189,191,134,195,197,200,201,215', -',207,209,,,,,,199,202,204,205,206,208,189,191,,195,197,200,201,215,', -'207,209,,190,194,,,199,202,204,205,206,208,189,191,,195,197,200,201', -'203,,207,209,,190,194,,,199,202,204,205,206,208,189,191,,195,197,200', -'201,203,,207,209,,190,194,,,199,202,204,205,206,208,189,191,,195,197', -'200,201,203,,207,209,,190,194,,,199,202,204,205,206,208,189,191,,195', -'197,200,201,215,,207,209,,190,194,,,199,202,204,148,149,132,133,135', -'136,,140,144,,,,,,134,190,194,148,149,132,133,135,136,,140,144,,,,,', -'134' ] - racc_action_table = arr = ::Array.new(2419, nil) +'35,36,199,198,246,159,-130,86,-112,82,277,357,361,379,356,215,210,159', +'276,-197,360,158,85,158,211,213,212,214,39,248,48,49,267,33,50,158,51', +'37,26,-129,40,46,30,35,36,32,84,217,216,31,398,203,204,206,205,208,209', +'-122,201,202,52,-130,-130,-130,-130,200,38,207,35,36,278,39,86,48,49', +'350,33,50,90,51,37,26,89,40,46,30,35,36,32,-178,94,253,31,257,280,257', +'364,274,273,92,93,215,210,52,257,255,226,336,62,38,211,213,212,214,39', +'338,48,49,254,33,50,339,51,37,26,347,40,46,30,35,36,32,-180,217,216', +'31,310,203,204,206,205,208,209,341,201,202,52,35,36,274,273,200,38,207', +'223,303,-178,39,304,48,49,-177,33,50,185,51,37,26,95,40,46,30,35,36', +'32,190,-184,281,31,397,189,344,90,201,202,226,89,215,210,52,200,357', +'250,32,356,38,211,213,212,214,39,-179,48,49,268,33,50,90,51,37,26,89', +'40,46,30,35,36,32,185,217,216,31,395,203,204,206,205,208,209,190,201', +'202,52,119,189,201,202,200,38,207,201,202,200,39,119,48,49,200,33,50', +'185,51,37,26,267,40,46,30,35,36,32,190,185,90,31,392,189,89,119,265', +'375,118,337,190,120,52,257,280,189,302,-96,38,118,257,263,120,39,-185', +'48,49,52,33,50,52,51,37,26,-123,40,46,30,35,36,32,52,103,118,31,252', +'120,279,206,205,251,257,280,201,202,52,250,243,243,262,200,38,207,257', +'263,52,137,135,139,134,136,80,132,140,141,177,168,240,131,162,35,36', +'82,32,180,142,130,163,271,94,-184,274,273,203,204,206,205,-183,52,-181', +'201,202,62,138,144,-180,353,200,39,207,48,49,354,33,50,330,51,37,26', +'-182,40,46,30,35,36,32,-177,52,-179,31,367,203,204,206,205,157,368,370', +'201,202,52,35,36,371,372,200,38,207,122,327,326,39,110,48,49,109,33', +'50,267,51,37,26,334,40,46,30,35,36,32,91,81,62,31,377,80,90,323,-179', +'37,128,382,40,46,52,35,36,32,383,-180,38,31,385,61,-228,39,387,48,49', +'388,33,50,52,51,37,26,323,40,46,30,35,36,32,319,110,393,31,308,80,90', +'317,305,37,128,158,40,46,52,53,399,32,400,,38,31,,,,39,,48,49,,33,50', +'52,51,37,26,,40,46,30,230,,32,,,,31,,,215,210,56,57,58,59,,,52,211,213', +'212,214,,38,203,204,206,205,208,209,,201,202,56,57,58,59,,200,,207,217', +'216,210,,203,204,206,205,208,209,211,201,202,228,-38,-38,-38,-38,200', +',207,,215,210,-44,-44,-44,-44,,,,211,213,212,214,,,203,204,206,205,208', +'209,,201,202,-40,-40,-40,-40,,200,,207,217,216,,,203,204,206,205,208', +'209,,201,202,229,,,,,200,,207,,215,210,206,205,,,,201,202,211,213,212', +'214,,200,,207,,,,35,36,,,103,,104,,,,,,217,216,,,203,204,206,205,208', +'209,102,201,202,35,36,,,103,200,104,207,80,,,,37,77,,,46,,,,32,101,102', +',31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36', +'100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,', +'104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52', +',37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46', +',,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101', +'102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35', +'36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103', +',104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,', +'52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77', +',,46,35,36,,32,101,102,155,31,,,100,,,35,36,,80,103,52,,37,77,,,46,', +',,32,101,35,36,31,80,103,100,104,37,231,,,46,,52,,32,80,,,31,37,77,102', +',46,35,36,,32,103,52,104,31,80,,35,36,37,77,,,46,358,52,,32,101,102', +',31,,,100,35,36,,,,80,,52,,37,77,,,46,,80,,32,101,37,231,31,,46,100', +',,32,,,,31,52,80,,,,37,231,,,46,52,,,32,35,36,,31,103,,104,,,,,,,,52', +',,35,36,,,103,102,104,,,,,,,,,,80,,,,37,77,102,,46,,,,32,101,,,31,80', +',100,,37,77,,,46,,52,,32,101,35,36,31,,103,100,104,,,,35,36,,52,103', +',104,,,,35,36,102,,103,161,104,,,,,,102,80,,,,37,77,,,46,102,80,,32', +'101,37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101,35,36,31', +'52,103,100,104,,,,35,36,,52,103,,104,,,,35,36,102,,103,,104,,,,,,102', +'80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31', +'52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,78,,-197,,,', +'35,36,102,,103,,104,,,,,,63,80,,,,37,77,,,46,102,80,,32,101,37,77,31', +',46,100,80,,32,,37,77,31,52,46,,,,32,101,35,36,31,52,103,100,104,,,', +'35,36,,52,103,,104,,,,35,36,102,,103,,104,,,,,,102,80,,,,37,77,,,46', +'102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101', +'35,36,31,52,103,100,104,,,,35,36,,52,103,,104,,,,35,36,102,,103,,104', +',,,,,102,80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101', +'37,77,31,52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,103', +',104,,,,35,36,102,,103,,104,,,,,,102,80,,,,37,77,,,46,102,80,,32,101', +'37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101,35,36,31,52', +'103,100,104,,,,35,36,,52,103,161,104,,,,35,36,102,,103,,104,,,,,,102', +'80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31', +'52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,,,,,,,35,36', +'102,,,,234,,,,35,36,,80,103,,,37,77,,,46,,80,,32,101,37,231,31,,46,100', +'80,,32,,37,231,31,52,46,,80,,32,,37,231,31,52,46,35,36,,32,103,,104', +'31,52,,35,36,,,103,,104,,52,,35,36,102,,,,,,,,,,102,80,,,,37,77,,,46', +',80,,32,101,37,77,31,,46,100,80,,32,101,37,231,31,52,46,100,,,32,,35', +'36,31,52,103,,104,,,,,,,52,,,,35,36,,,103,102,104,,,,,,,,,,80,,,,37', +'77,102,260,46,,35,36,32,101,103,,31,80,,100,,37,77,,,46,,52,,32,101', +'35,36,31,,103,100,104,,,,,,80,52,,,37,77,,,46,,102,,32,,210,,31,,,,', +'80,211,,,37,77,52,,46,,,,32,101,215,210,31,,,100,,,,211,213,212,214', +'52,203,204,206,205,208,209,,201,202,210,,,,,200,,207,211,217,216,,,203', +'204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,203', +'204,206,205,208,209,,201,202,,210,,,,200,,207,,211,217,216,,,203,204', +'206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,203,204', +'206,205,208,209,,201,202,,,,,,200,,207,,,217,216,,,203,204,206,205,208', +'209,,201,202,215,210,,,,200,,207,,211,213,212,214,203,204,206,205,208', +'209,,201,202,,,,,,200,,207,,,217,216,,,203,204,206,205,208,209,,201', +'202,215,210,,,,200,,207,301,211,213,212,214,,,,,215,210,,,,,,,,211,213', +'212,214,,,217,216,,,203,204,206,205,208,209,,201,202,,,,,,200,,207,203', +'204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,,', +',,,,,,215,210,,,,,,,,211,213,212,214,,,203,204,206,205,208,209,,201', +'202,,,,,,200,,207,217,216,,,203,204,206,205,208,209,,201,202,215,210', +',,,200,,207,,211,213,212,214,,,,,,,,,,,,,,,,,,,,,216,,,203,204,206,205', +'208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,,,,,,,,,,,,,,', +',,,,,217,216,,,203,204,206,205,208,209,,201,202,215,210,,,,200,,207', +',211,213,212,214,,,,,215,210,,,,,,,,211,213,212,214,,,217,216,,,203', +'204,206,205,208,209,,201,202,,,,,,200,,207,203,204,206,205,208,209,', +'201,202,215,210,,,,200,,207,,211,213,212,214,,,,,,,,,,,,,,,,,,,,217', +'216,,,203,204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213', +'212,214,,,,,,,,,,,,,,,,,,,,217,216,,,203,204,206,205,208,209,,201,202', +',,,,,200,,207,137,135,139,134,136,,132,140,141,148,179,,131,133,,,,', +',142,130,143,137,135,139,134,136,,132,140,141,148,146,,131,133,,138', +'144,,,142,130,143,137,135,139,134,136,,132,140,141,148,146,,131,133', +',138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148,179,,131', +'133,,138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148,179', +',131,133,,138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148', +'146,,131,133,,138,144,,,142,130,143,,,,,,,,,,,,,,,,138,144' ] + racc_action_table = arr = ::Array.new(2588, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| @@ -138,109 +146,113 @@ clist = [ end clist = [ -'0,0,88,88,71,75,320,23,156,23,95,156,177,12,320,88,88,12,227,227,177', -'71,23,75,88,88,88,88,0,174,0,0,227,0,0,95,0,0,0,307,0,0,0,229,229,0', -'23,88,88,0,229,88,88,88,88,88,88,237,88,88,0,230,230,210,236,88,0,174', -'279,279,174,229,294,229,229,317,229,229,317,229,229,229,279,229,229', -'229,390,390,229,174,210,210,229,390,230,235,294,294,230,230,78,78,230', -'229,309,309,230,78,309,229,230,286,243,233,390,286,390,390,286,390,390', -'230,390,390,390,243,390,390,390,388,388,390,129,232,129,390,388,309', -'78,14,179,309,309,226,173,309,390,14,222,309,129,179,390,309,281,281', -'179,388,219,388,388,281,388,388,309,388,388,388,215,388,388,388,380', -'380,388,106,153,231,388,380,153,153,173,212,10,173,106,231,231,388,198', -'106,281,10,10,388,231,231,231,231,380,21,380,380,173,380,380,22,380', -'380,380,22,380,380,380,269,269,380,22,231,231,380,193,231,231,231,231', -'231,231,270,231,231,380,361,361,273,273,231,380,21,273,273,21,269,175', -'269,269,273,269,269,166,269,269,269,315,269,269,269,147,147,269,21,275', -'1,269,147,361,1,102,298,361,361,311,311,361,269,254,254,361,102,298', -'269,361,254,102,298,147,364,147,147,364,147,147,361,147,147,147,158', -'147,147,147,121,121,147,242,121,144,147,140,315,315,315,315,123,257', -'257,257,257,147,370,370,257,257,184,147,285,184,184,257,121,121,121', -'121,121,121,121,121,121,121,121,122,121,121,118,291,291,121,110,121', -'121,121,291,370,272,272,104,370,370,272,272,370,121,223,223,370,272', -'121,121,370,255,255,100,291,92,291,291,255,291,291,370,291,291,291,301', -'291,291,291,26,26,291,87,303,228,291,82,223,228,228,81,223,223,238,79', -'223,291,225,225,223,187,225,291,223,187,187,77,26,251,26,26,313,26,26', -'223,26,26,26,314,26,26,26,342,342,26,74,316,280,26,342,225,280,280,250', -'225,225,73,69,225,26,324,67,225,64,327,26,225,328,55,332,342,333,342', -'342,50,342,342,225,342,342,342,48,342,342,342,248,248,342,249,346,45', -'342,248,36,352,35,35,30,28,35,360,35,342,4,4,4,4,24,342,5,5,5,5,248', -'362,248,248,35,248,248,239,248,248,248,20,248,248,248,35,367,248,247', -'35,35,248,371,35,8,8,374,35,8,246,8,35,248,244,58,58,240,2,248,393,395', -',35,,40,40,8,,40,,40,11,11,11,11,,,8,,,,8,8,,,8,40,58,58,8,8,58,58,8', -'58,58,8,40,,58,,40,40,58,8,40,,,,40,40,44,44,40,58,44,40,44,,,,283,283', -',40,283,,283,,,,167,167,44,,167,,167,47,47,47,47,,283,44,,,,44,44,,', -'44,167,283,,44,44,283,283,44,,283,44,167,,283,283,167,167,283,44,167', -'283,,,167,167,330,330,167,283,330,167,330,,165,258,258,258,258,167,', -',258,258,165,165,,,330,258,,,,165,165,165,165,,,330,,,,330,330,,,330', -',325,325,330,330,325,,330,165,165,330,,165,165,165,165,165,165,330,165', -'165,65,65,,,65,165,65,,,,276,276,,325,276,,276,325,325,,,325,65,,,325', -',,,325,,,276,65,,,,65,65,,325,65,,276,,65,65,276,276,65,,276,65,,,276', -'276,13,13,276,65,13,276,13,,,,155,155,,276,155,,155,16,16,16,16,,13', -',,,,,,,,,155,13,,,,13,13,,,13,,155,,13,13,155,155,13,,155,13,,,155,155', -'154,154,155,13,154,155,154,,,,72,72,,155,72,72,72,,,,318,318,154,154', -',,,318,,,,,72,154,,,,154,154,,,154,,72,,154,154,72,72,154,,72,154,318', -',72,72,318,318,72,154,318,72,,,318,,149,149,318,72,149,,149,,,,148,148', -',318,148,,148,,,,146,146,149,,146,,146,,,,,,148,149,,,,149,149,,,149', -'146,148,,149,149,148,148,149,,148,149,146,,148,148,146,146,148,149,146', -'148,,,146,146,308,308,146,148,308,146,308,,,,145,145,,146,145,,145,', -',,304,304,308,,304,,304,,,,,,145,308,,,,308,308,,,308,304,145,,308,308', -'145,145,308,,145,308,304,,145,145,304,304,145,308,304,145,,,304,304', -'83,83,304,145,83,304,83,,,,84,84,,304,84,,84,,,,85,85,83,,85,,85,,,', -',,84,83,,,,83,83,,,83,85,84,,83,83,84,84,83,,84,83,85,,84,84,85,85,84', -'83,85,84,,,85,85,86,86,85,84,86,85,86,,,,143,143,,85,143,,143,,,,366', -'366,86,,366,,366,,,,,,143,86,,,,86,86,,,86,366,143,,86,86,143,143,86', -',143,86,366,,143,143,366,366,143,86,366,143,,,366,366,90,90,366,143', -'90,366,90,,,,91,91,,366,,,,,,,142,142,90,,142,,142,,,,,,,90,,,,90,90', -',,90,142,91,91,90,90,91,91,90,91,91,90,142,,91,,142,142,91,90,142,,', -',142,142,141,141,142,91,141,142,141,,,,139,139,,142,139,,139,,,,138', -'138,141,,138,,138,,,,,,139,141,,,,141,141,,,141,138,139,,141,141,139', -'139,141,,139,141,138,,139,139,138,138,139,141,138,139,,,138,138,103', -'103,138,139,103,138,103,,,,293,293,,138,293,,293,,,,137,137,103,,137', -',137,,,,,,293,103,,,,103,103,,,103,137,293,,103,103,293,293,103,,293', -'103,137,,293,293,137,137,293,103,137,293,,,137,137,136,136,137,293,136', -'137,136,,,,135,135,,137,135,,135,,,,109,109,136,,,,109,,,,,,135,136', -',,,136,136,,,136,,135,,136,136,135,135,136,,135,136,109,,135,135,109', -'109,135,136,109,135,,,109,,292,292,109,135,292,,292,,,,,,,109,,,,112', -'112,,,112,292,112,,,,,,,,,,292,,,,292,292,112,112,292,,,,292,292,,,292', -'112,,292,,112,112,,,112,,292,,112,112,,,112,113,113,112,,113,113,113', -',,,112,,,,,,,134,134,,,134,113,134,,,,,,,,,,113,,,,113,113,134,,113', -',,,113,113,,,113,134,,113,,134,134,,,134,,113,,134,134,133,133,134,', -'133,134,133,,,,117,117,,134,117,,117,,,,132,132,133,,132,,132,,,,,,117', -'133,,,,133,133,,,133,132,117,,133,133,117,117,133,,117,133,132,,117', -'117,132,132,117,133,132,117,,,132,132,68,68,132,117,68,132,68,,,116', -',,,132,,,,,116,116,,,68,,,,,116,116,116,116,,,68,,,,68,68,,,68,,,,68', -'68,,,68,116,116,68,,116,116,116,116,116,116,68,116,116,66,,,,,116,,', -',66,66,,,,,114,,,66,66,66,66,,,114,114,,,,,,,,114,114,114,114,,,,,66', -'66,,,66,66,66,66,66,66,,66,66,,,114,114,,66,114,114,114,114,114,114', -',114,114,170,170,,,,114,,,,170,170,170,170,,,131,131,,,,,,,131,131,131', -'131,131,,,,,170,170,,,170,170,170,170,170,170,,170,170,,,131,131,,170', -'131,131,131,131,131,131,,131,131,260,260,,,,131,,,,260,260,260,260,', -',355,355,,,,,,,,355,355,355,355,,,,,,260,,,260,260,260,260,260,260,', -'260,260,,,355,355,,260,355,355,355,355,355,355,,355,355,358,358,,,,355', -',,,358,358,358,358,,,349,349,,,,,,,,349,349,349,349,,,,,358,358,,,358', -'358,358,358,358,358,,358,358,,,,,,358,349,349,349,349,349,349,,349,349', -'348,348,,,,349,,,,348,348,348,348,,,341,341,,,,,,,,341,341,341,341,', -',,,,,,,348,348,348,348,348,348,,348,348,,,341,341,,348,341,341,341,341', -'341,341,,341,341,336,336,,,,341,,,,336,336,336,336,,,164,164,,,,,,,', -'164,164,164,164,,,,,336,336,,,336,336,336,336,336,336,,336,336,,,164', -'164,,336,164,164,164,164,164,164,,164,164,163,163,,,,164,,,,163,163', -'163,163,,,377,377,,,,,,,,377,377,377,377,,,,,163,163,,,163,163,163,163', -'163,163,,163,163,,,377,377,,163,377,377,377,377,377,377,,377,377,151', -'151,,,,377,,,,151,151,151,151,,,263,263,,,,,,,,263,263,263,263,,,,,151', -'151,265,,151,151,151,151,151,151,265,151,151,,,,,264,151,263,263,263', -'263,263,263,264,263,263,,,,,,263,,267,,265,265,265,265,265,265,267,265', -'265,,,,,261,265,264,264,264,264,264,264,261,264,264,,,,,,264,,,,267', -'267,267,267,267,267,,267,267,,,,,,267,261,261,261,261,261,261,,261,261', -'107,107,107,107,107,261,107,107,107,107,107,,107,107,,,,,,107,107,107', -'306,306,306,306,306,,306,306,306,306,306,,306,306,,107,107,,,306,306', -'306,105,105,105,105,105,,105,105,105,105,105,,105,105,,306,306,,,105', -'105,105,302,302,302,302,302,,302,302,302,302,302,,302,302,,105,105,', -',302,302,302,326,326,326,326,326,,326,326,326,326,326,,326,326,,302', -'302,,,326,326,326,188,188,188,188,188,,188,188,188,188,188,,188,188', -',326,326,,,188,188,188,259,259,259,259,259,259,,259,259,,,,,,259,188', -'188,268,268,268,268,268,268,,268,268,,,,,,268' ] - racc_action_check = arr = ::Array.new(2419, nil) +'0,0,97,97,115,77,262,28,168,28,186,354,313,345,354,97,97,128,186,128', +'313,168,28,77,97,97,97,97,0,115,0,0,178,0,0,128,0,0,0,177,0,0,0,391', +'391,0,28,97,97,0,391,97,97,97,97,97,97,254,97,97,0,262,262,262,262,97', +'0,97,304,304,191,391,68,391,391,304,391,391,49,391,391,391,49,391,391', +'391,2,2,391,68,33,153,391,259,259,315,315,191,191,33,33,153,153,391', +'154,154,304,259,175,391,153,153,153,153,2,263,2,2,154,2,2,264,2,2,2', +'275,2,2,2,229,229,2,173,153,153,2,229,153,153,153,153,153,153,266,153', +'153,2,104,104,275,275,153,2,153,104,222,171,229,222,229,229,170,229', +'229,84,229,229,229,34,229,229,229,383,383,229,84,34,195,229,383,84,269', +'29,288,288,104,29,195,195,229,288,310,270,29,310,229,195,195,195,195', +'383,169,383,383,166,383,383,50,383,383,383,50,383,383,383,382,382,383', +'185,195,195,383,382,195,195,195,195,195,195,185,195,195,383,51,185,290', +'290,195,383,195,289,289,290,382,248,382,382,289,382,382,272,382,382', +'382,165,382,382,382,372,372,382,272,85,326,382,372,272,326,246,164,326', +'51,261,85,51,382,261,261,85,221,163,382,248,221,221,248,372,162,372', +'372,201,372,372,51,372,372,372,155,372,372,372,81,81,372,248,81,246', +'372,149,246,192,287,287,146,192,192,287,287,372,145,114,113,160,287', +'372,287,160,160,246,81,81,81,81,81,81,81,81,81,81,81,112,81,81,306,306', +'87,81,83,81,81,81,181,80,79,181,181,292,292,292,292,76,81,75,292,292', +'73,81,81,71,307,292,306,292,306,306,309,306,306,249,306,306,306,69,306', +'306,306,319,319,306,67,202,66,306,319,291,291,291,291,64,320,321,291', +'291,306,55,55,323,324,291,306,291,53,245,244,319,48,319,319,41,319,319', +'343,319,319,319,255,319,319,319,327,327,319,30,27,25,319,327,55,55,243', +'23,55,55,357,55,55,319,60,60,55,360,22,319,55,362,21,364,327,366,327', +'327,369,327,327,55,327,327,327,370,327,327,327,228,228,327,241,240,376', +'327,228,60,60,235,225,60,60,231,60,60,327,1,394,60,396,,327,60,,,,228', +',228,228,,228,228,60,228,228,228,,228,228,228,108,,228,,,,228,,,108', +'108,20,20,20,20,,,228,108,108,108,108,,228,294,294,294,294,294,294,', +'294,294,19,19,19,19,,294,,294,108,108,296,,108,108,108,108,108,108,296', +'108,108,105,5,5,5,5,108,,108,,105,105,9,9,9,9,,,,105,105,105,105,,,296', +'296,296,296,296,296,,296,296,7,7,7,7,,296,,296,105,105,,,105,105,105', +'105,105,105,,105,105,107,,,,,105,,105,,107,107,286,286,,,,286,286,107', +'107,107,107,,286,,286,,,,204,204,,,204,,204,,,,,,107,107,,,107,107,107', +'107,107,107,204,107,107,36,36,,,36,107,36,107,204,,,,204,204,,,204,', +',,204,204,36,,204,38,38,204,,38,,38,,36,,204,,36,36,,,36,,,,36,36,38', +',36,39,39,36,,39,,39,,38,,36,,38,38,,,38,,,,38,38,39,,38,40,40,38,,40', +',40,,39,,38,,39,39,,,39,,,,39,39,40,,39,214,214,39,,214,,214,,40,,39', +',40,40,,,40,,,,40,40,214,,40,213,213,40,,213,,213,,214,,40,,214,214', +',,214,,,,214,214,213,,214,212,212,214,,212,,212,,213,,214,,213,213,', +',213,,,,213,213,212,,213,211,211,213,,211,,211,,212,,213,,212,212,,', +'212,,,,212,212,211,,212,210,210,212,,210,,210,,211,,212,,211,211,,,211', +',,,211,211,210,,211,209,209,211,,209,,209,,210,,211,,210,210,,,210,', +',,210,210,209,,210,356,356,210,,356,,356,,209,,210,,209,209,,,209,,', +',209,209,356,,209,63,63,209,,63,,63,,356,,209,,356,356,,,356,361,361', +',356,356,63,63,356,,,356,,,317,317,,63,317,356,,63,63,,,63,,,,63,63', +'208,208,63,361,208,63,208,361,361,,,361,,63,,361,317,,,361,317,317,208', +',317,207,207,,317,207,361,207,317,208,,311,311,208,208,,,208,311,317', +',208,208,207,,208,,,208,363,363,,,,207,,208,,207,207,,,207,,311,,207', +'207,311,311,207,,311,207,,,311,,,,311,207,363,,,,363,363,,,363,311,', +',363,305,305,,363,305,,305,,,,,,,,363,,,206,206,,,206,305,206,,,,,,', +',,,305,,,,305,305,206,,305,,,,305,305,,,305,206,,305,,206,206,,,206', +',305,,206,206,205,205,206,,205,206,205,,,,215,215,,206,215,,215,,,,78', +'78,205,,78,78,78,,,,,,215,205,,,,205,205,,,205,78,215,,205,205,215,215', +'205,,215,205,78,,215,215,78,78,215,205,78,215,,,78,78,203,203,78,215', +'203,78,203,,,,371,371,,78,371,,371,,,,200,200,203,,200,,200,,,,,,371', +'203,,,,203,203,,,203,200,371,,203,203,371,371,203,,371,203,200,,371', +'371,200,200,371,203,200,371,,,200,200,199,199,200,371,199,200,199,,', +',26,26,,200,26,,26,,,,251,251,199,,251,,251,,,,,,26,199,,,,199,199,', +',199,251,26,,199,199,26,26,199,,26,199,251,,26,,251,251,26,199,251,', +',,251,251,86,86,251,26,86,251,86,,,,252,252,,251,252,,252,,,,92,92,86', +',92,,92,,,,,,252,86,,,,86,86,,,86,92,252,,86,86,252,252,86,,252,86,92', +',252,252,92,92,252,86,92,252,,,92,92,93,93,92,252,93,92,93,,,,94,94', +',92,94,,94,,,,95,95,93,,95,,95,,,,,,94,93,,,,93,93,,,93,95,94,,93,93', +'94,94,93,,94,93,95,,94,94,95,95,94,93,95,94,,,95,95,216,216,95,94,216', +'95,216,,,,100,100,,95,100,,100,,,,101,101,216,,101,,101,,,,,,100,216', +',,,216,216,,,216,101,100,,216,216,100,100,216,,100,216,101,,100,100', +'101,101,100,216,101,100,,,101,101,102,102,101,100,102,101,102,,,,103', +'103,,101,103,103,103,,,,256,256,102,,256,,256,,,,,,103,102,,,,102,102', +',,102,256,103,,102,102,103,103,102,,103,102,256,,103,103,256,256,103', +'102,256,103,,,256,256,217,217,256,103,217,256,217,,,,234,234,,256,,', +',,,,109,109,217,,,,109,,,,265,265,,217,265,,,217,217,,,217,,234,,217', +'217,234,234,217,,234,217,109,,234,,109,109,234,217,109,,265,,109,,265', +'265,109,234,265,276,276,,265,276,,276,265,109,,277,277,,,277,,277,,265', +',230,230,276,,,,,,,,,,277,276,,,,276,276,,,276,,277,,276,276,277,277', +'276,,277,276,230,,277,277,230,230,277,276,230,277,,,230,,159,159,230', +'277,159,,159,,,,,,,230,,,,158,158,,,158,159,158,,,,,,,,,,159,,,,159', +'159,158,158,159,,157,157,159,159,157,,159,158,,159,,158,158,,,158,,159', +',158,158,62,62,158,,62,158,62,,,,,,157,158,,,157,157,,,157,,62,,157', +',298,,157,,,,,62,298,,,62,62,157,,62,,,,62,62,335,335,62,,,62,,,,335', +'335,335,335,62,298,298,298,298,298,298,,298,298,295,,,,,298,,298,295', +'335,335,,,335,335,335,335,335,335,,335,335,390,390,,,,335,,335,,390', +'390,390,390,295,295,295,295,295,295,,295,295,,297,,,,295,,295,,297,390', +'390,,,390,390,390,390,390,390,,390,390,156,156,,,,390,,390,,156,156', +'156,156,297,297,297,297,297,297,,297,297,,,,,,297,,297,,,156,156,,,156', +'156,156,156,156,156,,156,156,194,194,,,,156,,156,,194,194,194,194,293', +'293,293,293,293,293,,293,293,,,,,,293,,293,,,194,194,,,194,194,194,194', +'194,194,,194,194,220,220,,,,194,,194,220,220,220,220,220,,,,,348,348', +',,,,,,,348,348,348,348,,,220,220,,,220,220,220,220,220,220,,220,220', +',,,,,220,,220,348,348,348,348,348,348,,348,348,349,349,,,,348,,348,', +'349,349,349,349,,,,,,,,,333,333,,,,,,,,333,333,333,333,,,349,349,349', +'349,349,349,,349,349,,,,,,349,,349,333,333,,,333,333,333,333,333,333', +',333,333,300,300,,,,333,,333,,300,300,300,300,,,,,,,,,,,,,,,,,,,,,300', +',,300,300,300,300,300,300,,300,300,352,352,,,,300,,300,,352,352,352', +'352,,,,,,,,,,,,,,,,,,,,352,352,,,352,352,352,352,352,352,,352,352,196', +'196,,,,352,,352,,196,196,196,196,,,,,299,299,,,,,,,,299,299,299,299', +',,196,196,,,196,196,196,196,196,196,,196,196,,,,,,196,,196,299,299,299', +'299,299,299,,299,299,193,193,,,,299,,299,,193,193,193,193,,,,,,,,,,', +',,,,,,,,,193,193,,,193,193,193,193,193,193,,193,193,332,332,,,,193,', +'193,,332,332,332,332,,,,,,,,,,,,,,,,,,,,332,332,,,332,332,332,332,332', +'332,,332,332,,,,,,332,,332,268,268,268,268,268,,268,268,268,268,268', +',268,268,,,,,,268,268,268,180,180,180,180,180,,180,180,180,180,180,', +'180,180,,268,268,,,180,180,180,61,61,61,61,61,,61,61,61,61,61,,61,61', +',180,180,,,61,61,61,267,267,267,267,267,,267,267,267,267,267,,267,267', +',61,61,,,267,267,267,82,82,82,82,82,,82,82,82,82,82,,82,82,,267,267', +',,82,82,82,250,250,250,250,250,,250,250,250,250,250,,250,250,,82,82', +',,250,250,250,,,,,,,,,,,,,,,,250,250' ] + racc_action_check = arr = ::Array.new(2588, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| @@ -250,328 +262,338 @@ clist = [ end racc_action_pointer = [ - -2, 227, 494, nil, 428, 434, nil, nil, 523, nil, - 178, 489, -23, 777, 124, nil, 733, nil, nil, nil, - 512, 199, 171, 1, 487, nil, 385, nil, 463, nil, - 462, nil, nil, nil, nil, 481, 458, nil, nil, nil, - 543, nil, nil, nil, 590, 472, nil, 556, 467, nil, - 454, nil, nil, nil, nil, 432, nil, nil, 533, nil, - nil, nil, nil, nil, 445, 720, 1692, 426, 1635, 422, - nil, -2, 844, 421, 410, -1, nil, 391, 98, 379, - nil, 375, 388, 1035, 1045, 1055, 1102, 366, -2, nil, - 1169, 1179, 373, nil, nil, -2, nil, nil, nil, nil, - 363, nil, 244, 1303, 347, 2269, 151, 2225, nil, 1390, - 335, nil, 1454, 1504, 1707, nil, 1638, 1578, 331, nil, - nil, 299, 332, 278, nil, nil, nil, nil, nil, 126, - nil, 1767, 1588, 1568, 1521, 1380, 1370, 1323, 1256, 1246, - 246, 1236, 1189, 1112, 244, 978, 921, 256, 911, 901, - nil, 2112, nil, 169, 834, 787, -1, nil, 286, nil, - nil, nil, nil, 2052, 2007, 660, 213, 610, nil, nil, - 1752, nil, nil, 142, 27, 236, nil, -6, nil, 116, - nil, nil, nil, nil, 277, nil, nil, 401, 2335, nil, - nil, nil, nil, 210, nil, nil, nil, nil, 179, nil, + -2, 490, 84, nil, nil, 507, nil, 539, nil, 517, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 485, + 463, 447, 428, 417, nil, 428, 1304, 425, 1, 146, + 388, nil, nil, 84, 153, nil, 675, nil, 700, 725, + 750, 395, nil, nil, nil, nil, nil, nil, 413, 42, + 171, 231, nil, 411, nil, 402, nil, nil, nil, nil, + 445, 2453, 1832, 950, 386, nil, 368, 366, 66, 359, + nil, 345, nil, 359, nil, 339, 337, -1, 1180, 330, + 346, 299, 2497, 339, 140, 238, 1361, 337, nil, nil, + nil, nil, 1381, 1428, 1438, 1448, nil, -2, nil, nil, + 1505, 1515, 1562, 1572, 145, 561, nil, 615, 507, 1649, + nil, nil, 328, 297, 296, -8, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 11, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 41, nil, 171, nil, nil, 116, nil, nil, nil, 147, - nil, nil, 124, 360, nil, 403, 120, 7, 385, 41, - 59, 170, 131, 72, nil, 75, 52, 34, 378, 485, - 514, nil, 281, 101, 511, nil, 524, 496, 471, 461, - 416, 404, nil, nil, 216, 309, nil, 261, 615, 2334, - 1812, 2193, nil, 2127, 2160, 2145, nil, 2178, 2351, 213, - 220, nil, 298, 180, nil, 255, 730, nil, nil, 57, - 428, 152, nil, 600, nil, 317, 75, nil, nil, nil, - nil, 342, 1437, 1313, 47, nil, nil, nil, 245, nil, - nil, 371, 2291, 382, 988, nil, 2247, 30, 968, 102, - nil, 261, nil, 394, 403, 246, 425, 44, 854, nil, - -6, nil, nil, nil, 439, 700, 2313, 443, 446, nil, - 657, nil, 422, 434, nil, nil, 1992, nil, nil, nil, - nil, 1947, 428, nil, nil, nil, 468, nil, 1932, 1887, - nil, nil, 473, nil, nil, 1827, nil, nil, 1872, nil, - 479, 231, 493, nil, 256, nil, 1122, 509, nil, nil, - 317, 515, nil, nil, 515, nil, nil, 2067, nil, nil, - 170, nil, nil, nil, nil, nil, nil, nil, 127, nil, - 84, nil, nil, 531, nil, 532, nil, nil, nil ] + nil, nil, nil, nil, nil, 307, 261, nil, nil, 297, + nil, nil, nil, 84, 93, 274, 1954, 1811, 1785, 1768, + 315, nil, 264, 257, 228, 242, 184, nil, -3, 178, + 138, 133, nil, 109, nil, 102, nil, 16, 20, nil, + 2431, 305, nil, nil, nil, 194, -8, nil, nil, nil, + nil, 48, 303, 2324, 1999, 170, 2262, nil, nil, 1294, + 1247, 228, 328, 1227, 650, 1160, 1113, 1022, 997, 900, + 875, 850, 825, 800, 775, 1170, 1495, 1629, nil, nil, + 2044, 272, 146, nil, nil, 472, nil, nil, 470, 127, + 1721, 462, nil, nil, 1639, 471, nil, nil, nil, nil, + 474, 467, nil, 404, 376, 404, 266, nil, 242, 369, + 2519, 1314, 1371, nil, 34, 400, 1582, nil, nil, 82, + nil, 265, -2, 108, 112, 1659, 134, 2475, 2409, 172, + 180, nil, 226, nil, nil, 100, 1701, 1711, nil, nil, + nil, nil, nil, nil, nil, nil, 579, 256, 123, 180, + 175, 341, 303, 1976, 486, 1886, 540, 1931, 1842, 2279, + 2172, nil, nil, nil, 66, 1096, 341, 360, nil, 366, + 160, 1032, nil, 0, nil, 84, nil, 980, nil, 384, + 374, 388, nil, 391, 399, nil, 227, 427, nil, nil, + nil, nil, 2369, 2127, nil, 1864, nil, nil, nil, nil, + nil, nil, nil, 409, nil, 4, nil, nil, 2061, 2106, + nil, nil, 2217, nil, -20, nil, 925, 435, nil, nil, + 442, 967, 445, 1050, 447, nil, 449, nil, nil, 436, + 433, 1237, 256, nil, nil, nil, 468, nil, nil, nil, + nil, nil, 213, 170, nil, nil, nil, nil, nil, nil, + 1909, 41, nil, nil, 482, nil, 484, nil, nil, nil, + nil ] racc_action_default = [ - -205, -240, -74, -19, -8, -20, -9, -185, -240, -124, - -220, -10, -197, -240, -238, -98, -240, -11, -176, -12, - -240, -240, -240, -177, -39, -13, -1, -181, -240, -129, - -41, -14, -2, -228, -96, -97, -42, -15, -3, -180, - -240, -43, -16, -182, -240, -45, -17, -6, -240, -184, - -240, -18, -7, -197, -196, -205, -75, -49, -240, -46, - -47, -48, -142, -141, -220, -240, -240, -238, -240, -59, - -66, -60, -240, -63, -61, -97, -64, -58, -240, -67, - -62, -68, -65, -240, -240, -240, -240, -205, -240, -125, - -240, -240, -240, -204, -202, -205, -198, -200, -201, -203, - -240, -73, -205, -240, -77, -109, -205, -119, -4, -240, - -53, -54, -240, -240, -240, -134, -240, -240, -189, -186, - -187, -109, -240, -206, -207, -40, -44, -37, -39, -97, - -38, -240, -240, -240, -240, -240, -240, -240, -240, -240, - -240, -240, -240, -240, -240, -240, -240, -240, -240, -240, - -159, -56, -223, -240, -240, -240, -240, -232, -240, -236, - -235, -231, -152, -106, -108, -240, -205, -240, -127, -126, - -107, -36, 399, -240, -240, -240, -214, -240, -220, -205, - -90, -89, -80, -97, -240, -81, -83, -240, -119, -25, - -29, -27, -113, -226, -35, -23, -110, -31, -240, -33, - -32, -114, -21, -112, -34, -28, -26, -22, -30, -24, - -240, -117, -226, -118, -120, -112, -183, -177, -179, -240, - -178, -170, -97, -240, -171, -240, -52, -240, -240, -240, - -240, -240, -240, -94, -92, -240, -226, -100, -101, -114, - -103, -104, -99, -97, -34, -102, -105, -24, -240, -213, - -240, -226, -210, -162, -149, -148, -143, -150, -151, -154, - -161, -156, -144, -160, -158, -155, -145, -157, -153, -5, - -240, -133, -146, -147, -224, -225, -240, -221, -123, -240, - -240, -240, -229, -240, -237, -240, -240, -216, -128, -199, - -215, -240, -240, -240, -240, -78, -86, -85, -240, -225, - -131, -226, -227, -240, -240, -79, -227, -240, -240, -240, - -173, -226, -55, -225, -50, -221, -240, -137, -240, -164, - -240, -168, -239, -188, -240, -95, -109, -240, -240, -191, - -240, -208, -227, -240, -132, -222, -57, -122, -130, -233, - -230, -234, -240, -219, -218, -217, -240, -195, -87, -88, - -84, -82, -240, -111, -71, -115, -121, -72, -116, -175, - -225, -240, -240, -51, -137, -136, -240, -240, -165, -163, - -240, -240, -69, -93, -226, -70, -190, -212, -211, -209, - -240, -193, -194, -76, -174, -172, -135, -138, -240, -169, - -240, -91, -192, -240, -140, -240, -167, -139, -166 ] + -206, -241, -1, -2, -3, -6, -7, -8, -9, -10, + -11, -12, -13, -14, -15, -16, -17, -18, -19, -20, + -241, -39, -41, -42, -43, -45, -97, -241, -178, -241, + -74, -96, -98, -221, -239, -124, -241, -129, -241, -241, + -241, -241, -177, -181, -182, -183, -185, -186, -241, -241, + -198, -241, -229, -241, -4, -241, -46, -47, -48, -49, + -241, -119, -241, -241, -53, -54, -58, -59, -60, -61, + -62, -63, -64, -65, -66, -67, -68, -97, -241, -239, + -221, -109, -109, -77, -206, -206, -241, -241, -73, -197, + -198, -75, -241, -241, -241, -241, -125, -241, -141, -142, + -241, -241, -241, -241, -241, -241, -134, -241, -241, -241, + -187, -188, -190, -206, -206, -206, -199, -201, -202, -203, + -204, -205, 401, -37, -38, -39, -40, -44, -97, -36, + -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, + -31, -32, -33, -34, -35, -227, -112, -113, -114, -241, + -117, -118, -120, -241, -241, -52, -56, -241, -241, -241, + -241, -224, -24, -34, -94, -227, -241, -92, -97, -99, + -100, -101, -102, -103, -104, -105, -110, -114, -227, -112, + -119, -241, -80, -81, -83, -206, -241, -89, -90, -97, + -221, -241, -241, -106, -108, -241, -107, -126, -127, -241, + -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, + -241, -241, -241, -241, -241, -241, -241, -241, -153, -160, + -241, -241, -241, -232, -233, -241, -236, -237, -241, -241, + -241, -97, -171, -172, -241, -241, -178, -179, -180, -184, + -241, -241, -208, -207, -206, -241, -241, -215, -241, -241, + -228, -241, -241, -240, -50, -226, -241, -225, -55, -241, + -123, -241, -222, -226, -241, -95, -241, -228, -109, -241, + -227, -78, -241, -85, -86, -241, -241, -241, -79, -131, + -226, -238, -128, -143, -144, -145, -146, -147, -148, -149, + -150, -151, -152, -154, -155, -156, -157, -158, -159, -161, + -162, -163, -222, -230, -241, -241, -5, -241, -133, -241, + -137, -241, -165, -241, -169, -227, -174, -241, -189, -241, + -241, -227, -211, -214, -241, -217, -241, -241, -200, -216, + -72, -121, -116, -115, -51, -57, -122, -130, -223, -69, + -93, -70, -111, -227, -71, -241, -82, -84, -87, -88, + -231, -234, -235, -132, -137, -136, -241, -241, -164, -166, + -241, -241, -241, -241, -226, -176, -241, -192, -209, -241, + -228, -241, -241, -218, -219, -220, -241, -196, -91, -76, + -135, -138, -241, -241, -170, -173, -175, -191, -210, -212, + -213, -241, -194, -195, -241, -140, -241, -168, -193, -139, + -167 ] racc_goto_table = [ - 23, 26, 55, 27, 115, 111, 224, 252, 234, 108, - 157, 241, 120, 87, 39, 193, 219, 89, 270, 153, - 184, 32, 100, 96, 210, 211, 23, 319, 303, 27, - 101, 236, 93, 212, 104, 365, 125, 58, 318, 356, - 39, 127, 126, 351, 14, 122, 221, 307, 91, 169, - 187, 311, 324, 45, 118, 233, 285, 95, 23, 227, - 228, 27, 175, 361, 290, 250, 130, 18, 251, 125, - 14, 327, 39, 110, 171, 126, 124, 166, 345, 45, - 156, 43, 386, 20, nil, nil, 333, nil, nil, nil, - nil, 23, nil, 18, 27, nil, nil, 294, nil, 130, - 316, 279, 280, nil, nil, 39, 211, 43, 124, 217, - nil, 45, 27, nil, 301, 368, 176, nil, nil, 328, - 310, 238, nil, 39, 27, 18, nil, nil, 41, nil, - 219, 24, nil, nil, nil, 39, 352, 321, nil, 43, - nil, nil, nil, nil, 45, nil, 362, 23, nil, nil, - 27, 262, nil, 216, 41, 266, nil, 24, 18, nil, - nil, 39, 346, nil, nil, nil, nil, nil, nil, 180, - nil, 288, 43, 180, 246, 289, 18, nil, nil, nil, - nil, nil, nil, nil, 93, 93, 41, 287, 237, 128, - 43, 14, nil, nil, nil, 312, 323, nil, nil, nil, - 45, nil, 43, nil, nil, nil, nil, nil, nil, 391, - nil, nil, 373, 339, 18, 241, 378, nil, nil, 41, - 353, nil, 128, 217, 211, 321, 27, nil, 43, 23, - 217, nil, 27, 27, nil, nil, 374, 39, nil, nil, - nil, nil, nil, 39, 39, nil, 180, nil, 23, 245, - nil, 27, 108, nil, nil, nil, nil, nil, 384, 393, - nil, 395, 39, nil, nil, nil, nil, 216, 219, 23, - nil, nil, 27, 14, 216, 41, nil, 389, 24, 359, - nil, nil, 45, 39, nil, nil, nil, 344, nil, nil, - 18, 23, 14, nil, 27, nil, 18, 18, nil, nil, - nil, 45, nil, nil, 43, 39, nil, nil, nil, nil, - 43, 43, nil, 14, nil, 18, nil, nil, 217, nil, - nil, 27, 45, nil, nil, 238, nil, nil, 27, 43, - 387, nil, 39, nil, nil, 14, 18, nil, nil, 39, - nil, nil, 23, 380, 45, 27, nil, nil, nil, nil, - 43, nil, nil, nil, nil, nil, 39, 41, 18, nil, - 24, 217, 216, 108, 27, 180, nil, nil, nil, nil, - 217, nil, 43, 27, nil, 39, 41, 30, 246, 24, - 23, 36, nil, 27, 39, 18, 14, nil, 23, nil, - 23, 27, 237, 27, 39, 45, nil, 41, nil, 43, - 24, nil, 39, 30, 39, 216, 43, 36, nil, 18, - nil, nil, nil, nil, 216, nil, nil, nil, nil, 41, - nil, nil, 24, 43, 14, nil, nil, nil, 18, nil, - nil, nil, 14, 45, 14, 30, nil, 18, nil, 36, - nil, 45, 43, 45, nil, nil, nil, 18, nil, nil, - nil, 43, nil, 245, nil, 18, nil, 18, nil, 159, - nil, 43, nil, nil, nil, nil, nil, nil, 30, 43, - 41, 43, 36, 24, nil, nil, nil, nil, nil, 181, - nil, nil, nil, 181, nil, nil, 218, nil, nil, nil, - 220, nil, nil, nil, nil, nil, nil, nil, 240, nil, - nil, nil, 242, nil, nil, nil, nil, 66, 41, nil, - nil, 24, 88, nil, nil, nil, 41, nil, 41, 24, - nil, 24, nil, nil, 30, nil, nil, nil, 36, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 114, - nil, nil, nil, 116, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 181, nil, nil, nil, - nil, nil, nil, nil, 131, nil, nil, 150, nil, nil, + 28, 2, 28, 42, 224, 42, 54, 65, 106, 233, + 113, 114, 235, 121, 116, 44, 174, 44, 43, 111, + 43, 249, 167, 96, 307, 309, 3, 322, 145, 87, + 25, 88, 25, 165, 178, 176, 176, 83, 312, 355, + 123, 266, 181, 191, 311, 129, 127, 331, 126, 154, + 24, 127, 24, 126, 269, 28, 346, 124, 42, 232, + 28, 197, 124, 42, 160, 55, 60, 241, 244, 315, + 44, 264, 192, 43, 112, 44, 164, 324, 43, 115, + 245, 171, 363, 380, 170, 25, 329, 188, 188, 221, + 25, 320, 321, 64, 373, 222, 44, 1, nil, 43, + nil, nil, nil, nil, nil, 24, nil, nil, nil, 236, + 24, 175, 42, nil, nil, 366, nil, nil, 34, 359, + 34, nil, nil, 376, 44, nil, nil, 43, nil, nil, + nil, 172, nil, 314, 316, nil, nil, 235, 258, 242, + 242, 247, nil, 275, 259, 261, 345, 270, nil, nil, + nil, nil, nil, nil, 389, nil, nil, nil, nil, nil, + nil, nil, nil, 284, 285, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 394, 396, + nil, nil, nil, nil, nil, nil, 282, nil, 188, nil, + nil, 362, nil, nil, nil, nil, nil, 369, nil, nil, + 174, nil, nil, nil, 351, nil, 340, nil, 121, 328, + 121, 318, nil, nil, 314, nil, nil, nil, nil, 378, + 343, 342, 176, nil, nil, nil, nil, 239, 28, 28, + 236, 42, 42, 42, 236, nil, nil, 42, nil, nil, + nil, nil, nil, 44, 44, 44, 43, 43, 43, 44, + nil, nil, 43, nil, nil, nil, nil, nil, 25, 25, + nil, nil, nil, 386, 384, 171, 235, nil, 170, nil, + 325, nil, nil, nil, nil, 188, nil, nil, 24, 24, + 44, nil, nil, 43, 23, nil, 23, 374, nil, nil, + nil, nil, nil, nil, nil, 175, nil, nil, 365, 45, + nil, 45, nil, nil, nil, nil, 28, nil, nil, 42, + 54, 236, nil, nil, 42, 172, nil, nil, nil, 28, + nil, 44, 42, nil, 43, 381, 44, 28, nil, 43, + 42, nil, nil, nil, 44, nil, 25, 43, nil, 23, + nil, nil, 44, nil, 23, 43, 34, 34, 239, 25, + nil, nil, 239, nil, 45, nil, 24, 25, nil, 45, + nil, 236, nil, 236, 42, 169, 42, nil, 22, 24, + 22, nil, 28, 391, nil, 42, 44, 24, 44, 43, + 45, 43, 28, 28, nil, 42, 42, 44, 227, nil, + 43, 28, nil, 237, 42, 54, nil, 44, 44, nil, + 43, 43, 25, 21, nil, 21, 44, nil, 45, 43, + nil, nil, 25, 25, nil, nil, nil, nil, nil, nil, + nil, 25, 24, 22, 34, nil, nil, nil, 22, 239, + nil, nil, 24, 24, nil, nil, nil, 34, nil, nil, + nil, 24, nil, nil, nil, 34, nil, nil, nil, 173, + nil, nil, 187, 187, nil, nil, nil, nil, 125, nil, + nil, nil, nil, 125, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 238, nil, 239, + nil, 239, nil, nil, nil, nil, nil, nil, nil, nil, + 34, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 34, 34, nil, nil, nil, nil, nil, nil, nil, 34, + nil, nil, 23, 23, 237, nil, nil, nil, 237, nil, + nil, nil, nil, nil, nil, nil, nil, 45, 45, 45, + nil, nil, nil, 45, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 169, + nil, nil, nil, 187, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 45, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 162, 163, 164, 165, nil, nil, nil, 170, + nil, nil, nil, nil, nil, nil, nil, nil, 227, nil, + 23, nil, nil, nil, nil, 237, 22, 22, 238, nil, + nil, nil, 238, 23, nil, 45, nil, nil, nil, nil, + 45, 23, nil, nil, nil, nil, nil, nil, 45, nil, + nil, nil, nil, nil, nil, nil, 45, nil, nil, nil, + nil, 21, 21, 173, nil, 97, nil, 105, 107, 108, + 187, nil, nil, nil, nil, 237, nil, 237, nil, nil, + nil, nil, nil, nil, nil, nil, 23, nil, nil, nil, + 45, 153, 45, nil, nil, nil, 23, 23, nil, nil, + nil, 45, nil, nil, 22, 23, nil, nil, nil, 238, + nil, 45, 45, nil, nil, nil, nil, 22, nil, nil, + 45, 193, 194, 195, 196, 22, nil, nil, nil, 218, + 219, 220, nil, nil, nil, nil, nil, nil, nil, 21, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 218, nil, nil, nil, 220, nil, 30, 218, nil, nil, - 36, 220, nil, nil, nil, nil, 231, nil, nil, nil, - nil, nil, nil, nil, nil, 30, nil, nil, nil, 36, - nil, 254, 255, 256, 257, 258, 259, 260, 261, nil, - 263, 264, 265, nil, 267, 268, 30, 272, 273, nil, - 36, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 159, nil, nil, nil, 88, nil, 30, nil, - nil, nil, 36, nil, nil, 181, nil, nil, nil, nil, + nil, nil, 21, nil, nil, nil, nil, nil, nil, 238, + 21, 238, nil, nil, nil, nil, nil, nil, nil, nil, + 22, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 22, 22, nil, nil, nil, nil, nil, nil, nil, 22, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 218, nil, nil, nil, 220, - nil, nil, 240, nil, nil, nil, 242, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 30, - nil, nil, nil, 36, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 218, nil, - nil, nil, 220, nil, nil, nil, nil, 218, nil, nil, - nil, 220, nil, nil, nil, nil, nil, 30, nil, nil, - nil, 36, nil, nil, nil, 30, nil, 30, nil, 36, - nil, 36, nil, nil, nil, 336, nil, nil, nil, nil, - nil, nil, 341, nil, nil, nil, nil, nil, nil, nil, - nil, 348, 349, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 355, nil, nil, nil, 358, nil, nil, + nil, nil, nil, nil, nil, 21, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 21, 21, nil, nil, nil, + nil, nil, nil, nil, 21, nil, nil, nil, 97, 283, + nil, nil, 286, 287, 288, 289, 290, 291, 292, 293, + 294, 295, 296, 297, 298, 299, 300, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 377, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 332, 333, nil, nil, nil, 335, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 114 ] + nil, nil, nil, nil, nil, 348, 349, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 352, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 107, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 390 ] racc_goto_check = [ - 35, 2, 40, 36, 62, 31, 71, 83, 53, 4, - 86, 37, 73, 40, 38, 43, 69, 60, 5, 29, - 48, 3, 35, 79, 48, 56, 35, 67, 44, 36, - 6, 43, 65, 45, 47, 63, 8, 23, 66, 59, - 38, 22, 10, 49, 55, 75, 70, 44, 23, 61, - 29, 72, 42, 28, 74, 41, 76, 77, 35, 29, - 29, 36, 78, 33, 80, 81, 6, 34, 82, 8, - 55, 44, 38, 30, 22, 10, 3, 75, 84, 28, - 85, 39, 63, 1, nil, nil, 44, nil, nil, nil, - nil, 35, nil, 34, 36, nil, nil, 48, nil, 6, - 5, 29, 29, nil, nil, 38, 56, 39, 3, 35, - nil, 28, 36, nil, 45, 67, 3, nil, nil, 5, - 71, 35, nil, 38, 36, 34, nil, nil, 27, nil, - 69, 24, nil, nil, nil, 38, 44, 69, nil, 39, - nil, nil, nil, nil, 28, nil, 44, 35, nil, nil, - 36, 65, nil, 55, 27, 65, nil, 24, 34, nil, - nil, 38, 5, nil, nil, nil, nil, nil, nil, 34, - nil, 60, 39, 34, 28, 79, 34, nil, nil, nil, - nil, nil, nil, nil, 65, 65, 27, 3, 34, 24, - 39, 55, nil, nil, nil, 31, 73, nil, nil, nil, - 28, nil, 39, nil, nil, nil, nil, nil, nil, 44, - nil, nil, 53, 86, 34, 37, 83, nil, nil, 27, - 56, nil, 24, 35, 56, 69, 36, nil, 39, 35, - 35, nil, 36, 36, nil, nil, 43, 38, nil, nil, - nil, nil, nil, 38, 38, nil, 34, nil, 35, 27, - nil, 36, 4, nil, nil, nil, nil, nil, 71, 5, - nil, 5, 38, nil, nil, nil, nil, 55, 69, 35, - nil, nil, 36, 55, 55, 27, nil, 69, 24, 31, - nil, nil, 28, 38, nil, nil, nil, 40, nil, nil, - 34, 35, 55, nil, 36, nil, 34, 34, nil, nil, - nil, 28, nil, nil, 39, 38, nil, nil, nil, nil, - 39, 39, nil, 55, nil, 34, nil, nil, 35, nil, - nil, 36, 28, nil, nil, 35, nil, nil, 36, 39, - 62, nil, 38, nil, nil, 55, 34, nil, nil, 38, - nil, nil, 35, 2, 28, 36, nil, nil, nil, nil, - 39, nil, nil, nil, nil, nil, 38, 27, 34, nil, - 24, 35, 55, 4, 36, 34, nil, nil, nil, nil, - 35, nil, 39, 36, nil, 38, 27, 25, 28, 24, - 35, 26, nil, 36, 38, 34, 55, nil, 35, nil, - 35, 36, 34, 36, 38, 28, nil, 27, nil, 39, - 24, nil, 38, 25, 38, 55, 39, 26, nil, 34, - nil, nil, nil, nil, 55, nil, nil, nil, nil, 27, - nil, nil, 24, 39, 55, nil, nil, nil, 34, nil, - nil, nil, 55, 28, 55, 25, nil, 34, nil, 26, - nil, 28, 39, 28, nil, nil, nil, 34, nil, nil, - nil, 39, nil, 27, nil, 34, nil, 34, nil, 26, - nil, 39, nil, nil, nil, nil, nil, nil, 25, 39, - 27, 39, 26, 24, nil, nil, nil, nil, nil, 25, - nil, nil, nil, 25, nil, nil, 25, nil, nil, nil, - 26, nil, nil, nil, nil, nil, nil, nil, 25, nil, - nil, nil, 26, nil, nil, nil, nil, 32, 27, nil, - nil, 24, 32, nil, nil, nil, 27, nil, 27, 24, - nil, 24, nil, nil, 25, nil, nil, nil, 26, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 32, - nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 25, nil, nil, nil, - nil, nil, nil, nil, 32, nil, nil, 32, nil, nil, + 35, 2, 35, 34, 86, 34, 4, 31, 62, 71, + 40, 40, 69, 65, 79, 36, 37, 36, 38, 73, + 38, 44, 53, 60, 5, 5, 3, 83, 45, 35, + 28, 6, 28, 43, 43, 56, 56, 47, 67, 63, + 22, 44, 48, 48, 66, 22, 10, 59, 8, 29, + 27, 10, 27, 8, 44, 35, 49, 6, 34, 70, + 35, 61, 6, 34, 29, 23, 23, 75, 75, 72, + 36, 42, 29, 38, 74, 36, 41, 76, 38, 77, + 78, 35, 33, 63, 34, 28, 80, 34, 34, 29, + 28, 81, 82, 30, 84, 85, 36, 1, nil, 38, + nil, nil, nil, nil, nil, 27, nil, nil, nil, 35, + 27, 28, 34, nil, nil, 5, nil, nil, 55, 67, + 55, nil, nil, 5, 36, nil, nil, 38, nil, nil, + nil, 27, nil, 69, 71, nil, nil, 69, 31, 3, + 3, 3, nil, 48, 29, 29, 44, 45, nil, nil, + nil, nil, nil, nil, 83, nil, nil, nil, nil, nil, + nil, nil, nil, 65, 65, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 5, 5, + nil, nil, nil, nil, nil, nil, 60, nil, 34, nil, + nil, 44, nil, nil, nil, nil, nil, 44, nil, nil, + 37, nil, nil, nil, 86, nil, 53, nil, 65, 79, + 65, 73, nil, nil, 69, nil, nil, nil, nil, 44, + 43, 56, 56, nil, nil, nil, nil, 55, 35, 35, + 35, 34, 34, 34, 35, nil, nil, 34, nil, nil, + nil, nil, nil, 36, 36, 36, 38, 38, 38, 36, + nil, nil, 38, nil, nil, nil, nil, nil, 28, 28, + nil, nil, nil, 71, 69, 35, 69, nil, 34, nil, + 3, nil, nil, nil, nil, 34, nil, nil, 27, 27, + 36, nil, nil, 38, 26, nil, 26, 40, nil, nil, + nil, nil, nil, nil, nil, 28, nil, nil, 31, 39, + nil, 39, nil, nil, nil, nil, 35, nil, nil, 34, + 4, 35, nil, nil, 34, 27, nil, nil, nil, 35, + nil, 36, 34, nil, 38, 62, 36, 35, nil, 38, + 34, nil, nil, nil, 36, nil, 28, 38, nil, 26, + nil, nil, 36, nil, 26, 38, 55, 55, 55, 28, + nil, nil, 55, nil, 39, nil, 27, 28, nil, 39, + nil, 35, nil, 35, 34, 26, 34, nil, 25, 27, + 25, nil, 35, 2, nil, 34, 36, 27, 36, 38, + 39, 38, 35, 35, nil, 34, 34, 36, 26, nil, + 38, 35, nil, 26, 34, 4, nil, 36, 36, nil, + 38, 38, 28, 24, nil, 24, 36, nil, 39, 38, + nil, nil, 28, 28, nil, nil, nil, nil, nil, nil, + nil, 28, 27, 25, 55, nil, nil, nil, 25, 55, + nil, nil, 27, 27, nil, nil, nil, 55, nil, nil, + nil, 27, nil, nil, nil, 55, nil, nil, nil, 25, + nil, nil, 25, 25, nil, nil, nil, nil, 24, nil, + nil, nil, nil, 24, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 25, nil, 55, + nil, 55, nil, nil, nil, nil, nil, nil, nil, nil, + 55, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 55, 55, nil, nil, nil, nil, nil, nil, nil, 55, + nil, nil, 26, 26, 26, nil, nil, nil, 26, nil, + nil, nil, nil, nil, nil, nil, nil, 39, 39, 39, + nil, nil, nil, 39, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 26, + nil, nil, nil, 25, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 39, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 26, nil, + 26, nil, nil, nil, nil, 26, 25, 25, 25, nil, + nil, nil, 25, 26, nil, 39, nil, nil, nil, nil, + 39, 26, nil, nil, nil, nil, nil, nil, 39, nil, + nil, nil, nil, nil, nil, nil, 39, nil, nil, nil, + nil, 24, 24, 25, nil, 32, nil, 32, 32, 32, + 25, nil, nil, nil, nil, 26, nil, 26, nil, nil, + nil, nil, nil, nil, nil, nil, 26, nil, nil, nil, + 39, 32, 39, nil, nil, nil, 26, 26, nil, nil, + nil, 39, nil, nil, 25, 26, nil, nil, nil, 25, + nil, 39, 39, nil, nil, nil, nil, 25, nil, nil, + 39, 32, 32, 32, 32, 25, nil, nil, nil, 32, + 32, 32, nil, nil, nil, nil, nil, nil, nil, 24, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, 24, nil, nil, nil, nil, nil, nil, 25, + 24, 25, nil, nil, nil, nil, nil, nil, nil, nil, + 25, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 25, 25, nil, nil, nil, nil, nil, nil, nil, 25, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 24, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 24, 24, nil, nil, nil, + nil, nil, nil, nil, 24, nil, nil, nil, 32, 32, + nil, nil, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 32, 32, 32, 32, nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 25, nil, nil, nil, 26, nil, 25, 25, nil, nil, - 26, 26, nil, nil, nil, nil, 32, nil, nil, nil, - nil, nil, nil, nil, nil, 25, nil, nil, nil, 26, - nil, 32, 32, 32, 32, 32, 32, 32, 32, nil, - 32, 32, 32, nil, 32, 32, 25, 32, 32, nil, - 26, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 26, nil, nil, nil, 32, nil, 25, nil, - nil, nil, 26, nil, nil, 25, nil, nil, nil, nil, + 32, 32, nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 25, nil, nil, nil, 26, - nil, nil, 25, nil, nil, nil, 26, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 25, - nil, nil, nil, 26, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 25, nil, - nil, nil, 26, nil, nil, nil, nil, 25, nil, nil, - nil, 26, nil, nil, nil, nil, nil, 25, nil, nil, - nil, 26, nil, nil, nil, 25, nil, 25, nil, 26, - nil, 26, nil, nil, nil, 32, nil, nil, nil, nil, - nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, - nil, 32, 32, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 32, nil, nil, nil, 32, nil, nil, + nil, nil, nil, nil, nil, 32, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 32 ] + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 32, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 32 ] racc_goto_pointer = [ - nil, 83, 1, 21, -17, -129, 8, nil, -22, nil, - -16, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, -17, 32, 131, 377, 381, 128, 53, -53, - 38, -30, 499, -248, 67, 0, 3, -110, 14, 81, - 1, -66, -181, -90, -165, -74, nil, 11, -82, -255, - nil, nil, nil, -113, nil, 44, -82, nil, nil, -267, - 4, -39, -36, -282, nil, 11, -192, -203, nil, -93, - -63, -103, -172, -36, 6, -10, -110, 36, -33, 2, - -110, -58, -55, -116, -208, 2, -68, nil ] + nil, 97, 1, 26, 4, -204, 2, nil, -7, nil, + -9, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, -15, 46, 403, 368, 284, 50, 30, -14, + 67, -19, 599, -233, 3, 0, 15, -65, 18, 299, + -39, -5, -93, -48, -124, -33, nil, 9, -42, -216, + nil, nil, nil, -59, nil, 118, -46, nil, nil, -203, + -13, -36, -31, -271, nil, -38, -186, -192, nil, -97, + -50, -100, -165, -29, 26, -46, -167, 28, -35, -37, + -162, -152, -151, -216, -232, -9, -100, nil ] racc_goto_default = [ - nil, nil, 269, 182, 38, nil, 47, 52, 4, 6, - 11, 17, 19, 25, 31, 37, 42, 46, 51, 3, - 5, 192, 16, nil, 70, 73, 77, 80, 82, nil, - nil, 63, 151, 276, 69, 71, 74, 76, 79, 81, - 50, nil, nil, nil, nil, nil, 22, nil, nil, 185, - 298, 186, 177, nil, 235, 67, 196, 198, 213, 214, - nil, nil, nil, nil, 62, 7, nil, nil, 320, 28, + nil, nil, 306, 182, 4, nil, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 147, 20, nil, 74, 71, 66, 70, 73, nil, + nil, 98, 156, 256, 67, 68, 69, 72, 75, 76, + 27, nil, nil, nil, nil, nil, 29, nil, nil, 183, + 272, 184, 186, nil, 166, 79, 150, 149, 151, 152, + nil, nil, nil, nil, 99, 47, nil, nil, 313, 41, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 97, nil, nil, nil, nil, nil, nil, 158 ] + 117, nil, nil, nil, nil, nil, nil, 225 ] racc_reduce_table = [ 0, 0, :racc_error, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 71, :_reduce_3, - 2, 71, :_reduce_4, - 1, 74, :_reduce_5, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 1, 90, :_reduce_none, - 3, 89, :_reduce_36, - 3, 89, :_reduce_37, + 1, 71, :_reduce_none, + 1, 71, :_reduce_none, + 1, 72, :_reduce_3, + 2, 72, :_reduce_4, + 1, 75, :_reduce_5, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, @@ -580,204 +602,219 @@ racc_reduce_table = [ 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 3, 90, :_reduce_36, + 3, 90, :_reduce_37, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, - 4, 83, :_reduce_50, - 5, 83, :_reduce_51, - 3, 83, :_reduce_52, - 2, 83, :_reduce_53, - 1, 99, :_reduce_54, - 3, 99, :_reduce_55, - 1, 98, :_reduce_56, - 3, 98, :_reduce_57, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 1, 100, :_reduce_none, - 5, 75, :_reduce_69, - 5, 75, :_reduce_70, - 5, 75, :_reduce_71, - 5, 87, :_reduce_72, - 2, 76, :_reduce_73, - 1, 115, :_reduce_74, - 2, 115, :_reduce_75, - 6, 77, :_reduce_76, - 2, 77, :_reduce_77, - 3, 116, :_reduce_78, - 3, 116, :_reduce_79, - 1, 117, :_reduce_none, - 1, 117, :_reduce_none, - 3, 117, :_reduce_82, - 1, 118, :_reduce_none, - 3, 118, :_reduce_84, - 1, 119, :_reduce_85, - 1, 119, :_reduce_86, - 3, 120, :_reduce_87, - 3, 120, :_reduce_88, - 1, 121, :_reduce_none, - 1, 121, :_reduce_none, - 4, 122, :_reduce_91, - 1, 110, :_reduce_92, - 3, 110, :_reduce_93, - 0, 111, :_reduce_none, - 1, 111, :_reduce_none, - 1, 108, :_reduce_96, - 1, 103, :_reduce_97, - 1, 104, :_reduce_98, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 3, 78, :_reduce_106, - 3, 78, :_reduce_107, - 3, 88, :_reduce_108, - 0, 112, :_reduce_109, - 1, 112, :_reduce_110, - 3, 112, :_reduce_111, - 1, 126, :_reduce_none, - 1, 126, :_reduce_none, - 1, 126, :_reduce_none, - 3, 125, :_reduce_115, - 3, 127, :_reduce_116, - 1, 128, :_reduce_none, - 1, 128, :_reduce_none, - 0, 114, :_reduce_119, - 1, 114, :_reduce_120, - 3, 114, :_reduce_121, - 4, 107, :_reduce_122, - 3, 107, :_reduce_123, - 1, 95, :_reduce_124, - 2, 95, :_reduce_125, - 2, 129, :_reduce_126, - 1, 130, :_reduce_127, - 2, 130, :_reduce_128, - 1, 105, :_reduce_129, - 4, 93, :_reduce_130, - 4, 93, :_reduce_131, - 5, 81, :_reduce_132, - 4, 81, :_reduce_133, - 2, 80, :_reduce_134, - 5, 131, :_reduce_135, - 4, 131, :_reduce_136, - 0, 132, :_reduce_none, - 2, 132, :_reduce_138, - 4, 132, :_reduce_139, - 3, 132, :_reduce_140, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 4, 84, :_reduce_50, + 5, 84, :_reduce_51, + 3, 84, :_reduce_52, + 2, 84, :_reduce_53, + 1, 100, :_reduce_54, + 3, 100, :_reduce_55, + 1, 99, :_reduce_56, + 3, 99, :_reduce_57, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, - 3, 101, :_reduce_143, - 3, 101, :_reduce_144, - 3, 101, :_reduce_145, - 3, 101, :_reduce_146, - 3, 101, :_reduce_147, - 3, 101, :_reduce_148, - 3, 101, :_reduce_149, - 3, 101, :_reduce_150, - 3, 101, :_reduce_151, - 2, 101, :_reduce_152, - 3, 101, :_reduce_153, - 3, 101, :_reduce_154, - 3, 101, :_reduce_155, - 3, 101, :_reduce_156, - 3, 101, :_reduce_157, - 3, 101, :_reduce_158, - 2, 101, :_reduce_159, - 3, 101, :_reduce_160, - 3, 101, :_reduce_161, - 3, 101, :_reduce_162, - 5, 79, :_reduce_163, - 1, 135, :_reduce_164, - 2, 135, :_reduce_165, - 5, 136, :_reduce_166, - 4, 136, :_reduce_167, - 1, 137, :_reduce_168, - 3, 137, :_reduce_169, - 3, 96, :_reduce_170, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 5, 76, :_reduce_69, + 5, 76, :_reduce_70, + 5, 76, :_reduce_71, + 5, 88, :_reduce_72, + 2, 77, :_reduce_73, + 1, 116, :_reduce_74, + 2, 116, :_reduce_75, + 6, 78, :_reduce_76, + 2, 78, :_reduce_77, + 3, 117, :_reduce_78, + 3, 117, :_reduce_79, + 1, 118, :_reduce_none, + 1, 118, :_reduce_none, + 3, 118, :_reduce_82, + 1, 119, :_reduce_none, + 3, 119, :_reduce_84, + 1, 120, :_reduce_85, + 1, 120, :_reduce_86, + 3, 121, :_reduce_87, + 3, 121, :_reduce_88, + 1, 122, :_reduce_none, + 1, 122, :_reduce_none, + 4, 123, :_reduce_91, + 1, 111, :_reduce_92, + 3, 111, :_reduce_93, + 0, 112, :_reduce_none, + 1, 112, :_reduce_none, + 1, 109, :_reduce_96, + 1, 104, :_reduce_97, + 1, 105, :_reduce_98, + 1, 124, :_reduce_none, + 1, 124, :_reduce_none, + 1, 124, :_reduce_none, + 1, 124, :_reduce_none, + 1, 124, :_reduce_none, + 1, 124, :_reduce_none, + 1, 124, :_reduce_none, + 3, 79, :_reduce_106, + 3, 79, :_reduce_107, + 3, 89, :_reduce_108, + 0, 113, :_reduce_109, + 1, 113, :_reduce_110, + 3, 113, :_reduce_111, + 1, 127, :_reduce_none, + 1, 127, :_reduce_none, + 1, 127, :_reduce_none, + 3, 126, :_reduce_115, + 3, 128, :_reduce_116, + 1, 129, :_reduce_none, + 1, 129, :_reduce_none, + 0, 115, :_reduce_119, + 1, 115, :_reduce_120, + 3, 115, :_reduce_121, + 4, 108, :_reduce_122, + 3, 108, :_reduce_123, + 1, 96, :_reduce_124, + 2, 96, :_reduce_125, + 2, 130, :_reduce_126, + 1, 131, :_reduce_127, + 2, 131, :_reduce_128, + 1, 106, :_reduce_129, + 4, 94, :_reduce_130, + 4, 94, :_reduce_131, + 5, 82, :_reduce_132, + 4, 82, :_reduce_133, + 2, 81, :_reduce_134, + 5, 132, :_reduce_135, + 4, 132, :_reduce_136, + 0, 133, :_reduce_none, + 2, 133, :_reduce_138, + 4, 133, :_reduce_139, + 3, 133, :_reduce_140, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 3, 102, :_reduce_143, + 3, 102, :_reduce_144, + 3, 102, :_reduce_145, + 3, 102, :_reduce_146, + 3, 102, :_reduce_147, + 3, 102, :_reduce_148, + 3, 102, :_reduce_149, + 3, 102, :_reduce_150, + 3, 102, :_reduce_151, + 3, 102, :_reduce_152, + 2, 102, :_reduce_153, + 3, 102, :_reduce_154, + 3, 102, :_reduce_155, + 3, 102, :_reduce_156, + 3, 102, :_reduce_157, + 3, 102, :_reduce_158, + 3, 102, :_reduce_159, + 2, 102, :_reduce_160, + 3, 102, :_reduce_161, + 3, 102, :_reduce_162, + 3, 102, :_reduce_163, + 5, 80, :_reduce_164, + 1, 136, :_reduce_165, + 2, 136, :_reduce_166, + 5, 137, :_reduce_167, + 4, 137, :_reduce_168, + 1, 138, :_reduce_169, + 3, 138, :_reduce_170, + 3, 97, :_reduce_171, + 1, 140, :_reduce_none, + 4, 140, :_reduce_173, + 1, 142, :_reduce_none, + 3, 142, :_reduce_175, + 3, 141, :_reduce_176, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, 1, 139, :_reduce_none, - 4, 139, :_reduce_172, - 1, 141, :_reduce_none, - 3, 141, :_reduce_174, - 3, 140, :_reduce_175, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_184, - 1, 138, :_reduce_none, - 1, 142, :_reduce_186, - 1, 143, :_reduce_none, - 3, 143, :_reduce_188, - 2, 82, :_reduce_189, - 6, 84, :_reduce_190, - 5, 84, :_reduce_191, - 7, 85, :_reduce_192, - 6, 85, :_reduce_193, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, + 1, 139, :_reduce_185, + 1, 139, :_reduce_none, + 1, 143, :_reduce_187, + 1, 144, :_reduce_none, + 3, 144, :_reduce_189, + 2, 83, :_reduce_190, + 6, 85, :_reduce_191, + 5, 85, :_reduce_192, + 7, 86, :_reduce_193, 6, 86, :_reduce_194, - 5, 86, :_reduce_195, - 1, 109, :_reduce_196, - 1, 109, :_reduce_197, - 1, 146, :_reduce_198, - 3, 146, :_reduce_199, - 1, 148, :_reduce_200, + 6, 87, :_reduce_195, + 5, 87, :_reduce_196, + 1, 110, :_reduce_197, + 1, 110, :_reduce_198, + 1, 147, :_reduce_199, + 3, 147, :_reduce_200, 1, 149, :_reduce_201, - 1, 149, :_reduce_202, - 1, 149, :_reduce_203, - 1, 149, :_reduce_none, - 0, 72, :_reduce_205, - 0, 150, :_reduce_206, - 1, 144, :_reduce_none, - 3, 144, :_reduce_208, - 4, 144, :_reduce_209, - 1, 151, :_reduce_none, - 3, 151, :_reduce_211, - 3, 152, :_reduce_212, - 1, 152, :_reduce_213, - 1, 147, :_reduce_none, - 2, 147, :_reduce_215, + 1, 150, :_reduce_202, + 1, 150, :_reduce_203, + 1, 150, :_reduce_204, + 1, 150, :_reduce_none, + 0, 73, :_reduce_206, + 0, 151, :_reduce_207, 1, 145, :_reduce_none, - 2, 145, :_reduce_217, - 1, 153, :_reduce_none, - 1, 153, :_reduce_none, - 1, 94, :_reduce_220, - 3, 106, :_reduce_221, - 4, 106, :_reduce_222, - 2, 106, :_reduce_223, - 1, 102, :_reduce_none, - 1, 102, :_reduce_none, - 0, 113, :_reduce_none, - 1, 113, :_reduce_227, - 1, 134, :_reduce_228, - 3, 133, :_reduce_229, - 4, 133, :_reduce_230, - 2, 133, :_reduce_231, + 3, 145, :_reduce_209, + 4, 145, :_reduce_210, + 1, 152, :_reduce_none, + 3, 152, :_reduce_212, + 3, 153, :_reduce_213, + 1, 153, :_reduce_214, + 1, 148, :_reduce_none, + 2, 148, :_reduce_216, + 1, 146, :_reduce_none, + 2, 146, :_reduce_218, + 1, 154, :_reduce_none, 1, 154, :_reduce_none, - 3, 154, :_reduce_233, + 1, 95, :_reduce_221, + 3, 107, :_reduce_222, + 4, 107, :_reduce_223, + 2, 107, :_reduce_224, + 1, 103, :_reduce_none, + 1, 103, :_reduce_none, + 0, 114, :_reduce_none, + 1, 114, :_reduce_228, + 1, 135, :_reduce_229, + 3, 134, :_reduce_230, + 4, 134, :_reduce_231, + 2, 134, :_reduce_232, + 1, 155, :_reduce_none, 3, 155, :_reduce_234, - 1, 156, :_reduce_235, - 1, 156, :_reduce_236, - 4, 124, :_reduce_237, - 1, 97, :_reduce_none, - 4, 97, :_reduce_239 ] + 3, 156, :_reduce_235, + 1, 157, :_reduce_236, + 1, 157, :_reduce_237, + 4, 125, :_reduce_238, + 1, 98, :_reduce_none, + 4, 98, :_reduce_240 ] -racc_reduce_n = 240 +racc_reduce_n = 241 -racc_shift_n = 399 +racc_shift_n = 401 racc_token_table = { false => 0, @@ -848,9 +885,10 @@ racc_token_table = { :IN_EDGE_SUB => 65, :OUT_EDGE_SUB => 66, :IN => 67, - :UNLESS => 68 } + :UNLESS => 68, + :MODULO => 69 } -racc_nt_base = 69 +racc_nt_base = 70 racc_use_result_var = true @@ -940,6 +978,7 @@ Racc_token_to_s_table = [ "OUT_EDGE_SUB", "IN", "UNLESS", + "MODULO", "$start", "program", "statements_and_declarations", @@ -1041,7 +1080,7 @@ Racc_debug_parser = false module_eval(<<'.,.,', 'grammar.ra', 34) def _reduce_3(val, _values, result) - result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : []) + result = ast AST::BlockExpression, :children => (val[0] ? [val[0]] : []) result end @@ -1899,7 +1938,7 @@ module_eval(<<'.,.,', 'grammar.ra', 495) module_eval(<<'.,.,', 'grammar.ra', 498) def _reduce_152(val, _values, result) - result = ast AST::Minus, :value => val[1] + result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end @@ -1907,7 +1946,7 @@ module_eval(<<'.,.,', 'grammar.ra', 498) module_eval(<<'.,.,', 'grammar.ra', 501) def _reduce_153(val, _values, result) - result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] + result = ast AST::Minus, :value => val[1] result end @@ -1955,7 +1994,7 @@ module_eval(<<'.,.,', 'grammar.ra', 516) module_eval(<<'.,.,', 'grammar.ra', 519) def _reduce_159(val, _values, result) - result = ast AST::Not, :value => val[1] + result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end @@ -1963,7 +2002,7 @@ module_eval(<<'.,.,', 'grammar.ra', 519) module_eval(<<'.,.,', 'grammar.ra', 522) def _reduce_160(val, _values, result) - result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] + result = ast AST::Not, :value => val[1] result end @@ -1979,14 +2018,22 @@ module_eval(<<'.,.,', 'grammar.ra', 525) module_eval(<<'.,.,', 'grammar.ra', 528) def _reduce_162(val, _values, result) - result = val[1] + result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 532) +module_eval(<<'.,.,', 'grammar.ra', 531) def _reduce_163(val, _values, result) + result = val[1] + + result + end +.,., + +module_eval(<<'.,.,', 'grammar.ra', 535) + def _reduce_164(val, _values, result) @lexer.commentpop result = ast AST::CaseStatement, :test => val[1], :options => val[3] @@ -1994,15 +2041,15 @@ module_eval(<<'.,.,', 'grammar.ra', 532) end .,., -module_eval(<<'.,.,', 'grammar.ra', 536) - def _reduce_164(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 539) + def _reduce_165(val, _values, result) result = aryfy(val[0]) result end .,., -module_eval(<<'.,.,', 'grammar.ra', 538) - def _reduce_165(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 541) + def _reduce_166(val, _values, result) val[0].push val[1] result = val[0] @@ -2010,8 +2057,8 @@ module_eval(<<'.,.,', 'grammar.ra', 538) end .,., -module_eval(<<'.,.,', 'grammar.ra', 543) - def _reduce_166(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 546) + def _reduce_167(val, _values, result) @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] @@ -2019,30 +2066,30 @@ module_eval(<<'.,.,', 'grammar.ra', 543) end .,., -module_eval(<<'.,.,', 'grammar.ra', 546) - def _reduce_167(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 549) + def _reduce_168(val, _values, result) @lexer.commentpop result = ast( AST::CaseOpt, :value => val[0], - :statements => ast(AST::ASTArray) + :statements => ast(AST::BlockExpression) ) result end .,., -module_eval(<<'.,.,', 'grammar.ra', 556) - def _reduce_168(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 559) + def _reduce_169(val, _values, result) result = aryfy(val[0]) result end .,., -module_eval(<<'.,.,', 'grammar.ra', 558) - def _reduce_169(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 561) + def _reduce_170(val, _values, result) val[0].push(val[2]) result = val[0] @@ -2050,18 +2097,18 @@ module_eval(<<'.,.,', 'grammar.ra', 558) end .,., -module_eval(<<'.,.,', 'grammar.ra', 563) - def _reduce_170(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 566) + def _reduce_171(val, _values, result) result = ast AST::Selector, :param => val[0], :values => val[2] result end .,., -# reduce 171 omitted +# reduce 172 omitted -module_eval(<<'.,.,', 'grammar.ra', 568) - def _reduce_172(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 571) + def _reduce_173(val, _values, result) @lexer.commentpop result = val[1] @@ -2069,10 +2116,10 @@ module_eval(<<'.,.,', 'grammar.ra', 568) end .,., -# reduce 173 omitted +# reduce 174 omitted -module_eval(<<'.,.,', 'grammar.ra', 574) - def _reduce_174(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 577) + def _reduce_175(val, _values, result) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -2084,16 +2131,14 @@ module_eval(<<'.,.,', 'grammar.ra', 574) end .,., -module_eval(<<'.,.,', 'grammar.ra', 583) - def _reduce_175(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 586) + def _reduce_176(val, _values, result) result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., -# reduce 176 omitted - # reduce 177 omitted # reduce 178 omitted @@ -2108,34 +2153,36 @@ module_eval(<<'.,.,', 'grammar.ra', 583) # reduce 183 omitted -module_eval(<<'.,.,', 'grammar.ra', 595) - def _reduce_184(val, _values, result) +# reduce 184 omitted + +module_eval(<<'.,.,', 'grammar.ra', 598) + def _reduce_185(val, _values, result) result = ast AST::Default, :value => val[0][:value], :line => val[0][:line] result end .,., -# reduce 185 omitted +# reduce 186 omitted -module_eval(<<'.,.,', 'grammar.ra', 600) - def _reduce_186(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 603) + def _reduce_187(val, _values, result) result = [val[0][:value]] result end .,., -# reduce 187 omitted +# reduce 188 omitted -module_eval(<<'.,.,', 'grammar.ra', 602) - def _reduce_188(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 605) + def _reduce_189(val, _values, result) result = val[0] += val[2] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 605) - def _reduce_189(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 608) + def _reduce_190(val, _values, result) val[1].each do |file| import(file) end @@ -2146,8 +2193,8 @@ module_eval(<<'.,.,', 'grammar.ra', 605) end .,., -module_eval(<<'.,.,', 'grammar.ra', 615) - def _reduce_190(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 618) + def _reduce_191(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :code => val[4], @@ -2160,8 +2207,8 @@ module_eval(<<'.,.,', 'grammar.ra', 615) end .,., -module_eval(<<'.,.,', 'grammar.ra', 623) - def _reduce_191(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 626) + def _reduce_192(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :line => val[0][:line])) @@ -2171,8 +2218,8 @@ module_eval(<<'.,.,', 'grammar.ra', 623) end .,., -module_eval(<<'.,.,', 'grammar.ra', 631) - def _reduce_192(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 634) + def _reduce_193(val, _values, result) @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop @@ -2184,8 +2231,8 @@ module_eval(<<'.,.,', 'grammar.ra', 631) end .,., -module_eval(<<'.,.,', 'grammar.ra', 638) - def _reduce_193(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 641) + def _reduce_194(val, _values, result) @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop @@ -2197,8 +2244,8 @@ module_eval(<<'.,.,', 'grammar.ra', 638) end .,., -module_eval(<<'.,.,', 'grammar.ra', 647) - def _reduce_194(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 650) + def _reduce_195(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :code => val[4], @@ -2208,8 +2255,8 @@ module_eval(<<'.,.,', 'grammar.ra', 647) end .,., -module_eval(<<'.,.,', 'grammar.ra', 652) - def _reduce_195(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 655) + def _reduce_196(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :line => val[0][:line])) @@ -2217,30 +2264,30 @@ module_eval(<<'.,.,', 'grammar.ra', 652) end .,., -module_eval(<<'.,.,', 'grammar.ra', 656) - def _reduce_196(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 659) + def _reduce_197(val, _values, result) result = val[0][:value] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 657) - def _reduce_197(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 660) + def _reduce_198(val, _values, result) result = "class" result end .,., -module_eval(<<'.,.,', 'grammar.ra', 662) - def _reduce_198(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 665) + def _reduce_199(val, _values, result) result = [result] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 665) - def _reduce_199(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 668) + def _reduce_200(val, _values, result) result = val[0] result << val[2] @@ -2248,65 +2295,65 @@ module_eval(<<'.,.,', 'grammar.ra', 665) end .,., -module_eval(<<'.,.,', 'grammar.ra', 670) - def _reduce_200(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 673) + def _reduce_201(val, _values, result) result = ast AST::HostName, :value => val[0] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 673) - def _reduce_201(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 676) + def _reduce_202(val, _values, result) result = val[0][:value] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 674) - def _reduce_202(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 677) + def _reduce_203(val, _values, result) result = val[0][:value] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 675) - def _reduce_203(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 678) + def _reduce_204(val, _values, result) result = val[0][:value] result end .,., -# reduce 204 omitted +# reduce 205 omitted -module_eval(<<'.,.,', 'grammar.ra', 679) - def _reduce_205(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 682) + def _reduce_206(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'grammar.ra', 683) - def _reduce_206(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 686) + def _reduce_207(val, _values, result) result = ast AST::ASTArray, :children => [] result end .,., -# reduce 207 omitted +# reduce 208 omitted -module_eval(<<'.,.,', 'grammar.ra', 688) - def _reduce_208(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 691) + def _reduce_209(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'grammar.ra', 691) - def _reduce_209(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 694) + def _reduce_210(val, _values, result) result = val[1] result = [result] unless result[0].is_a?(Array) @@ -2314,10 +2361,10 @@ module_eval(<<'.,.,', 'grammar.ra', 691) end .,., -# reduce 210 omitted +# reduce 211 omitted -module_eval(<<'.,.,', 'grammar.ra', 697) - def _reduce_211(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 700) + def _reduce_212(val, _values, result) result = val[0] result = [result] unless result[0].is_a?(Array) result << val[2] @@ -2326,96 +2373,96 @@ module_eval(<<'.,.,', 'grammar.ra', 697) end .,., -module_eval(<<'.,.,', 'grammar.ra', 703) - def _reduce_212(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 706) + def _reduce_213(val, _values, result) result = [val[0][:value], val[2]] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 704) - def _reduce_213(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 707) + def _reduce_214(val, _values, result) result = [val[0][:value]] result end .,., -# reduce 214 omitted +# reduce 215 omitted -module_eval(<<'.,.,', 'grammar.ra', 708) - def _reduce_215(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 711) + def _reduce_216(val, _values, result) result = val[1] result end .,., -# reduce 216 omitted +# reduce 217 omitted -module_eval(<<'.,.,', 'grammar.ra', 713) - def _reduce_217(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 716) + def _reduce_218(val, _values, result) result = val[1] result end .,., -# reduce 218 omitted - # reduce 219 omitted -module_eval(<<'.,.,', 'grammar.ra', 719) - def _reduce_220(val, _values, result) +# reduce 220 omitted + +module_eval(<<'.,.,', 'grammar.ra', 722) + def _reduce_221(val, _values, result) result = ast AST::Variable, :value => val[0][:value], :line => val[0][:line] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 722) - def _reduce_221(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 725) + def _reduce_222(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 723) - def _reduce_222(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 726) + def _reduce_223(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 724) - def _reduce_223(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 727) + def _reduce_224(val, _values, result) result = ast AST::ASTArray result end .,., -# reduce 224 omitted - # reduce 225 omitted # reduce 226 omitted -module_eval(<<'.,.,', 'grammar.ra', 730) - def _reduce_227(val, _values, result) +# reduce 227 omitted + +module_eval(<<'.,.,', 'grammar.ra', 733) + def _reduce_228(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'grammar.ra', 733) - def _reduce_228(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 736) + def _reduce_229(val, _values, result) result = ast AST::Regex, :value => val[0][:value] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 737) - def _reduce_229(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 740) + def _reduce_230(val, _values, result) if val[1].instance_of?(AST::ASTHash) result = val[1] else @@ -2426,8 +2473,8 @@ module_eval(<<'.,.,', 'grammar.ra', 737) end .,., -module_eval(<<'.,.,', 'grammar.ra', 744) - def _reduce_230(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 747) + def _reduce_231(val, _values, result) if val[1].instance_of?(AST::ASTHash) result = val[1] else @@ -2438,18 +2485,18 @@ module_eval(<<'.,.,', 'grammar.ra', 744) end .,., -module_eval(<<'.,.,', 'grammar.ra', 750) - def _reduce_231(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 753) + def _reduce_232(val, _values, result) result = ast AST::ASTHash result end .,., -# reduce 232 omitted +# reduce 233 omitted -module_eval(<<'.,.,', 'grammar.ra', 755) - def _reduce_233(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 758) + def _reduce_234(val, _values, result) if val[0].instance_of?(AST::ASTHash) result = val[0].merge(val[2]) else @@ -2461,40 +2508,40 @@ module_eval(<<'.,.,', 'grammar.ra', 755) end .,., -module_eval(<<'.,.,', 'grammar.ra', 764) - def _reduce_234(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 767) + def _reduce_235(val, _values, result) result = ast AST::ASTHash, { :value => { val[0] => val[2] } } result end .,., -module_eval(<<'.,.,', 'grammar.ra', 767) - def _reduce_235(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 770) + def _reduce_236(val, _values, result) result = val[0][:value] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 768) - def _reduce_236(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 771) + def _reduce_237(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'grammar.ra', 771) - def _reduce_237(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 774) + def _reduce_238(val, _values, result) result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2] result end .,., -# reduce 238 omitted +# reduce 239 omitted -module_eval(<<'.,.,', 'grammar.ra', 776) - def _reduce_239(val, _values, result) +module_eval(<<'.,.,', 'grammar.ra', 779) + def _reduce_240(val, _values, result) result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2] result diff --git a/lib/puppet/parser/parser_factory.rb b/lib/puppet/parser/parser_factory.rb new file mode 100644 index 000000000..bf80c12d2 --- /dev/null +++ b/lib/puppet/parser/parser_factory.rb @@ -0,0 +1,62 @@ +module Puppet; end + +module Puppet::Parser + # The ParserFactory makes selection of parser possible. + # Currently, it is possible to switch between two different parsers: + # * classic_parser, the parser in 3.1 + # * eparser, the Expression Based Parser + # + class ParserFactory + # Produces a parser instance for the given environment + def self.parser(environment) + case Puppet[:parser] + when 'future' + eparser(environment) + else + classic_parser(environment) + end + end + + # Creates an instance of the classic parser. + # + def self.classic_parser(environment) + require 'puppet/parser' + Puppet::Parser::Parser.new(environment) + end + + # Creates an instance of the expression based parser 'eparser' + # + def self.eparser(environment) + # Since RGen is optional, test that it is installed + @@asserted ||= false + assert_rgen_installed() unless @asserted + require 'puppet/parser' + require 'puppet/parser/e_parser_adapter' + EParserAdapter.new(Puppet::Parser::Parser.new(environment)) + end + + private + + def self.assert_rgen_installed + begin + require 'rgen/metamodel_builder' + rescue LoadError + raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using the setting '--parser future'. Please install 'rgen'.") + end + # Since RGen is optional, there is nothing specifying its version. + # It is not installed in any controlled way, so not possible to use gems to check (it may be installed some other way). + # Instead check that "eContainer, and eContainingFeature" has been installed. + require 'puppet/pops' + begin + litstring = Puppet::Pops::Model::LiteralString.new(); + container = Puppet::Pops::Model::ArithmeticExpression.new(); + container.left_expr = litstring + raise "no eContainer" if litstring.eContainer() != container + raise "no eContainingFeature" if litstring.eContainingFeature() != :left_expr + rescue =>e + raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using '--parser future'. An older version is installed, please update.") + end + end + end + +end diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index c7c0057d2..69226a5a6 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -38,6 +38,11 @@ class Puppet::Parser::Parser ast AST::ASTArray, :children => [arg] end + # Create an AST block containing a single element + def block(arg) + ast AST::BlockExpression, :children => [arg] + end + # Create an AST object, and automatically add the file and line information if # available. def ast(klass, hash = {}) @@ -146,9 +151,10 @@ class Puppet::Parser::Parser rescue Puppet::ParseError => except except.line ||= @lexer.line except.file ||= @lexer.file + except.pos ||= @lexer.pos raise except rescue => except - raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, except) + raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, nil, except) end end # Store the results as the top-level class. @@ -166,6 +172,6 @@ class Puppet::Parser::Parser main_object.instance_eval(File.read(self.file)) # Then extract any types that were created. - Puppet::Parser::AST::ASTArray.new :children => main_object.instance_eval { @__created_ast_objects__ } + Puppet::Parser::AST::BlockExpression.new :children => main_object.instance_eval { @__created_ast_objects__ } end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 1a05b1202..e80680e71 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -2,7 +2,7 @@ # such. require 'forwardable' -require 'puppet/parser/parser' +require 'puppet/parser' require 'puppet/parser/templatewrapper' require 'puppet/resource/type_collection_helper' @@ -42,12 +42,13 @@ class Puppet::Parser::Scope class Ephemeral extend Forwardable - def initialize(parent=nil) + def initialize(parent=nil, local=false) @symbols = {} @parent = parent + @local_scope = local end - def_delegators :@symbols, :include?, :delete, :[]= + def_delegators :@symbols, :delete, :[]=, :each def [](name) if @symbols.include?(name) or @parent.nil? @@ -56,6 +57,23 @@ class Puppet::Parser::Scope @parent[name] end end + + def include?(name) + bound?(name) or (@parent and @parent.include?(name)) + end + + def bound?(name) + @symbols.include?(name) + end + + def is_local_scope? + @local_scope + end + + # @return [Ephemeral, Hash, nil] + def parent + @parent + end end # Initialize a new scope suitable for parser function testing. This method @@ -164,14 +182,9 @@ class Puppet::Parser::Scope @tags = [] # The symbol table for this scope. This is where we store variables. - @symtable = {} + @symtable = Ephemeral.new - # the ephemeral symbol tables - # those should not persist long, and are used for the moment only - # for $0..$xy capture variables of regexes - # this is actually implemented as a stack, with each ephemeral scope - # shadowing the previous one - @ephemeral = [ Ephemeral.new ] + @ephemeral = [ Ephemeral.new(@symtable) ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being @@ -257,27 +270,21 @@ class Puppet::Parser::Scope raise Puppet::DevError, "Scope variable name is a #{name.class}, not a string" end - # Save the originating scope for the request - options[:origin] = self unless options[:origin] - table = ephemeral?(name) ? @ephemeral.last : @symtable + table = @ephemeral.last if name =~ /^(.*)::(.+)$/ - begin - qualified_scope($1).lookupvar($2, options.merge({:origin => nil})) - rescue RuntimeError => e - location = (options[:file] && (options[:line] || options[:lineproc])) ? " at #{options[:file]}:#{options[:line]|| options[:lineproc].call}" : '' - warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}" - nil - end - # If the value is present and either we are top/node scope or originating scope... - elsif (ephemeral_include?(name) or table.include?(name)) and (compiler and self == compiler.topscope or (resource and resource.type == "Node") or self == options[:origin]) + class_name = $1 + variable_name = $2 + lookup_qualified_variable(class_name, variable_name, options) + elsif table.include?(name) table[name] - elsif resource and resource.type == "Class" and parent_type = resource.resource_type.parent - qualified_scope(parent_type).lookupvar(name, options.merge({:origin => nil})) - elsif parent - parent.lookupvar(name, options) else - nil + next_scope = inherited_scope || enclosing_scope + if next_scope + next_scope.lookupvar(name, options) + else + nil + end end end @@ -295,6 +302,75 @@ class Puppet::Parser::Scope lookupvar(varname, options) end + # The scope of the inherited thing of this scope's resource. This could + # either be a node that was inherited or the class. + # + # @returns [Puppet::Parser::Scope] The scope or nil if there is not an inherited scope + def inherited_scope + if has_inherited_class? + qualified_scope(resource.resource_type.parent) + else + nil + end + end + + # The enclosing scope (topscope or nodescope) of this scope. + # The enclosing scopes are produced when a class or define is included at + # some point. The parent scope of the included class or define becomes the + # scope in which it was included. The chain of parent scopes is followed + # until a node scope or the topscope is found + # + # @returns [Puppet::Parser::Scope] The scope or nil if there is no enclosing scope + def enclosing_scope + if has_enclosing_scope? + if parent.is_topscope? or parent.is_nodescope? + parent + else + parent.enclosing_scope + end + else + nil + end + end + + def is_classscope? + resource and resource.type == "Class" + end + + def is_nodescope? + resource and resource.type == "Node" + end + + def is_topscope? + compiler and self == compiler.topscope + end + + def lookup_qualified_variable(class_name, variable_name, position) + begin + qualified_scope(class_name).lookupvar(variable_name, position) + rescue RuntimeError => e + location = if position[:lineproc] + " at #{position[:lineproc].call}" + elsif position[:file] && position[:line] + " at #{position[:file]}:#{position[:line]}" + else + "" + end + warning "Could not look up qualified variable '#{class_name}::#{variable_name}'; #{e.message}#{location}" + nil + end + end + + def has_inherited_class? + is_classscope? and resource.resource_type.parent + end + private :has_inherited_class? + + def has_enclosing_scope? + not parent.nil? + end + private :has_enclosing_scope? + def qualified_scope(classname) raise "class #{classname} could not be found" unless klass = find_hostclass(classname) raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) @@ -361,12 +437,15 @@ class Puppet::Parser::Scope # It's preferred that you use self[]= instead of this; only use this # when you need to set options. def setvar(name, value, options = {}) + if name =~ /^[0-9]+$/ + raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") unless options[:ephemeral] + end unless name.is_a? String raise Puppet::DevError, "Scope variable name is a #{name.class}, not a string" end - table = options[:ephemeral] ? @ephemeral.last : @symtable - if table.include?(name) + table = effective_symtable options[:ephemeral] + if table.bound?(name) if options[:append] error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") else @@ -379,9 +458,26 @@ class Puppet::Parser::Scope if options[:append] table[name] = append_value(undef_as('', self[name]), value) - else + else table[name] = value end + table[name] + end + + # Return the effective "table" for setting variables. + # This method returns the first ephemeral "table" that acts as a local scope, or this + # scope's symtable. If the parameter `use_ephemeral` is true, the "top most" ephemeral "table" + # will be returned (irrespective of it being a match scope or a local scope). + # + # @param [Boolean] whether the top most ephemeral (of any kind) should be used or not + def effective_symtable use_ephemeral + s = @ephemeral.last + return s if use_ephemeral + + while s && !(s.is_a?(Hash) || s.is_local_scope?()) + s = s.parent + end + s ? s : @symtable end # Sets the variable value of the name given as an argument to the given value. The value is @@ -407,7 +503,7 @@ class Puppet::Parser::Scope bound_value.merge(new_value) else if bound_value.is_a?(Hash) - raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" + raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" end bound_value + new_value end @@ -425,7 +521,7 @@ class Puppet::Parser::Scope # remove ephemeral scope up to level def unset_ephemeral_var(level=:all) if level == :all - @ephemeral = [ Ephemeral.new ] + @ephemeral = [ Ephemeral.new(@symtable)] else # If we ever drop 1.8.6 and lower, this should be replaced by a single # pop-with-a-count - or if someone more ambitious wants to monkey-patch @@ -436,32 +532,41 @@ class Puppet::Parser::Scope end end - # check if name exists in one of the ephemeral scope. + # check if name exists in one of the ephemeral scopes. def ephemeral_include?(name) @ephemeral.any? {|eph| eph.include?(name) } end - # is name an ephemeral variable? + # Checks whether the variable should be processed in the ephemeral scope or not. + # All numerical variables are processed in ephemeral scope at all times, and all other + # variables when the ephemeral scope is a local scope. + # def ephemeral?(name) - name =~ /^\d+$/ + @ephemeral.last.is_local_scope? || name =~ /^\d+$/ end def ephemeral_level @ephemeral.size end - def new_ephemeral - @ephemeral.push(Ephemeral.new(@ephemeral.last)) + def new_ephemeral(local_scope = false) + @ephemeral.push(Ephemeral.new(@ephemeral.last, local_scope)) end def ephemeral_from(match, file = nil, line = nil) - raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) - - new_ephemeral - - setvar("0", match[0], :file => file, :line => line, :ephemeral => true) - match.captures.each_with_index do |m,i| - setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) + case match + when Hash + # Create local scope ephemeral and set all values from hash + new_ephemeral true + match.each {|k,v| setvar(k, v, :file => file, :line => line, :ephemeral => true) } + else + raise(ArgumentError,"Invalid regex match data. Got a #{match.class}") unless match.is_a?(MatchData) + # Create a match ephemeral and set values from match data + new_ephemeral false + setvar("0", match[0], :file => file, :line => line, :ephemeral => true) + match.captures.each_with_index do |m,i| + setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) + end end end @@ -481,14 +586,15 @@ class Puppet::Parser::Scope def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ - super unless $1 - super unless Puppet::Parser::Functions.function($1) + name = $1 + super unless name + super unless Puppet::Parser::Functions.function(name) # In odd circumstances, this might not end up defined by the previous # method, so we might as well be certain. if respond_to? method send(method, *args) else - raise Puppet::DevError, "Function #{$1} not defined despite being loaded!" + raise Puppet::DevError, "Function #{name} not defined despite being loaded!" end end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 999d6068a..fc5721626 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -1,10 +1,11 @@ -# A simple wrapper for templates, so they don't have full access to -# the scope objects. require 'puppet/parser/files' require 'erb' +# A simple wrapper for templates, so they don't have full access to +# the scope objects. +# +# @api private class Puppet::Parser::TemplateWrapper - attr_writer :scope include Puppet::Util Puppet::Util.logmethods(self) @@ -12,41 +13,47 @@ class Puppet::Parser::TemplateWrapper @__scope__ = scope end + # @return [String] The full path name of the template that is being executed + # @api public def file @__file__ end + # @return [Puppet::Parser::Scope] The scope in which the template is evaluated + # @api public def scope @__scope__ end - def script_line_proc - # find which line in the template (if any) we were called from - # but defer to when necessary since fetching the caller information on - # every variable lookup can be quite time consuming - Proc.new { (caller.find { |l| l =~ /#{@__file__}:/ }||"")[/:(\d+):/,1] } - end - + # Find which line in the template (if any) we were called from. + # @return [String] the line number + # @api private def script_line - script_line_proc.call + identifier = Regexp.escape(@__file__ || "(erb)") + (caller.find { |l| l =~ /#{identifier}:/ }||"")[/:(\d+):/,1] end + private :script_line # Should return true if a variable is defined, false if it is not + # @api public def has_variable?(name) scope.include?(name.to_s) end - # Allow templates to access the defined classes + # @return [Array<String>] The list of defined classes + # @api public def classes scope.catalog.classes end - # Allow templates to access the tags defined in the current scope + # @return [Array<String>] The tags defined in the current scope + # @api public def tags scope.tags end - # Allow templates to access the all the defined tags + # @return [Array<String>] All the defined tags + # @api public def all_tags scope.catalog.tags end @@ -64,15 +71,18 @@ class Puppet::Parser::TemplateWrapper # the missing_method definition here until we declare the syntax finally # dead. def method_missing(name, *args) + line_number = script_line if scope.include?(name.to_s) - return scope[name.to_s, {:file => @__file__, :lineproc => script_line_proc}] + Puppet.deprecation_warning("Variable access via '#{name}' is deprecated. Use '@#{name}' instead. #{to_s}:#{line_number}") + return scope[name.to_s, { :file => @__file__, :line => line_number }] else # Just throw an error immediately, instead of searching for # other missingmethod things or whatever. - raise Puppet::ParseError.new("Could not find value for '#{name}'", @__file__, script_line) + raise Puppet::ParseError.new("Could not find value for '#{name}'", @__file__, line_number) end end + # @api private def file=(filename) unless @__file__ = Puppet::Parser::Files.find_template(filename, scope.compiler.environment.to_s) raise Puppet::ParseError, "Could not find template '#{filename}'" @@ -82,6 +92,7 @@ class Puppet::Parser::TemplateWrapper scope.known_resource_types.watch_file(@__file__) end + # @api private def result(string = nil) if string template_source = "inline template" @@ -95,11 +106,7 @@ class Puppet::Parser::TemplateWrapper # to the regular methods. benchmark(:debug, "Bound template variables for #{template_source}") do scope.to_hash.each do |name, value| - if name.kind_of?(String) - realname = name.gsub(/[^\w]/, "_") - else - realname = name - end + realname = name.gsub(/[^\w]/, "_") instance_variable_set("@#{realname}", value) end end diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb index fdb6ceda6..852d47472 100644 --- a/lib/puppet/parser/type_loader.rb +++ b/lib/puppet/parser/type_loader.rb @@ -1,6 +1,7 @@ require 'find' require 'forwardable' require 'puppet/node/environment' +require 'puppet/parser/parser_factory' class Puppet::Parser::TypeLoader extend Forwardable @@ -137,7 +138,8 @@ class Puppet::Parser::TypeLoader def parse_file(file) Puppet.debug("importing '#{file}' in environment #{environment}") - parser = Puppet::Parser::Parser.new(environment) +# parser = Puppet::Parser::Parser.new(environment) + parser = Puppet::Parser::ParserFactory.parser(environment) parser.file = file return parser.parse end diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb new file mode 100644 index 000000000..b0a36aa2c --- /dev/null +++ b/lib/puppet/pops.rb @@ -0,0 +1,40 @@ +module Puppet + 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/label_provider' + require 'puppet/pops/validation' + + require 'puppet/pops/model/model' + + 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 Parser + require 'puppet/pops/parser/eparser' + require 'puppet/pops/parser/parser_support' + require 'puppet/pops/parser/lexer' + end + + module Validation + require 'puppet/pops/validation/checker3_1' + require 'puppet/pops/validation/validator_factory_3_1' + end + end +end diff --git a/lib/puppet/pops/adaptable.rb b/lib/puppet/pops/adaptable.rb new file mode 100644 index 000000000..86cc97a27 --- /dev/null +++ b/lib/puppet/pops/adaptable.rb @@ -0,0 +1,190 @@ +# Adaptable is a mix-in module that adds adaptability to a class. +# This means that an adapter can +# associate itself with an instance of the class and store additional data/have behavior. +# +# This mechanism should be used when there is a desire to keep implementation concerns separate. +# In Ruby it is always possible to open and modify a class or instance to teach it new tricks, but it +# is however not possible to do this for two different versions of some service at the same time. +# The Adaptable pattern is also good when only a few of the objects of some class needs to have extra +# information (again possible in Ruby by adding instance variables dynamically). In fact, the implementation +# of Adaptable does just that; it adds an instance variable named after the adapter class and keeps an +# instance of this class in this slot. +# +# @note the implementation details; the fact that an instance variable is used to keep the adapter +# instance data should not +# be exploited as the implementation of _being adaptable_ may change in the future. +# @api private +# +module Puppet::Pops::Adaptable + # Base class for an Adapter. + # + # A typical adapter just defines some accessors. + # + # A more advanced adapter may need to setup the adapter based on the object it is adapting. + # @example Making Duck adaptable + # class Duck + # include Puppet::Pops::Adaptable + # end + # @example Giving a Duck a nick name + # class NickNameAdapter < Puppet::Pops::Adaptable::Adapter + # attr_accessor :nick_name + # end + # d = Duck.new + # NickNameAdapter.adapt(d).nick_name = "Daffy" + # NickNameAdapter.get(d).nick_name # => "Daffy" + # + # @example Giving a Duck a more elaborate nick name + # class NickNameAdapter < Puppet::Pops::Adaptable::Adapter + # attr_accessor :nick_name, :object + # def initialize o + # @object = o + # @nick_name = "Yo" + # end + # def nick_name + # "#{@nick_name}, the #{o.class.name}" + # end + # def NickNameAdapter.create_adapter(o) + # x = new o + # x + # end + # end + # d = Duck.new + # n = NickNameAdapter.adapt(d) + # n.nick_name # => "Yo, the Duck" + # n.nick_name = "Daffy" + # n.nick_name # => "Daffy, the Duck" + # @example Using a block to set values + # NickNameAdapter.adapt(o) { |a| a.nick_name = "Buddy!" } + # NickNameAdapter.adapt(o) { |a, o| a.nick_name = "You're the best #{o.class.name} I met."} + # + class Adapter + # Returns an existing adapter for the given object, or nil, if the object is not + # adapted. + # + # @param o [Adaptable] object to get adapter from + # @return [Adapter<self>] an adapter of the same class as the receiver of #get + # @return [nil] if the given object o has not been adapted by the receiving adapter + # @raise [ArgumentError] if the object is not adaptable + # + def self.get(o) + attr_name = :"@#{instance_var_name(self.name)}" + if existing = o.instance_variable_defined?(attr_name) + o.instance_variable_get(attr_name) + else + nil + end + end + + # Returns an existing adapter for the given object, or creates a new adapter if the + # object has not been adapted, or the adapter has been cleared. + # + # @example Using a block to set values + # NickNameAdapter.adapt(o) { |a| a.nick_name = "Buddy!" } + # NickNameAdapter.adapt(o) { |a, o| a.nick_name = "Your the best #{o.class.name} I met."} + # @overload adapt(o) + # @overload adapt(o, {|adapter| block}) + # @overload adapt(o, {|adapter, o| block}) + # @param o [Adaptable] object to add adapter to + # @yieldparam adapter [Adapter<self>] the created adapter + # @yieldparam o [Adaptable] optional, the given adaptable + # @param block [Proc] optional, evaluated in the context of the adapter (existing or new) + # @return [Adapter<self>] an adapter of the same class as the receiver of the call + # @raise [ArgumentError] if the given object o is not adaptable + # + def self.adapt(o, &block) + attr_name = :"@#{instance_var_name(self.name)}" + adapter = if existing = o.instance_variable_defined?(attr_name) && value = o.instance_variable_get(attr_name) + value + else + associate_adapter(create_adapter(o), o) + end + if block_given? + case block.arity + when 1 + block.call(adapter) + else + block.call(adapter, o) + end + end + adapter + end + + # Creates a new adapter, associates it with the given object and returns the adapter. + # + # @example Using a block to set values + # NickNameAdapter.adapt_new(o) { |a| a.nick_name = "Buddy!" } + # NickNameAdapter.adapt_new(o) { |a, o| a.nick_name = "Your the best #{o.class.name} I met."} + # This is used when a fresh adapter is wanted instead of possible returning an + # existing adapter as in the case of {Adapter.adapt}. + # @overload adapt_new(o) + # @overload adapt_new(o, {|adapter| block}) + # @overload adapt_new(o, {|adapter, o| block}) + # @yieldparam adapter [Adapter<self>] the created adapter + # @yieldparam o [Adaptable] optional, the given adaptable + # @param o [Adaptable] object to add adapter to + # @param block [Proc] optional, evaluated in the context of the new adapter + # @return [Adapter<self>] an adapter of the same class as the receiver of the call + # @raise [ArgumentError] if the given object o is not adaptable + # + def self.adapt_new(o, &block) + adapter = associate_adapter(create_adapter(o), o) + if block_given? + case block.arity + when 1 + block.call(adapter) + else + block.call(adapter, o) + end + end + adapter + end + + # Clears the adapter set in the given object o. Returns any set adapter or nil. + # @param o [Adaptable] the object where the adapter should be cleared + # @return [Adapter] if an adapter was set + # @return [nil] if the adapter has not been set + # + def self.clear(o) + attr_name = :"@#{instance_var_name(self.name)}" + if o.instance_variable_defined?(attr_name) + o.send(:remove_instance_variable, attr_name) + else + nil + end + end + + # This base version creates an instance of the class (i.e. an instance of the concrete subclass + # of Adapter). A Specialization may want to create an adapter instance specialized for the given target + # object. + # @param o [Adaptable] The object to adapt. This implementation ignores this variable, but a + # specialization may want to initialize itself differently depending on the object it is adapting. + # @return [Adapter<self>] instance of the subclass of Adapter receiving the call + # + def self.create_adapter(o) + new + end + + # Associates the given adapter with the given target object + # @param adapter [Adapter] the adapter to associate with the given object _o_ + # @param o [Adaptable] the object to adapt + # @return [adapter] the given adapter + # + def self.associate_adapter(adapter, o) + attr_name = :"@#{instance_var_name(adapter.class.name)}" + o.instance_variable_set(attr_name, adapter) + adapter + end + + # Returns a suitable instance variable name given a class name. + # The returned string is the fully qualified name of a class with '::' replaced by '_' since + # '::' is not allowed in an instance variable name. + # @param name [String] the fully qualified name of a class + # @return [String] the name with all '::' replaced by '_' + # @api private + # @private + # + def self.instance_var_name(name) + name.split("::").join('_') + end + end +end diff --git a/lib/puppet/pops/adapters.rb b/lib/puppet/pops/adapters.rb new file mode 100644 index 000000000..07b3a1caa --- /dev/null +++ b/lib/puppet/pops/adapters.rb @@ -0,0 +1,65 @@ +# The Adapters module contains adapters for Documentation, Origin, SourcePosition, and Loader. +# +module Puppet::Pops::Adapters + # A documentation adapter adapts an object with a documentation string. + # (The intended use is for a source text parser to extract documentation and store this + # in DocumentationAdapter instances). + # + class DocumentationAdapter < Puppet::Pops::Adaptable::Adapter + # @return [String] The documentation associated with an object + attr_accessor :documentation + end + + # An origin adapter adapts an object with where it came from. This origin + # describes the resource (a file, etc.) where source text originates. + # Instances of SourcePosAdapter is then used on other objects in a model to + # describe their relative position versus the origin. + # + # @see Puppet::Pops::Utils#find_adapter + # + class OriginAdapter < Puppet::Pops::Adaptable::Adapter + # @return [String] the origin of the adapted (usually a filename) + attr_accessor :origin + end + + # A SourcePosAdapter describes a position relative to an origin. (Typically an {OriginAdapter} is + # associated with the root of a model. This origin has a URI to the resource, and a line number. + # The offset in the SourcePosAdapter is then relative to this origin. + # (This somewhat complex structure makes it possible to correctly refer to a source position + # in source that is embedded in some resource; a parser only sees the embedded snippet of source text + # and does not know where it was embedded). + # + # @see Puppet::Pops::Utils#find_adapter + # + class SourcePosAdapter < Puppet::Pops::Adaptable::Adapter + # @return [Fixnum] The start line in source starting from 1 + attr_accessor :line + + # @return [Fixnum] The position on the start_line (in characters) starting from 0 + attr_accessor :pos + + # @return [Fixnum] The (start) offset of source text characters + # (starting from 0) representing the adapted object. + # Value may be nil + attr_accessor :offset + + # @return [Fixnum] The length (count) of characters of source text + # representing the adapted object from the origin. Not including any + # trailing whitespace. + attr_accessor :length + end + + # A LoaderAdapter adapts an object with a {Puppet::Pops::Loader}. This is used to make further loading from the + # perspective of the adapted object take place in the perspective of this Loader. + # + # It is typically enough to adapt the root of a model as a search is made towards the root of the model + # until a loader is found, but there is no harm in duplicating this information provided a contained + # object is adapted with the correct loader. + # + # @see Puppet::Pops::Utils#find_adapter + # + class LoaderAdapter < Puppet::Pops::Adaptable::Adapter + # @return [Puppet::Pops::Loader] the loader + attr_accessor :loader + end +end diff --git a/lib/puppet/pops/containment.rb b/lib/puppet/pops/containment.rb new file mode 100644 index 000000000..a019044b0 --- /dev/null +++ b/lib/puppet/pops/containment.rb @@ -0,0 +1,37 @@ +# FIXME: This module should be updated when a newer version of RGen (>0.6.2) adds required meta model "e-method" supports. +# +module Puppet::Pops::Containment + # Returns Enumerable, thus allowing + # some_element.eAllContents each {|contained| } + # This is a depth first enumeration where parent appears before children. + # @note the top-most object itself is not included in the enumeration, only what it contains. + def eAllContents + EAllContentsEnumerator.new(self) + end + + class EAllContentsEnumerator + include Enumerable + def initialize o + @element = o + end + + def each &block + if block_given? + eAllContents(@element, &block) + @element + else + self + end + end + + def eAllContents(element, &block) + element.class.ecore.eAllReferences.select{|r| r.containment}.each do |r| + children = element.getGenericAsArray(r.name) + children.each do |c| + block.call(c) + eAllContents(c, &block) + end + end + end + end +end diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb new file mode 100644 index 000000000..1440c9030 --- /dev/null +++ b/lib/puppet/pops/issues.rb @@ -0,0 +1,258 @@ +# Defines classes to deal with issues, and message formatting and defines constants with Issues. +# @api public +# +module Puppet::Pops::Issues + # Describes an issue, and can produce a message for an occurrence of the issue. + # + class Issue + # The issue code + # @return [Symbol] + attr_reader :issue_code + + # A block producing the message + # @return [Proc] + attr_reader :message_block + + # Names that must be bound in an occurrence of the issue to be able to produce a message. + # These are the names in addition to requirements stipulated by the Issue formatter contract; i.e. :label`, + # and `:semantic`. + # + attr_reader :arg_names + + # If this issue can have its severity lowered to :warning, :deprecation, or :ignored + attr_writer :demotable + # Configures the Issue with required arguments (bound by occurrence), and a block producing a message. + def initialize issue_code, *args, &block + @issue_code = issue_code + @message_block = block + @arg_names = args + @demotable = true + end + + # Returns true if it is allowed to demote this issue + def demotable? + @demotable + end + + # Formats a message for an occurrence of the issue with argument bindings passed in a hash. + # The hash must contain a LabelProvider bound to the key `label` and the semantic model element + # bound to the key `semantic`. All required arguments as specified by `arg_names` must be bound + # in the given `hash`. + # @api public + # + def format(hash ={}) + # Create a Message Data where all hash keys become methods for convenient interpolation + # in issue text. + msgdata = MessageData.new(*arg_names) + # Evaluate the message block in the msg data's binding + msgdata.format(hash, &message_block) + end + end + + # Provides a binding of arguments passed to Issue.format to method names available + # in the issue's message producing block. + # @api private + # + class MessageData + def initialize *argnames + singleton = class << self; self end + argnames.each do |name| + singleton.send(:define_method, name) do + @data[name] + end + end + end + + def format(hash, &block) + @data = hash + instance_eval &block + end + + # Returns the label provider given as a key in the hash passed to #format. + # + def label + raise "Label provider key :label must be set to produce the text of the message!" unless @data[:label] + @data[:label] + end + + # Returns the label provider given as a key in the hash passed to #format. + # + def semantic + raise "Label provider key :semantic must be set to produce the text of the message!" unless @data[:semantic] + @data[:semantic] + end + end + + # Defines an issue with the given `issue_code`, additional required parameters, and a block producing a message. + # The block is evaluated in the context of a MessageData which provides convenient access to all required arguments + # via accessor methods. In addition to accessors for specified arguments, these are also available: + # * `label` - a `LabelProvider` that provides human understandable names for model elements and production of article (a/an/the). + # * `semantic` - the model element for which the issue is reported + # + # @param issue_code [Symbol] the issue code for the issue used as an identifier, should be the same as the constant + # the issue is bound to. + # @param *args [Symbol] required arguments that must be passed when formatting the message, may be empty + # @param &block [Proc] a block producing the message string, evaluated in a MessageData scope. The produced string + # should not end with a period as additional information may be appended. + # + # @see MessageData + # @api public + # + def self.issue (issue_code, *args, &block) + Issue.new(issue_code, *args, &block) + end + + # Creates a non demotable issue. + # @see Issue.issue + # + def self.hard_issue(issue_code, *args, &block) + result = Issue.new(issue_code, *args, &block) + result.demotable = false + result + end + + # @comment Here follows definitions of issues. The intent is to provide a list from which yardoc can be generated + # containing more detailed information / explanation of the issue. + # These issues are set as constants, but it is unfortunately not possible for the created object to easily know which + # name it is bound to. Instead the constant has to be repeated. (Alternatively, it could be done by instead calling + # #const_set on the module, but the extra work required to get yardoc output vs. the extra effort to repeat the name + # twice makes it not worth it (if doable at all, since there is no tag to artificially construct a constant, and + # the parse tag does not produce any result for a constant assignment). + + # This is allowed (3.1) and has not yet been deprecated. + # @todo configuration + # + NAME_WITH_HYPHEN = issue :NAME_WITH_HYPHEN, :name do + "#{label.a_an_uc(semantic)} may not have a name contain a hyphen. The name '#{name}' is not legal" + end + + # When a variable name contains a hyphen and these are illegal. + # It is possible to control if a hyphen is legal in a name or not using the setting TODO + # @todo describe the setting + # @api public + # @todo configuration if this is error or warning + # + VAR_WITH_HYPHEN = issue :VAR_WITH_HYPHEN, :name do + "A variable name may not contain a hyphen. The name '#{name}' is not legal" + end + + # A class, definition, or node may only appear at top level or inside other classes + # @todo Is this really true for nodes? Can they be inside classes? Isn't that too late? + # @api public + # + NOT_TOP_LEVEL = hard_issue :NOT_TOP_LEVEL do + "Classes, definitions, and nodes may only appear at toplevel or inside other classes" + end + + CROSS_SCOPE_ASSIGNMENT = hard_issue :CROSS_SCOPE_ASSIGNMENT, :name do + "Illegal attempt to assign to '#{name}'. Cannot assign to variables in other namespaces" + end + + # Assignment can only be made to certain types of left hand expressions such as variables. + ILLEGAL_ASSIGNMENT = hard_issue :ILLEGAL_ASSIGNMENT do + "Illegal attempt to assign to '#{label.a_an(semantic)}'. Not an assignable reference" + end + + # Assignment cannot be made to numeric match result variables + ILLEGAL_NUMERIC_ASSIGNMENT = issue :ILLEGAL_NUMERIC_ASSIGNMENT, :varname do + "Illegal attempt to assign to the numeric match result variable '$#{varname}'. Numeric variables are not assignable" + end + + # parameters cannot have numeric names, clashes with match result variables + ILLEGAL_NUMERIC_PARAMETER = issue :ILLEGAL_NUMERIC_PARAMETER, :name do + "The numeric parameter name '$#{varname}' cannot be used (clashes with numeric match result variables)" + end + + # In certain versions of Puppet it may be allowed to assign to a not already assigned key + # in an array or a hash. This is an optional validation that may be turned on to prevent accidental + # mutation. + # + ILLEGAL_INDEXED_ASSIGNMENT = issue :ILLEGAL_INDEXED_ASSIGNMENT do + "Illegal attempt to assign via [index/key]. Not an assignable reference" + end + + # When indexed assignment ($x[]=) is allowed, the leftmost expression must be + # a variable expression. + # + ILLEGAL_ASSIGNMENT_VIA_INDEX = hard_issue :ILLEGAL_ASSIGNMENT_VIA_INDEX do + "Illegal attempt to assign to #{label.a_an(semantic)} via [index/key]. Not an assignable reference" + end + + # Some expressions/statements may not produce a value (known as right-value, or rvalue). + # This may vary between puppet versions. + # + NOT_RVALUE = issue :NOT_RVALUE do + "Invalid use of expression. #{label.a_an_uc(semantic)} does not produce a value" + end + + # Appending to attributes is only allowed in certain types of resource expressions. + # + ILLEGAL_ATTRIBUTE_APPEND = hard_issue :ILLEGAL_ATTRIBUTE_APPEND, :name, :parent do + "Illegal +> operation on attribute #{name}. This operator can not be used in #{label.a_an(parent)}" + end + + # In case a model is constructed programmatically, it must create valid type references. + # + ILLEGAL_CLASSREF = hard_issue :ILLEGAL_CLASSREF, :name do + "Illegal type reference. The given name '#{name}' does not conform to the naming rule" + end + + # This is a runtime issue - storeconfigs must be on in order to collect exported. This issue should be + # set to :ignore when just checking syntax. + # @todo should be a :warning by default + # + RT_NO_STORECONFIGS = issue :RT_NO_STORECONFIGS do + "You cannot collect exported resources without storeconfigs being set; the collection will be ignored" + end + + # This is a runtime issue - storeconfigs must be on in order to export a resource. This issue should be + # set to :ignore when just checking syntax. + # @todo should be a :warning by default + # + RT_NO_STORECONFIGS_EXPORT = issue :RT_NO_STORECONFIGS_EXPORT do + "You cannot collect exported resources without storeconfigs being set; the export is ignored" + end + + # A hostname may only contain letters, digits, '_', '-', and '.'. + # + ILLEGAL_HOSTNAME_CHARS = hard_issue :ILLEGAL_HOSTNAME_CHARS, :hostname do + "The hostname '#{hostname}' contains illegal characters (only letters, digits, '_', '-', and '.' are allowed)" + end + + # A hostname may only contain letters, digits, '_', '-', and '.'. + # + ILLEGAL_HOSTNAME_INTERPOLATION = hard_issue :ILLEGAL_HOSTNAME_INTERPOLATION do + "An interpolated expression is not allowed in a hostname of a node" + end + + # Issues when an expression is used where it is not legal. + # E.g. an arithmetic expression where a hostname is expected. + # + ILLEGAL_EXPRESSION = hard_issue :ILLEGAL_EXPRESSION, :feature, :container do + "Illegal expression. #{label.a_an_uc(semantic)} is unacceptable as #{feature} in #{label.a_an(container)}" + end + + # Issues when an expression is used illegaly in a query. + # query only supports == and !=, and not <, > etc. + # + ILLEGAL_QUERY_EXPRESSION = hard_issue :ILLEGAL_QUERY_EXPRESSION do + "Illegal query expression. #{label.a_an_uc(semantic)} cannot be used in a query" + end + + # If an attempt is made to make a resource default virtual or exported. + # + NOT_VIRTUALIZEABLE = hard_issue :NOT_VIRTUALIZEABLE do + "Resource Defaults are not virtualizable" + end + + # When an attempt is made to use multiple keys (to produce a range in Ruby - e.g. $arr[2,-1]). + # This is currently not supported, but may be in future versions + # + UNSUPPORTED_RANGE = issue :UNSUPPORTED_RANGE, :count do + "Attempt to use unsupported range in #{label.a_an(semantic)}, #{count} values given for max 1" + end + + DEPRECATED_NAME_AS_TYPE = issue :DEPRECATED_NAME_AS_TYPE, :name do + "Resource references should now be capitalized. The given '#{name}' does not have the correct form" + end +end diff --git a/lib/puppet/pops/label_provider.rb b/lib/puppet/pops/label_provider.rb new file mode 100644 index 000000000..e8a75a784 --- /dev/null +++ b/lib/puppet/pops/label_provider.rb @@ -0,0 +1,71 @@ +# Provides a label for an object. +# This simple implementation calls #to_s on the given object, and handles articles 'a/an/the'. +# +class Puppet::Pops::LabelProvider + VOWELS = %w{a e i o u y} + SKIPPED_CHARACTERS = %w{" '} + A = "a" + AN = "an" + + # Provides a label for the given object by calling `to_s` on the object. + # The intent is for this method to be overridden in concrete label providers. + def label o + o.to_s + end + + # Produces a label for the given text with indefinite article (a/an) + def a_an o + text = label(o) + "#{article(text)} #{text}" + end + + # Produces a label for the given text with indefinite article (A/An) + def a_an_uc o + text = label(o) + "#{article(text).capitalize} #{text}" + end + + # Produces a label for the given text with *definitie article* (the). + def the o + "the #{label(o)}" + end + + # Produces a label for the given text with *definitie article* (The). + def the_uc o + "The #{label(o)}" + end + + private + + # Produces an *indefinite article* (a/an) for the given text ('a' if + # it starts with a vowel) This is obviously flawed in the general + # sense as may labels have punctuation at the start and this method + # does not translate punctuation to English words. Also, if a vowel is + # pronounced as a consonant, the article should not be "an". + # + def article s + article_for_letter(first_letter_of(s)) + end + + def first_letter_of(string) + char = string[0,1] + if SKIPPED_CHARACTERS.include? char + char = string[1,1] + end + + if char == "" + raise Puppet::DevError, "<#{string}> does not appear to contain a word" + end + + char + end + + def article_for_letter(letter) + downcased = letter.downcase + if VOWELS.include? downcased + AN + else + A + end + end +end diff --git a/lib/puppet/pops/model/ast_transformer.rb b/lib/puppet/pops/model/ast_transformer.rb new file mode 100644 index 000000000..a9ce9946d --- /dev/null +++ b/lib/puppet/pops/model/ast_transformer.rb @@ -0,0 +1,636 @@ +require 'puppet/parser/ast' + +# The receiver of `import(file)` calls; once per imported file, or nil if imports are ignored +# +# Transforms a Pops::Model to classic Puppet AST. +# TODO: Documentation is currently skipped completely (it is only used for Rdoc) +# +class Puppet::Pops::Model::AstTransformer + AST = Puppet::Parser::AST + Model = Puppet::Pops::Model + + attr_reader :importer + def initialize(source_file = "unknown-file", importer=nil) + @@transform_visitor ||= Puppet::Pops::Visitor.new(nil,"transform",0,0) + @@query_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"query",0,0) + @@hostname_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"hostname",0,0) + @importer = importer + @source_file = source_file + end + + # Initialize klass from o (location) and hash (options to created instance). + # The object o is used to compute a source location. It may be nil. Source position is merged into + # the given options (non surgically). If o is non-nil, the first found source position going up + # the containment hierarchy is set. I.e. callers should pass nil if a source position is not wanted + # or known to be unobtainable for the object. + # + # @param o [Object, nil] object from which source position / location is obtained, may be nil + # @param klass [Class<Puppet::Parser::AST>] the ast class to create an instance of + # @param hash [Hash] hash with options for the class to create + # + def ast(o, klass, hash={}) + # create and pass hash with file and line information + klass.new(merge_location(hash, o)) + end + + def merge_location(hash, o) + if o + pos = {} + source_pos = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::SourcePosAdapter) + if source_pos + pos[:line] = source_pos.line + pos[:pos] = source_pos.pos + end + pos[:file] = @source_file if @source_file + hash = hash.merge(pos) + end + hash + end + + # Transforms pops expressions into AST 3.1 statements/expressions + def transform(o) + @@transform_visitor.visit_this(self,o) + end + + # Transforms pops expressions into AST 3.1 query expressions + def query(o) + @@query_transform_visitor.visit_this(self, o) + end + + # Transforms pops expressions into AST 3.1 hostnames + def hostname(o) + @@hostname_transform_visitor.visit_this(self, o) + end + + def transform_LiteralNumber(o) + s = case o.radix + when 10 + o.value.to_s + when 8 + "0%o" % o.value + when 16 + "0x%X" % o.value + else + "bad radix:" + o.value.to_s + end + + # Numbers are Names in the AST !! (Name a.k.a BareWord) + ast o, AST::Name, :value => s + end + + # Transforms all literal values to string (override for those that should not be AST::String) + # + def transform_LiteralValue(o) + ast o, AST::String, :value => o.value.to_s + end + + def transform_LiteralBoolean(o) + ast o, AST::Boolean, :value => o.value + end + + def transform_Factory(o) + transform(o.current) + end + + def transform_ArithmeticExpression(o) + ast o, AST::ArithmeticOperator, :lval => transform(o.left_expr), :rval=>transform(o.right_expr), + :operator => o.operator.to_s + end + + def transform_Array(o) + ast nil, AST::ASTArray, :children => o.collect {|x| transform(x) } + end + + # Puppet AST only allows: + # * variable[expression] => Hasharray Access + # * NAME [expressions] => Resource Reference(s) + # * type [epxressions] => Resource Reference(s) + # * HashArrayAccesses[expression] => HasharrayAccesses + # + # i.e. it is not possible to do `func()[3]`, `[1,2,3][$x]`, `{foo=>10, bar=>20}[$x]` etc. since + # LHS is not an expression + # + # Validation for 3.x semantics should validate the illegal cases. This transformation may fail, + # or ignore excess information if the expressions are not correct. + # This means that the transformation does not have to evaluate the lhs to detect the target expression. + # + # Hm, this seems to have changed, the LHS (variable) is evaluated if evaluateable, else it is used as is. + # + def transform_AccessExpression(o) + case o.left_expr + when Model::QualifiedName + ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) + + when Model::QualifiedReference + ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) + + when Model::VariableExpression + ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) + + else + ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) + end + end + + # Puppet AST has a complicated structure + # LHS can not be an expression, it must be a type (which is downcased). + # type = a downcased QualifiedName + # + def transform_CollectExpression(o) + raise "LHS is not a type" unless o.type_expr.is_a? Model::QualifiedReference + type = o.type_expr.value().downcase() + args = { :type => type } + + # This somewhat peculiar encoding is used by the 3.1 AST. + query = transform(o.query) + if query.is_a? Symbol + args[:form] = query + else + args[:form] = query.form + args[:query] = query + query.type = type + end + + if o.operations.size > 0 + args[:override] = transform(o.operations) + end + ast o, AST::Collection, args + end + + def transform_ExportedQuery(o) + if is_nop?(o.expr) + result = :exported + else + result = query(o.expr) + result.form = :exported + end + result + end + + def transform_VirtualQuery(o) + if is_nop?(o.expr) + result = :virtual + else + result = query(o.expr) + result.form = :virtual + end + result + end + + # Ensures transformation fails if a 3.1 non supported object is encountered in a query expression + # + def query_Object(o) + raise "Not a valid expression in a collection query: "+o.class.name + end + + # Puppet AST only allows == and !=, and left expr is restricted, but right value is an expression + # + def query_ComparisonExpression(o) + if [:'==', :'!='].include? o.operator + ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => o.operator.to_s, :test2 => transform(o.right_expr) + else + raise "Not a valid comparison operator in a collection query: " + o.operator.to_s + end + end + + def query_AndExpression(o) + ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'and', :test2 => query(o.right_expr) + end + + def query_OrExpression(o) + ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'or', :test2 => query(o.right_expr) + end + + def query_ParenthesizedExpression(o) + result = query(o.expr) # produces CollExpr + result.parens = true + result + end + + def query_VariableExpression(o) + transform(o) + end + + def query_QualifiedName(o) + transform(o) + end + + def query_LiteralNumber(o) + transform(o) # number to string in correct radix + end + + def query_LiteralString(o) + transform(o) + end + + def query_LiteralBoolean(o) + transform(o) + end + + def transform_QualifiedName(o) + ast o, AST::Name, :value => o.value + end + + def transform_QualifiedReference(o) + ast o, AST::Type, :value => o.value + end + + def transform_ComparisonExpression(o) + ast o, AST::ComparisonOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) + end + + def transform_AndExpression(o) + ast o, AST::BooleanOperator, :operator => 'and', :lval => transform(o.left_expr), :rval => transform(o.right_expr) + end + + def transform_OrExpression(o) + ast o, AST::BooleanOperator, :operator => 'or', :lval => transform(o.left_expr), :rval => transform(o.right_expr) + end + + def transform_InExpression(o) + ast o, AST::InOperator, :lval => transform(o.left_expr), :rval => transform(o.right_expr) + end + + # This is a complex transformation from a modeled import to a Nop result (where the import took place), + # and calls to perform import/parsing etc. during the transformation. + # When testing syntax, the @importer does not have to be set, but it is not possible to check + # the actual import without inventing a new AST::ImportExpression with nop effect when evaluating. + def transform_ImportExpression(o) + if importer + o.files.each {|f| + unless f.is_a? Model::LiteralString + raise "Illegal import file expression. Must be a single quoted string" + end + importer.import(f.value) + } + end + # Crazy stuff + # Transformation of "import" needs to parse the other files at the time of transformation. + # Then produce a :nop, since nothing should be evaluated. + ast o, AST::Nop, {} + end + + def transform_InstanceReferences(o) + ast o, AST::ResourceReference, :type => o.type_name.value, :title => transform(o.names) + end + + # Assignment in AST 3.1 is to variable or hasharray accesses !!! See Bug #16116 + def transform_AssignmentExpression(o) + args = {:value => transform(o.right_expr) } + args[:append] = true if o.operator == :'+=' + + args[:name] = case o.left_expr + when Model::VariableExpression + ast o, AST::Name, {:value => o.left_expr.expr.value } + when Model::AccessExpression + transform(o.left_expr) + else + raise "LHS is not an expression that can be assigned to" + end + ast o, AST::VarDef, args + end + + # Produces (name => expr) or (name +> expr) + def transform_AttributeOperation(o) + args = { :value => transform(o.value_expr) } + args[:add] = true if o.operator == :'+>' + args[:param] = o.attribute_name + ast o, AST::ResourceParam, args + end + + def transform_LiteralList(o) + # Uses default transform of Ruby Array to ASTArray + transform(o.values) + end + + # Literal hash has strange behavior in Puppet 3.1. See Bug #19426, and this implementation is bug + # compatible + def transform_LiteralHash(o) + if o.entries.size == 0 + ast o, AST::ASTHash, {:value=> {}} + else + value = {} + o.entries.each {|x| value.merge! transform(x) } + ast o, AST::ASTHash, {:value=> value} + end + end + + # Transforms entry into a hash (they are later merged with strange effects: Bug #19426). + # Puppet 3.x only allows: + # * NAME + # * quotedtext + # As keys (quoted text can be an interpolated string which is compared as a key in a less than satisfactory way). + # + def transform_KeyedEntry(o) + value = transform(o.value) + key = case o.key + when Model::QualifiedName + o.key.value + when Model::LiteralString + transform o.key + when Model::LiteralNumber + transform o.key + when Model::ConcatenatedString + transform o.key + else + raise "Illegal hash key expression of type (#{o.key.class})" + end + {key => value} + end + + def transform_MatchExpression(o) + ast o, AST::MatchOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) + end + + def transform_LiteralString(o) + ast o, AST::String, :value => o.value + end + + # Literal text in a concatenated string + def transform_LiteralText(o) + ast o, AST::String, :value => o.value + end + + def transform_LambdaExpression(o) + astargs = { :parameters => o.parameters.collect {|p| transform(p) } } + astargs.merge!({ :children => transform(o.body) }) if o.body # do not want children if it is nil/nop + ast o, AST::Lambda, astargs + end + + def transform_LiteralDefault(o) + ast o, AST::Default, :value => :default + end + + def transform_LiteralUndef(o) + ast o, AST::Undef, :value => :undef + end + + def transform_LiteralRegularExpression(o) + ast o, AST::Regex, :value => o.value + end + + def transform_Nop(o) + ast o, AST::Nop + end + + # In the 3.1. grammar this is a hash that is merged with other elements to form a method call + # Also in 3.1. grammar there are restrictions on the LHS (that are only there for grammar issues). + # + def transform_NamedAccessExpression(o) + receiver = transform(o.left_expr) + name = o.right_expr + raise "Unacceptable function/method name" unless name.is_a? Model::QualifiedName + {:receiver => receiver, :name => name.value} + end + + def transform_NilClass(o) + ast o, AST::Nop, {} + end + + def transform_NotExpression(o) + ast o, AST::Not, :value => transform(o.expr) + end + + def transform_VariableExpression(o) + # assumes the expression is a QualifiedName + ast o, AST::Variable, :value => o.expr.value + end + + # In Puppet 3.1, the ConcatenatedString is responsible for the evaluation and stringification of + # expression segments. Expressions and Strings are kept in an array. + def transform_TextExpression(o) + transform(o.expr) + end + + def transform_UnaryMinusExpression(o) + ast o, AST::Minus, :value => transform(o.expr) + end + + # Puppet 3.1 representation of a BlockExpression is an AST::Array - this makes it impossible to differentiate + # between a LiteralArray and a Sequence. (Should it return the collected array, or the last expression?) + # (A BlockExpression has now been introduced in the AST to solve this). + # + def transform_BlockExpression(o) + children = [] + # remove nops resulting from import + o.statements.each {|s| r = transform(s); children << r unless is_nop?(r) } + ast o, AST::BlockExpression, :children => children # o.statements.collect {|s| transform(s) } + end + + # Interpolated strings are kept in an array of AST (string or other expression). + def transform_ConcatenatedString(o) + ast o, AST::Concat, :value => o.segments.collect {|x| transform(x)} + end + + def transform_HostClassDefinition(o) + parameters = o.parameters.collect {|p| transform(p) } + args = { + :arguments => parameters, + :parent => o.parent_class, + } + args[:code] = transform(o.body) unless is_nop?(o.body) + Puppet::Parser::AST::Hostclass.new(o.name, merge_location(args, o)) + end + + def transform_NodeDefinition(o) + # o.host_matches are expressions, and 3.1 AST requires special object AST::HostName + # where a HostName is one of NAME, STRING, DEFAULT or Regexp - all of these are strings except regexp + # + args = { + :code => transform(o.body) + } + args[:parent] = transform(o.parent) unless is_nop?(o.parent) + Puppet::Parser::AST::Node.new(hostname(o.host_matches), merge_location(args, o)) + end + + # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName + def hostname_Array(o) + o.collect {|x| ast x, AST::HostName, :value => hostname(x) } + end + + def hostname_LiteralValue(o) + return o.value + end + + def hostname_QualifiedName(o) + return o.value + end + + def hostname_LiteralNumber(o) + transform(o) # Number to string with correct radix + end + + def hostname_LiteralDefault(o) + return 'default' + end + + def hostname_LiteralRegularExpression(o) + ast o, AST::Regex, :value => o.value + end + + def hostname_Object(o) + raise "Illegal expression - unacceptable as a node name" + end + + def transform_RelationshipExpression(o) + Puppet::Parser::AST::Relationship.new(transform(o.left_expr), transform(o.right_expr), o.operator.to_s, merge_location({}, o)) + end + + def transform_ResourceTypeDefinition(o) + parameters = o.parameters.collect {|p| transform(p) } + args = { :arguments => parameters } + args[:code] = transform(o.body) unless is_nop?(o.body) + + Puppet::Parser::AST::Definition.new(o.name, merge_location(args, o)) + end + + # Transformation of ResourceOverrideExpression is slightly more involved than a straight forward + # transformation. + # A ResourceOverrideExppression has "resources" which should be an AccessExpression + # on the form QualifiedName[expressions], or QualifiedReference[expressions] to be valid. + # It also has a set of attribute operations. + # + # The AST equivalence is an AST::ResourceOverride with a ResourceReference as its LHS, and + # a set of Parameters. + # ResourceReference has type as a string, and the expressions representing + # the "titles" to be an ASTArray. + # + def transform_ResourceOverrideExpression(o) + resource_ref = o.resources + raise "Unacceptable expression for resource override" unless resource_ref.is_a? Model::AccessExpression + + type = case resource_ref.left_expr + when Model::QualifiedName + # This is deprecated "Resource references should now be capitalized" - this is caught elsewhere + resource_ref.left_expr.value + when Model::QualifiedReference + resource_ref.left_expr.value + else + raise "Unacceptable expression for resource override; need NAME or CLASSREF" + end + + result_ref = ast o, AST::ResourceReference, :type => type, :title => transform(resource_ref.keys) + + # title is one or more expressions, if more than one it should be an ASTArray + ast o, AST::ResourceOverride, :object => result_ref, :parameters => transform(o.operations) + end + + # Parameter is a parameter in a definition of some kind. + # It is transformed to an array on the form `[name]´, or `[name, value]´. + def transform_Parameter(o) + if o.value + [o.name, transform(o.value)] + else + [o.name] + end + end + + # For non query expressions, parentheses can be dropped in the resulting AST. + def transform_ParenthesizedExpression(o) + transform(o.expr) + end + + def transform_IfExpression(o) + args = { :test => transform(o.test), :statements => transform(o.then_expr) } + args[:else] = transform(o.else_expr) # Tests say Nop should be there (unless is_nop? o.else_expr), probably not needed + result = ast o, AST::IfStatement, args + end + + # Unless is not an AST object, instead an AST::IfStatement is used with an AST::Not around the test + # + def transform_UnlessExpression(o) + args = { :test => ast(o, AST::Not, :value => transform(o.test)), + :statements => transform(o.then_expr) } + # AST 3.1 does not allow else on unless in the grammar, but it is ok since unless is encoded as a if !x + args.merge!({:else => transform(o.else_expr)}) unless is_nop?(o.else_expr) + result = ast o, AST::IfStatement, args + end + + # Puppet 3.1 AST only supports calling a function by name (it is not possible to produce a function + # that is then called). + # rval_required (for an expression) + # functor_expr (lhs - the "name" expression) + # arguments - list of arguments + # + def transform_CallNamedFunctionExpression(o) + name = o.functor_expr + raise "Unacceptable expression for name of function" unless name.is_a? Model::QualifiedName + args = { + :name => name.value, + :arguments => transform(o.arguments), + :ftype => o.rval_required ? :rvalue : :statement + } + args[:pblock] = transform(o.lambda) if o.lambda + ast o, AST::Function, args + end + + # Transformation of CallMethodExpression handles a NamedAccessExpression functor and + # turns this into a 3.1 AST::MethodCall. + # + def transform_CallMethodExpression(o) + name = o.functor_expr + raise "Unacceptable expression for name of function" unless name.is_a? Model::NamedAccessExpression + # transform of NamedAccess produces a hash, add arguments to it + astargs = transform(name).merge(:arguments => transform(o.arguments)) + astargs.merge!(:lambda => transform(o.lambda)) if o.lambda # do not want a Nop as the lambda + ast o, AST::MethodCall, astargs + + end + + def transform_CaseExpression(o) + # Expects expression, AST::ASTArray of AST + ast o, AST::CaseStatement, :test => transform(o.test), :options => transform(o.options) + end + + def transform_CaseOption(o) + ast o, AST::CaseOpt, :value => transform(o.values), :statements => transform(o.then_expr) + end + + def transform_ResourceBody(o) + # expects AST, AST::ASTArray of AST + ast o, AST::ResourceInstance, :title => transform(o.title), :parameters => transform(o.operations) + end + + def transform_ResourceDefaultsExpression(o) + ast o, AST::ResourceDefaults, :type => o.type_ref.value, :parameters => transform(o.operations) + end + + # Transformation of ResourceExpression requires calling a method on the resulting + # AST::Resource if it is virtual or exported + # + def transform_ResourceExpression(o) + raise "Unacceptable type name expression" unless o.type_name.is_a? Model::QualifiedName + resource = ast o, AST::Resource, :type => o.type_name.value, :instances => transform(o.bodies) + resource.send("#{o.form}=", true) unless o.form == :regular + resource + end + + # Transformation of SelectorExpression is limited to certain types of expressions. + # This is probably due to constraints in the old grammar rather than any real concerns. + def transform_SelectorExpression(o) + case o.left_expr + when Model::CallNamedFunctionExpression + when Model::AccessExpression + when Model::VariableExpression + when Model::ConcatenatedString + else + raise "Unacceptable select expression" unless o.left_expr.kind_of? Model::Literal + end + ast o, AST::Selector, :param => transform(o.left_expr), :values => transform(o.selectors) + end + + def transform_SelectorEntry(o) + ast o, AST::ResourceParam, :param => transform(o.matching_expr), :value => transform(o.value_expr) + end + + def transform_Object(o) + raise "Unacceptable transform - found an Object without a rule: #{o.class}" + end + + # Nil, nop + # Bee bopp a luh-lah, a bop bop boom. + # + def is_nop?(o) + o.nil? || o.is_a?(Model::Nop) + end +end diff --git a/lib/puppet/pops/model/ast_tree_dumper.rb b/lib/puppet/pops/model/ast_tree_dumper.rb new file mode 100644 index 000000000..ffe8fd848 --- /dev/null +++ b/lib/puppet/pops/model/ast_tree_dumper.rb @@ -0,0 +1,378 @@ +require 'puppet/parser/ast' + +# Dumps a Pops::Model in reverse polish notation; i.e. LISP style +# The intention is to use this for debugging output +# TODO: BAD NAME - A DUMP is a Ruby Serialization +# +class Puppet::Pops::Model::AstTreeDumper < Puppet::Pops::Model::TreeDumper + AST = Puppet::Parser::AST + Model = Puppet::Pops::Model + + def dump_LiteralNumber o + case o.radix + when 10 + o.value.to_s + when 8 + "0%o" % o.value + when 16 + "0x%X" % o.value + else + "bad radix:" + o.value.to_s + end + end + + def dump_Factory o + do_dump(o.current) + end + + def dump_ArithmeticOperator o + [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] + end + def dump_Relationship o + [o.arrow.to_s, do_dump(o.left), do_dump(o.right)] + end + + # Hostname is tricky, it is either a bare word, a string, or default, or regular expression + # Least evil, all strings except default are quoted + def dump_HostName o + result = do_dump o.value + unless o.value.is_a? AST::Regex + result = result == "default" ? ":default" : "'#{result}'" + end + result + end + + # x[y] prints as (slice x y) + def dump_HashOrArrayAccess o + var = o.variable.is_a?(String) ? "$#{o.variable}" : do_dump(o.variable) + ["slice", var, do_dump(o.key)] + end + + # The AST Collection knows about exported or virtual query, not the query. + def dump_Collection o + result = ["collect", do_dump(o.type), :indent, :break] + if o.form == :virtual + q = ["<| |>"] + else + q = ["<<| |>>"] + end + q << do_dump(o.query) unless is_nop?(o.query) + q << :indent + result << q + o.override do |ao| + result << :break << do_dump(ao) + end + result += [:dedent, :dedent ] + result + end + + def dump_CollExpr o + operator = case o.oper + when 'and' + '&&' + when 'or' + '||' + else + o.oper + end + [operator, do_dump(o.test1), do_dump(o.test2)] + end + + def dump_ComparisonOperator o + [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] + end + + def dump_Boolean o + o.to_s + end + + def dump_BooleanOperator o + operator = o.operator == 'and' ? '&&' : '||' + [operator, do_dump(o.lval), do_dump(o.rval)] + end + + def dump_InOperator o + ["in", do_dump(o.lval), do_dump(o.rval)] + end + + # $x = ... + # $x += ... + # + def dump_VarDef o + operator = o.append ? "+=" : "=" + [operator, '$' + do_dump(o.name), do_dump(o.value)] + end + + # Produces (name => expr) or (name +> expr) + def dump_ResourceParam o + operator = o.add ? "+>" : "=>" + [do_dump(o.param), operator, do_dump(o.value)] + end + + def dump_Array o + o.collect {|e| do_dump(e) } + end + + def dump_ASTArray o + ["[]"] + o.children.collect {|x| do_dump(x)} + end + + def dump_ASTHash o + ["{}"] + o.value.sort_by{|k,v| k.to_s}.collect {|x| [do_dump(x[0]), do_dump(x[1])]} +# ["{}"] + o.value.collect {|x| [do_dump(x[0]), do_dump(x[1])]} + end + + def dump_MatchOperator o + [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] + end + + # Dump a Ruby String in single quotes unless it is a number. + def dump_String o + + if o.is_a? String + o # A Ruby String, not quoted + elsif n = Puppet::Pops::Utils.to_n(o.value) + o.value # AST::String that is a number without quotes + else + "'#{o.value}'" # AST::String that is not a number + end + end + + def dump_Lambda o + result = ["lambda"] + result << ["parameters"] + o.parameters.collect {|p| _dump_ParameterArray(p) } if o.parameters.size() > 0 + if o.children == [] + result << [] # does not have a lambda body + else + result << do_dump(o.children) + end + result + end + + def dump_Default o + ":default" + end + + def dump_Undef o + ":undef" + end + + # Note this is Regex (the AST kind), not Ruby Regexp + def dump_Regex o + "/#{o.value.source}/" + end + + def dump_Nop o + ":nop" + end + + def dump_NilClass o + "()" + end + + def dump_Not o + ['!', dump(o.value)] + end + + def dump_Variable o + "$#{dump(o.value)}" + end + + def dump_Minus o + ['-', do_dump(o.value)] + end + + def dump_BlockExpression o + ["block"] + o.children.collect {|x| do_dump(x) } + end + + # Interpolated strings are shown as (cat seg0 seg1 ... segN) + def dump_Concat o + ["cat"] + o.value.collect {|x| x.is_a?(AST::String) ? " "+do_dump(x) : ["str", do_dump(x)]} + end + + def dump_Hostclass o + # ok, this is kind of crazy stuff in the AST, information in a context instead of in AST, and + # parameters are in a Ruby Array with each parameter being an Array... + # + context = o.context + args = context[:arguments] + parent = context[:parent] + result = ["class", o.name] + result << ["inherits", parent] if parent + result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0 + if is_nop?(o.code) + result << [] + else + result << do_dump(o.code) + end + result + end + + def dump_Name o + o.value + end + + def dump_Node o + context = o.context + parent = context[:parent] + code = context[:code] + + result = ["node"] + result << ["matches"] + o.names.collect {|m| do_dump(m) } + result << ["parent", do_dump(parent)] if !is_nop?(parent) + if is_nop?(code) + result << [] + else + result << do_dump(code) + end + result + end + + def dump_Definition o + # ok, this is even crazier that Hostclass. The name of the define does not have an accessor + # and some things are in the context (but not the name). Parameters are called arguments and they + # are in a Ruby Array where each parameter is an array of 1 or 2 elements. + # + context = o.context + name = o.instance_variable_get("@name") + args = context[:arguments] + code = context[:code] + result = ["define", name] + result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0 + if is_nop?(code) + result << [] + else + result << do_dump(code) + end + result + end + + def dump_ResourceReference o + result = ["slice", do_dump(o.type)] + if o.title.children.size == 1 + result << do_dump(o.title[0]) + else + result << do_dump(o.title.children) + end + result + end + + def dump_ResourceOverride o + result = ["override", do_dump(o.object), :indent] + o.parameters.each do |p| + result << :break << do_dump(p) + end + result << :dedent + result + end + + # Puppet AST encodes a parameter as a one or two slot Array. + # This is not a polymorph dump method. + # + def _dump_ParameterArray o + if o.size == 2 + ["=", o[0], do_dump(o[1])] + else + o[0] + end + end + + def dump_IfStatement o + result = ["if", do_dump(o.test), :indent, :break, + ["then", :indent, do_dump(o.statements), :dedent]] + result += + [:break, + ["else", :indent, do_dump(o.else), :dedent], + :dedent] unless is_nop? o.else + result + end + + # Produces (invoke name args...) when not required to produce an rvalue, and + # (call name args ... ) otherwise. + # + def dump_Function o + # somewhat ugly as Function hides its "ftype" instance variable + result = [o.instance_variable_get("@ftype") == :rvalue ? "call" : "invoke", do_dump(o.name)] + o.arguments.collect {|a| result << do_dump(a) } + result << do_dump(o.pblock) if o.pblock + result + end + + def dump_MethodCall o + # somewhat ugly as Method call (does the same as function) and hides its "ftype" instance variable + result = [o.instance_variable_get("@ftype") == :rvalue ? "call-method" : "invoke-method", + [".", do_dump(o.receiver), do_dump(o.name)]] + o.arguments.collect {|a| result << do_dump(a) } + result << do_dump(o.lambda) if o.lambda + result + end + + def dump_CaseStatement o + result = ["case", do_dump(o.test), :indent] + o.options.each do |s| + result << :break << do_dump(s) + end + result << :dedent + end + + def dump_CaseOpt o + result = ["when"] + result << o.value.collect {|x| do_dump(x) } + # A bit of trickery to get it into the same shape as Pops output + if is_nop?(o.statements) + result << ["then", []] # Puppet AST has a nop if there is no body + else + result << ["then", do_dump(o.statements) ] + end + result + end + + def dump_ResourceInstance o + result = [do_dump(o.title), :indent] + o.parameters.each do |p| + result << :break << do_dump(p) + end + result << :dedent + result + end + + def dump_ResourceDefaults o + result = ["resource-defaults", do_dump(o.type), :indent] + o.parameters.each do |p| + result << :break << do_dump(p) + end + result << :dedent + result + end + + def dump_Resource o + if o.exported + form = 'exported-' + elsif o.virtual + form = 'virtual-' + else + form = '' + end + result = [form+"resource", do_dump(o.type), :indent] + o.instances.each do |b| + result << :break << do_dump(b) + end + result << :dedent + result + end + + def dump_Selector o + values = o.values + values = [values] unless values.instance_of? AST::ASTArray or values.instance_of? Array + ["?", do_dump(o.param)] + values.collect {|x| do_dump(x) } + end + + def dump_Object o + ['dev-error-no-polymorph-dump-for:', o.class.to_s, o.to_s] + end + + def is_nop? o + o.nil? || o.is_a?(Model::Nop) || o.is_a?(AST::Nop) + end +end diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb new file mode 100644 index 000000000..835a3415c --- /dev/null +++ b/lib/puppet/pops/model/factory.rb @@ -0,0 +1,804 @@ +# Factory is a helper class that makes construction of a Pops Model +# much more convenient. It can be viewed as a small internal DSL for model +# constructions. +# For usage see tests using the factory. +# +# @todo All those uppercase methods ... they look bad in one way, but stand out nicely in the grammar... +# decide if they should change into lower case names (some of the are lower case)... +# +class Puppet::Pops::Model::Factory + Model = Puppet::Pops::Model + + attr_accessor :current + + # Shared build_visitor, since there are many instances of Factory being used + @@build_visitor = Puppet::Pops::Visitor.new(self, "build") + # Initialize a factory with a single object, or a class with arguments applied to build of + # created instance + # + def initialize popsobj, *args + @current = to_ops(popsobj, *args) + end + + # Polymorphic build + def build(o, *args) + begin + @@build_visitor.visit_this(self, o, *args) + rescue =>e + # require 'debugger'; debugger # enable this when in trouble... + raise e + end + end + + # Building of Model classes + + def build_ArithmeticExpression(o, op, a, b) + o.operator = op + build_BinaryExpression(o, a, b) + end + + def build_AssignmentExpression(o, op, a, b) + o.operator = op + build_BinaryExpression(o, a, b) + end + + def build_AttributeOperation(o, name, op, value) + o.operator = op + o.attribute_name = name.to_s # BOOLEAN is allowed in the grammar + o.value_expr = build(value) + o + end + + def build_AccessExpression(o, left, *keys) + o.left_expr = to_ops(left) + keys.each {|expr| o.addKeys(to_ops(expr)) } + o + end + + def build_BinaryExpression(o, left, right) + o.left_expr = to_ops(left) + o.right_expr = to_ops(right) + o + end + + def build_BlockExpression(o, *args) + args.each {|expr| o.addStatements(to_ops(expr)) } + o + end + + def build_CollectExpression(o, type_expr, query_expr, attribute_operations) + o.type_expr = to_ops(type_expr) + o.query = build(query_expr) + attribute_operations.each {|op| o.addOperations(build(op)) } + o + end + + def build_ComparisonExpression(o, op, a, b) + o.operator = op + build_BinaryExpression(o, a, b) + end + + def build_ConcatenatedString(o, *args) + args.each {|expr| o.addSegments(build(expr)) } + o + end + + def build_CreateTypeExpression(o, name, super_name = nil) + o.name = name + o.super_name = super_name + o + end + + def build_CreateEnumExpression(o, *args) + o.name = args.slice(0) if args.size == 2 + o.values = build(args.last) + o + end + + def build_CreateAttributeExpression(o, name, datatype_expr) + o.name = name + o.type = to_ops(datatype_expr) + o + end + + # @param name [String] a valid classname + # @param parameters [Array<Model::Parameter>] may be empty + # @param parent_class_name [String, nil] a valid classname referencing a parent class, optional. + # @param body [Array<Expression>, Expression, nil] expression that constitute the body + # @return [Model::HostClassDefinition] configured from the parameters + # + def build_HostClassDefinition(o, name, parameters, parent_class_name, body) + build_NamedDefinition(o, name, parameters, body) + o.parent_class = parent_class_name if parent_class_name + o + end + + # # @param name [String] a valid classname + # # @param parameters [Array<Model::Parameter>] may be empty + # # @param body [Array<Expression>, Expression, nil] expression that constitute the body + # # @return [Model::HostClassDefinition] configured from the parameters + # # + # def build_ResourceTypeDefinition(o, name, parameters, body) + # build_NamedDefinition(o, name, parameters, body) + # o.name = name + # parameters.each {|p| o.addParameters(build(p)) } + # b = f_build_body(body) + # o.body = b.current if b + # o + # end + + def build_ResourceOverrideExpression(o, resources, attribute_operations) + o.resources = build(resources) + attribute_operations.each {|ao| o.addOperations(build(ao)) } + o + end + + def build_KeyedEntry(o, k, v) + o.key = build(k) + o.value = build(v) + o + end + + def build_LiteralHash(o, *keyed_entries) + keyed_entries.each {|entry| o.addEntries build(entry) } + o + end + + def build_LiteralList(o, *values) + values.each {|v| o.addValues build(v) } + o + end + + def build_LiteralNumber(o, val, radix) + o.value = val + o.radix = radix + o + end + + def build_InstanceReferences(o, type_name, name_expressions) + o.type_name = build(type_name) + name_expressions.each {|n| o.addNames(build(n)) } + o + end + + def build_ImportExpression(o, files) + # The argument files has already been built + files.each {|f| o.addFiles(to_ops(f)) } + o + end + + def build_IfExpression(o, t, ift, els) + o.test = build(t) + o.then_expr = build(ift) + o.else_expr= build(els) + o + end + + def build_MatchExpression(o, op, a, b) + o.operator = op + build_BinaryExpression(o, a, b) + end + + # Builds body :) from different kinds of input + # @param body [nil] unchanged, produces nil + # @param body [Array<Expression>] turns into a BlockExpression + # @param body [Expression] produces the given expression + # @param body [Object] produces the result of calling #build with body as argument + def f_build_body(body) + case body + when NilClass + nil + when Array + Puppet::Pops::Model::Factory.new(Model::BlockExpression, *body) + else + build(body) + end + end + + def build_Definition(o, parameters, body) + parameters.each {|p| o.addParameters(build(p)) } + b = f_build_body(body) + o.body = b.current if b + o + end + + def build_NamedDefinition(o, name, parameters, body) + build_Definition(o, parameters, body) + o.name = name + o + end + + # @param o [Model::NodeDefinition] + # @param hosts [Array<Expression>] host matches + # @param parent [Expression] parent node matcher + # @param body [Object] see {#f_build_body} + def build_NodeDefinition(o, hosts, parent, body) + hosts.each {|h| o.addHost_matches(build(h)) } + o.parent = build(parent) if parent # no nop here + b = f_build_body(body) + o.body = b.current if b + o + end + + def build_Parameter(o, name, expr) + o.name = name + o.value = build(expr) if expr # don't build a nil/nop + o + end + + def build_QualifiedReference(o, name) + o.value = name.to_s.downcase + o + end + + def build_RelationshipExpression(o, op, a, b) + o.operator = op + build_BinaryExpression(o, a, b) + end + + def build_ResourceExpression(o, type_name, bodies) + o.type_name = build(type_name) + bodies.each {|b| o.addBodies(build(b)) } + o + end + + def build_ResourceBody(o, title_expression, attribute_operations) + o.title = build(title_expression) + attribute_operations.each {|ao| o.addOperations(build(ao)) } + o + end + + def build_ResourceDefaultsExpression(o, type_ref, attribute_operations) + o.type_ref = build(type_ref) + attribute_operations.each {|ao| o.addOperations(build(ao)) } + o + end + + def build_SelectorExpression(o, left, *selectors) + o.left_expr = to_ops(left) + selectors.each {|s| o.addSelectors(build(s)) } + o + end + + def build_SelectorEntry(o, matching, value) + o.matching_expr = build(matching) + o.value_expr = build(value) + o + end + + def build_QueryExpression(o, expr) + ops = to_ops(expr) + o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops + o + end + + def build_UnaryExpression(o, expr) + ops = to_ops(expr) + o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops + o + end + + def build_QualifiedName(o, name) + o.value = name.to_s + o + end + + # Puppet::Pops::Model::Factory helpers + def f_build_unary(klazz, expr) + Puppet::Pops::Model::Factory.new(build(klazz.new, expr)) + end + + def f_build_binary_op(klazz, op, left, right) + Puppet::Pops::Model::Factory.new(build(klazz.new, op, left, right)) + end + + def f_build_binary(klazz, left, right) + Puppet::Pops::Model::Factory.new(build(klazz.new, left, right)) + end + + def f_build_vararg(klazz, left, *arg) + Puppet::Pops::Model::Factory.new(build(klazz.new, left, *arg)) + end + + def f_arithmetic(op, r) + f_build_binary_op(Model::ArithmeticExpression, op, current, r) + end + + def f_comparison(op, r) + f_build_binary_op(Model::ComparisonExpression, op, current, r) + end + + def f_match(op, r) + f_build_binary_op(Model::MatchExpression, op, current, r) + end + + # Operator helpers + def in(r) f_build_binary(Model::InExpression, current, r); end + + def or(r) f_build_binary(Model::OrExpression, current, r); end + + def and(r) f_build_binary(Model::AndExpression, current, r); end + + def not(); f_build_unary(Model::NotExpression, self); end + + def minus(); f_build_unary(Model::UnaryMinusExpression, self); end + + def text(); f_build_unary(Model::TextExpression, self); end + + def var(); f_build_unary(Model::VariableExpression, self); end + + def [](*r); f_build_vararg(Model::AccessExpression, current, *r); end + + def dot r; f_build_binary(Model::NamedAccessExpression, current, r); end + + def + r; f_arithmetic(:+, r); end + + def - r; f_arithmetic(:-, r); end + + def / r; f_arithmetic(:/, r); end + + def * r; f_arithmetic(:*, r); end + + def % r; f_arithmetic(:%, r); end + + def << r; f_arithmetic(:<<, r); end + + def >> r; f_arithmetic(:>>, r); end + + def < r; f_comparison(:<, r); end + + def <= r; f_comparison(:<=, r); end + + def > r; f_comparison(:>, r); end + + def >= r; f_comparison(:>=, r); end + + def == r; f_comparison(:==, r); end + + def ne r; f_comparison(:'!=', r); end + + def =~ r; f_match(:'=~', r); end + + def mne r; f_match(:'!~', r); end + + def paren(); f_build_unary(Model::ParenthesizedExpression, current); end + + def relop op, r + f_build_binary_op(Model::RelationshipExpression, op.to_sym, current, r) + end + + def select *args + Puppet::Pops::Model::Factory.new(build(Model::SelectorExpression, current, *args)) + end + + # For CaseExpression, setting the default for an already build CaseExpression + def default r + current.addOptions(Puppet::Pops::Model::Factory.WHEN(:default, r).current) + self + end + + def lambda=(lambda) + current.lambda = lambda.current + self + end + + # Assignment = + def set(r) + f_build_binary_op(Model::AssignmentExpression, :'=', current, r) + end + + # Assignment += + def plus_set(r) + f_build_binary_op(Model::AssignmentExpression, :'+=', current, r) + end + + def attributes(*args) + args.each {|a| current.addAttributes(build(a)) } + self + end + + # Catch all delegation to current + def method_missing(meth, *args, &block) + if current.respond_to?(meth) + current.send(meth, *args, &block) + else + super + end + end + + def respond_to?(meth) + current.respond_to?(meth) || super + end + + # Records the position (start -> end) and computes the resulting length. + # + def record_position(start_pos, end_pos) + Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) do |a| + a.line = start_pos.line + a.offset = start_pos.offset + a.pos = start_pos.pos + a.length = start_pos.length + if(end_pos.offset && end_pos.length) + a.length = end_pos.offset + end_pos.length - start_pos.offset + end + end + self + end + + # Records the origin file of an element + # Does nothing if file is nil. + # + # @param file [String,nil] the file/path to the origin, may contain URI scheme of file: or some other URI scheme + # @returns [Factory] returns self + # + def record_origin(file) + return self unless file + Puppet::Pops::Adapters::OriginAdapter.adapt(current) do |a| + a.origin = file + end + self + end + + # @return [Puppet::Pops::Adapters::SourcePosAdapter] with location information + def loc() + Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) + end + + # Returns documentation string, or nil if not available + # @return [String, nil] associated documentation if available + def doc() + a = Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) + return a.documentation if a + nil + end + + def doc=(doc_string) + a = Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) + a.documentation = doc_string + end + + # Returns symbolic information about a expected share of a resource expression given the LHS of a resource expr. + # + # * `name { }` => `:resource`, create a resource of the given type + # * `Name { }` => ':defaults`, set defauls for the referenced type + # * `Name[] { }` => `:override`, ioverrides nstances referenced by LHS + # * _any other_ => ':error', all other are considered illegal + # + def self.resource_shape(expr) + expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) + case expr + when Model::QualifiedName + :resource + when Model::QualifiedReference + :defaults + when Model::AccessExpression + :override + when 'class' + :class + else + :error + end + end + # Factory starting points + + def self.literal(o); new(o); end + + def self.minus(o); new(o).minus; end + + def self.var(o); new(o).var; end + + def self.block(*args); new(Model::BlockExpression, *args); end + + def self.string(*args); new(Model::ConcatenatedString, *args); end + + def self.text(o); new(o).text; end + + def self.IF(test_e,then_e,else_e); new(Model::IfExpression, test_e, then_e, else_e); end + + def self.UNLESS(test_e,then_e,else_e); new(Model::UnlessExpression, test_e, then_e, else_e); end + + def self.CASE(test_e,*options); new(Model::CaseExpression, test_e, *options); end + + def self.WHEN(values_list, block); new(Model::CaseOption, values_list, block); end + + def self.MAP(match, value); new(Model::SelectorEntry, match, value); end + + def self.TYPE(name, super_name=nil); new(Model::CreateTypeExpression, name, super_name); end + + def self.ATTR(name, type_expr=nil); new(Model::CreateAttributeExpression, name, type_expr); end + + def self.ENUM(*args); new(Model::CreateEnumExpression, *args); end + + def self.KEY_ENTRY(key, val); new(Model::KeyedEntry, key, val); end + + def self.HASH(entries); new(Model::LiteralHash, *entries); end + + def self.LIST(entries); new(Model::LiteralList, *entries); end + + def self.PARAM(name, expr=nil); new(Model::Parameter, name, expr); end + + def self.NODE(hosts, parent, body); new(Model::NodeDefinition, hosts, parent, body); end + + # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which + # case it is returned. + # + def self.fqn(o) + o = o.current if o.is_a?(Puppet::Pops::Model::Factory) + o = new(Model::QualifiedName, o) unless o.is_a? Model::QualifiedName + o + end + + # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which + # case it is returned. + # + def self.fqr(o) + o = o.current if o.is_a?(Puppet::Pops::Model::Factory) + o = new(Model::QualifiedReference, o) unless o.is_a? Model::QualifiedReference + o + end + + def self.TEXT(expr) + new(Model::TextExpression, expr) + end + + # TODO: This is the same a fqn factory method, don't know if callers to fqn and QNAME can live with the + # same result or not yet - refactor into one method when decided. + # + def self.QNAME(name) + new(Model::QualifiedName, name) + end + + # Convert input string to either a qualified name, or a LiteralNumber with radix + # + def self.QNAME_OR_NUMBER(name) + if n_radix = Puppet::Pops::Utils.to_n_with_radix(name) + new(Model::LiteralNumber, *n_radix) + else + new(Model::QualifiedName, name) + end + end + + def self.QREF(name) + new(Model::QualifiedReference, name) + end + + def self.VIRTUAL_QUERY(query_expr) + new(Model::VirtualQuery, query_expr) + end + + def self.EXPORTED_QUERY(query_expr) + new(Model::ExportedQuery, query_expr) + end + + # Used by regular grammar, egrammar creates an AccessExpression instead, and evaluation determines + # if access is to instances or something else. + # + def self.INSTANCE(type_name, name_expressions) + new(Model::InstanceReferences, type_name, name_expressions) + end + + def self.ATTRIBUTE_OP(name, op, expr) + new(Model::AttributeOperation, name, op, expr) + end + + def self.CALL_NAMED(name, rval_required, argument_list) + unless name.kind_of?(Model::PopsObject) + name = Puppet::Pops::Model::Factory.fqn(name) unless name.is_a?(Puppet::Pops::Model::Factory) + end + new(Model::CallNamedFunctionExpression, name, rval_required, *argument_list) + end + + def self.CALL_METHOD(functor, argument_list) + new(Model::CallMethodExpression, functor, true, nil, *argument_list) + end + + def self.COLLECT(type_expr, query_expr, attribute_operations) + new(Model::CollectExpression, Puppet::Pops::Model::Factory.fqr(type_expr), query_expr, attribute_operations) + end + + def self.IMPORT(files) + new(Model::ImportExpression, files) + end + + def self.NAMED_ACCESS(type_name, bodies) + new(Model::NamedAccessExpression, type_name, bodies) + end + + def self.RESOURCE(type_name, bodies) + new(Model::ResourceExpression, type_name, bodies) + end + + def self.RESOURCE_DEFAULTS(type_name, attribute_operations) + new(Model::ResourceDefaultsExpression, type_name, attribute_operations) + end + + def self.RESOURCE_OVERRIDE(resource_ref, attribute_operations) + new(Model::ResourceOverrideExpression, resource_ref, attribute_operations) + end + + def self.RESOURCE_BODY(resource_title, attribute_operations) + new(Model::ResourceBody, resource_title, attribute_operations) + end + + # Builds a BlockExpression if args size > 1, else the single expression/value in args + def self.block_or_expression(*args) + if args.size > 1 + new(Model::BlockExpression, *args) + else + new(args[0]) + end + end + + def self.HOSTCLASS(name, parameters, parent, body) + new(Model::HostClassDefinition, name, parameters, parent, body) + end + + def self.DEFINITION(name, parameters, body) + new(Model::ResourceTypeDefinition, name, parameters, body) + end + + def self.LAMBDA(parameters, body) + new(Model::LambdaExpression, parameters, body) + end + + def self.nop? o + o.nil? || o.is_a?(Puppet::Pops::Model::Nop) + end + + # Transforms an array of expressions containing literal name expressions to calls if followed by an + # expression, or expression list. Also transforms a "call" to `import` into an ImportExpression. + # + def self.transform_calls(expressions) + expressions.reduce([]) do |memo, expr| + expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) + name = memo[-1] + if name.is_a? Model::QualifiedName + if name.value() == 'import' + memo[-1] = Puppet::Pops::Model::Factory.IMPORT(expr.is_a?(Array) ? expr : [expr]) + else + memo[-1] = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr.is_a?(Array) ? expr : [expr]) + end + else + memo << expr + end + if expr.is_a?(Model::CallNamedFunctionExpression) + # patch expression function call to statement style + # TODO: This is kind of meaningless, but to make it compatible... + expr.rval_required = false + end + memo + end + + end + + # Building model equivalences of Ruby objects + # Allows passing regular ruby objects to the factory to produce instructions + # that when evaluated produce the same thing. + + def build_String(o) + x = Model::LiteralString.new + x.value = o; + x + end + + def build_NilClass(o) + x = Model::Nop.new + x + end + + def build_TrueClass(o) + x = Model::LiteralBoolean.new + x.value = o + x + end + + def build_FalseClass(o) + x = Model::LiteralBoolean.new + x.value = o + x + end + + def build_Fixnum(o) + x = Model::LiteralNumber.new + x.value = o; + x + end + + def build_Float(o) + x = Model::LiteralNumber.new + x.value = o; + x + end + + def build_Regexp(o) + x = Model::LiteralRegularExpression.new + x.value = o; + x + end + + # If building a factory, simply unwrap the model oject contained in the factory. + def build_Factory(o) + o.current + end + + # Creates a String literal, unless the symbol is one of the special :undef, or :default + # which instead creates a LiterlUndef, or a LiteralDefault. + def build_Symbol(o) + case o + when :undef + Model::LiteralUndef.new + when :default + Model::LiteralDefault.new + else + build_String(o.to_s) + end + end + + # Creates a LiteralList instruction from an Array, where the entries are built. + def build_Array(o) + x = Model::LiteralList.new + o.each { |v| x.addValues(build(v)) } + x + end + + # Create a LiteralHash instruction from a hash, where keys and values are built + # The hash entries are added in sorted order based on key.to_s + # + def build_Hash(o) + x = Model::LiteralHash.new + (o.sort_by {|k,v| k.to_s}).each {|k,v| x.addEntries(build(Model::KeyedEntry.new, k, v)) } + x + end + + # @param rval_required [Boolean] if the call must produce a value + def build_CallExpression(o, functor, rval_required, *args) + o.functor_expr = to_ops(functor) + o.rval_required = rval_required + args.each {|x| o.addArguments(to_ops(x)) } + o + end + + # # @param rval_required [Boolean] if the call must produce a value + # def build_CallNamedFunctionExpression(o, name, rval_required, *args) + # build_CallExpression(o, name, rval_required, *args) + ## o.functor_expr = build(name) + ## o.rval_required = rval_required + ## args.each {|x| o.addArguments(build(x)) } + # o + # end + + def build_CallMethodExpression(o, functor, rval_required, lambda, *args) + build_CallExpression(o, functor, rval_required, *args) + o.lambda = lambda + o + end + + def build_CaseExpression(o, test, *args) + o.test = build(test) + args.each {|opt| o.addOptions(build(opt)) } + o + end + + def build_CaseOption(o, value_list, then_expr) + value_list = [value_list] unless value_list.is_a? Array + value_list.each { |v| o.addValues(build(v)) } + b = f_build_body(then_expr) + o.then_expr = to_ops(b) if b + o + end + + # Build a Class by creating an instance of it, and then calling build on the created instance + # with the given arguments + def build_Class(o, *args) + build(o.new(), *args) + end + + # Checks if the object is already a model object, or build it + def to_ops(o, *args) + if o.kind_of?(Model::PopsObject) + o + else + build(o, *args) + end + end +end diff --git a/lib/puppet/pops/model/model.rb b/lib/puppet/pops/model/model.rb new file mode 100644 index 000000000..82a9490e7 --- /dev/null +++ b/lib/puppet/pops/model/model.rb @@ -0,0 +1,567 @@ +# +# The Puppet Pops Metamodel +# +# This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*). +# It describes a Metamodel containing DSL instructions, a description of PuppetType and related +# classes needed to evaluate puppet logic. +# The metamodel resembles the existing AST model, but it is a semantic model of instructions and +# the types that they operate on rather than a Abstract Syntax Tree, although closely related. +# +# The metamodel is anemic (has no behavior) except basic datatype and type +# assertions and reference/containment assertions. +# The metamodel is also a generalized description of the Puppet DSL to enable the +# same metamodel to be used to express Puppet DSL models (instances) with different semantics as +# the language evolves. +# +# The metamodel is concretized by a validator for a particular version of +# the Puppet DSL language. +# +# This metamodel is expressed using RGen. +# +# TODO: Anonymous Enums - probably ok, but they can be named (don't know if that is meaningsful) + +require 'rgen/metamodel_builder' + +module Puppet::Pops::Model + # A base class for modeled objects that makes them Visitable, and Adaptable. + # @todo currently includes Containment which will not be needed when the corresponding methods + # are added to RGen (in some version after 0.6.2). + # + class PopsObject < RGen::MetamodelBuilder::MMBase + include Puppet::Pops::Visitable + include Puppet::Pops::Adaptable + include Puppet::Pops::Containment + abstract + end + + # @abstract base class for expressions + class Expression < PopsObject + abstract + end + + # A Nop - the "no op" expression. + # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp + # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model. + # + class Nop < Expression + end + + # A binary expression is abstract and has a left and a right expression. The order of evaluation + # and semantics are determined by the concrete subclass. + # + class BinaryExpression < Expression + abstract + # + # @!attribute [rw] left_expr + # @return [Expression] + contains_one_uni 'left_expr', Expression, :lowerBound => 1 + contains_one_uni 'right_expr', Expression, :lowerBound => 1 + end + + # An unary expression is abstract and contains one expression. The semantics are determined by + # a concrete subclass. + # + class UnaryExpression < Expression + abstract + contains_one_uni 'expr', Expression, :lowerBound => 1 + end + + # A class that simply evaluates to the contained expression. + # It is of value in order to preserve user entered parentheses in transformations, and + # transformations from model to source. + # + class ParenthesizedExpression < UnaryExpression; end + + # An import of one or several files. + # + class ImportExpression < Expression + contains_many_uni 'files', Expression, :lowerBound => 1 + end + + # A boolean not expression, reversing the truth of the unary expr. + # + class NotExpression < UnaryExpression; end + + # An arithmetic expression reversing the polarity of the numeric unary expr. + # + class UnaryMinusExpression < UnaryExpression; end + + # An assignment expression assigns a value to the lval() of the left_expr. + # + class AssignmentExpression < BinaryExpression + has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=', :'+=']), :lowerBound => 1 + end + + # An arithmetic expression applies an arithmetic operator on left and right expressions. + # + class ArithmeticExpression < BinaryExpression + has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ]), :lowerBound => 1 + end + + # A relationship expression associates the left and right expressions + # + class RelationshipExpression < BinaryExpression + has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'->', :'<-', :'~>', :'<~']), :lowerBound => 1 + end + + # A binary expression, that accesses the value denoted by right in left. i.e. typically + # expressed concretely in a language as left[right]. + # + class AccessExpression < Expression + contains_one_uni 'left_expr', Expression, :lowerBound => 1 + contains_many_uni 'keys', Expression, :lowerBound => 1 + end + + # A comparison expression compares left and right using a comparison operator. + # + class ComparisonExpression < BinaryExpression + has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'==', :'!=', :'<', :'>', :'<=', :'>=' ]), :lowerBound => 1 + end + + # A match expression matches left and right using a matching operator. + # + class MatchExpression < BinaryExpression + has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'!~', :'=~']), :lowerBound => 1 + end + + # An 'in' expression checks if left is 'in' right + # + class InExpression < BinaryExpression; end + + # A boolean expression applies a logical connective operator (and, or) to left and right expressions. + # + class BooleanExpression < BinaryExpression + abstract + end + + # An and expression applies the logical connective operator and to left and right expression + # and does not evaluate the right expression if the left expression is false. + # + class AndExpression < BooleanExpression; end + + # An or expression applies the logical connective operator or to the left and right expression + # and does not evaluate the right expression if the left expression is true + # + class OrExpression < BooleanExpression; end + + # A literal list / array containing 0:M expressions. + # + class LiteralList < Expression + contains_many_uni 'values', Expression + end + + # A Keyed entry has a key and a value expression. It it typically used as an entry in a Hash. + # + class KeyedEntry < PopsObject + contains_one_uni 'key', Expression, :lowerBound => 1 + contains_one_uni 'value', Expression, :lowerBound => 1 + end + + # A literal hash is a collection of KeyedEntry objects + # + class LiteralHash < Expression + contains_many_uni 'entries', KeyedEntry + end + + # A block contains a list of expressions + # + class BlockExpression < Expression + contains_many_uni 'statements', Expression + end + + # A case option entry in a CaseStatement + # + class CaseOption < Expression + contains_many_uni 'values', Expression, :lowerBound => 1 + contains_one_uni 'then_expr', Expression, :lowerBound => 1 + end + + # A case expression has a test, a list of options (multi values => block map). + # One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing + # else matched. + # + class CaseExpression < Expression + contains_one_uni 'test', Expression, :lowerBound => 1 + contains_many_uni 'options', CaseOption + end + + # A query expression is an expression that is applied to some collection. + # The contained optional expression may contain different types of relational expressions depending + # on what the query is applied to. + # + class QueryExpression < Expression + abstract + contains_one_uni 'expr', Expression, :lowerBound => 0 + end + + # An exported query is a special form of query that searches for exported objects. + # + class ExportedQuery < QueryExpression + end + + # A virtual query is a special form of query that searches for virtual objects. + # + class VirtualQuery < QueryExpression + end + + # An attribute operation sets or appends a value to a named attribute. + # + class AttributeOperation < PopsObject + has_attr 'attribute_name', String, :lowerBound => 1 + has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=>', :'+>', ]), :lowerBound => 1 + contains_one_uni 'value_expr', Expression, :lowerBound => 1 + end + + # An optional attribute operation sets or appends a value to a named attribute unless + # the value is undef/nil in which case the opereration is a Nop. + # + # This is a new feature proposed to solve the undef as antimatter problem + # @note Currently Unused + # + class OptionalAttributeOperation < AttributeOperation + end + + # An object that collects stored objects from the central cache and returns + # them to the current host. Operations may optionally be applied. + # + class CollectExpression < Expression + contains_one_uni 'type_expr', Expression, :lowerBound => 1 + contains_one_uni 'query', QueryExpression, :lowerBound => 1 + contains_many_uni 'operations', AttributeOperation + end + + class Parameter < PopsObject + has_attr 'name', String, :lowerBound => 1 + contains_one_uni 'value', Expression + end + + # Abstract base class for definitions. + # + class Definition < Expression + abstract + contains_many_uni 'parameters', Parameter + contains_one_uni 'body', Expression + end + + # Abstract base class for named definitions. + class NamedDefinition < Definition + abstract + has_attr 'name', String, :lowerBound => 1 + end + + # A resource type definition (a 'define' in the DSL). + # + class ResourceTypeDefinition < NamedDefinition + # FUTURE + # contains_one_uni 'producer', Producer + end + + # A node definition matches hosts using Strings, or Regular expressions. It may inherit from + # a parent node (also using a String or Regular expression). + # + class NodeDefinition < Expression + contains_one_uni 'parent', Expression + contains_many_uni 'host_matches', Expression, :lowerBound => 1 + contains_one_uni 'body', Expression + end + + # A class definition + # + class HostClassDefinition < NamedDefinition + has_attr 'parent_class', String + end + + # i.e {|parameters| body } + class LambdaExpression < Definition; end + + # If expression. If test is true, the then_expr part should be evaluated, else the (optional) + # else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block. + # a 'then' is typically a Block. + # + class IfExpression < Expression + contains_one_uni 'test', Expression, :lowerBound => 1 + contains_one_uni 'then_expr', Expression, :lowerBound => 1 + contains_one_uni 'else_expr', Expression + end + + # An if expression with boolean reversed test. + # + class UnlessExpression < IfExpression + end + + # An abstract call. + # + class CallExpression < Expression + abstract + # A bit of a crutch; functions are either procedures (void return) or has an rvalue + # this flag tells the evaluator that it is a failure to call a function that is void/procedure + # where a value is expected. + # + has_attr 'rval_required', Boolean, :defaultValueLiteral => "false" + contains_one_uni 'functor_expr', Expression, :lowerBound => 1 + contains_many_uni 'arguments', Expression + contains_one_uni 'lambda', Expression + end + + # A function call where the functor_expr should evaluate to something callable. + # + class CallFunctionExpression < CallExpression; end + + # A function call where the given functor_expr should evaluate to the name + # of a function. + # + class CallNamedFunctionExpression < CallExpression; end + + # A method/function call where the function expr is a NamedAccess and with support for + # an optional lambda block + # + class CallMethodExpression < CallExpression + end + + # Abstract base class for literals. + # + class Literal < Expression + abstract + end + + # A literal value is an abstract value holder. The type of the contained value is + # determined by the concrete subclass. + # + class LiteralValue < Literal + abstract + has_attr 'value', Object, :lowerBound => 1 + end + + # A Regular Expression Literal. + # + class LiteralRegularExpression < LiteralValue; end + + # A Literal String + # + class LiteralString < LiteralValue; end + + # A literal text is like a literal string, but has other rules for escaped characters. It + # is used as part of a ConcatenatedString + # + class LiteralText < LiteralValue; end + + # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix. + # By default, a radix of 10 is used. + # + class LiteralNumber < LiteralValue + has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10" + end + + # The DSL `undef`. + # + class LiteralUndef < Literal; end + + # The DSL `default` + class LiteralDefault < Literal; end + + # DSL `true` or `false` + class LiteralBoolean < LiteralValue; end + + # A text expression is an interpolation of an expression. If the embedded expression is + # a QualifiedName, it it taken as a variable name and resolved. All other expressions are evaluated. + # The result is transformed to a string. + # + class TextExpression < UnaryExpression; end + + # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections + # should be LiteralString instances, and interpolated expressions should either be + # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression + # if such treatment is not needed. + # + class ConcatenatedString < Expression + contains_many_uni 'segments', Expression + end + + # A DSL NAME (one or multiple parts separated by '::'). + # + class QualifiedName < LiteralValue; end + + # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter). + # + class QualifiedReference < LiteralValue; end + + # A Variable expression looks up value of expr (some kind of name) in scope. + # The expression is typically a QualifiedName, or QualifiedReference. + # + class VariableExpression < UnaryExpression; end + + # A type reference is a reference to a type. + # + class TypeReference < Expression + contains_one_uni 'type_name', QualifiedReference, :lowerBound => 1 + end + + # An instance reference is a reference to one or many named instances of a particular type + # + class InstanceReferences < TypeReference + contains_many_uni 'names', Expression, :lowerBound => 1 + end + + # A resource body describes one resource instance + # + class ResourceBody < PopsObject + contains_one_uni 'title', Expression + contains_many_uni 'operations', AttributeOperation + end + + # An abstract resource describes the form of the resource (regular, virtual or exported) + # and adds convenience methods to ask if it is virtual or exported. + # All derived classes may not support all forms, and these needs to be validated + # + class AbstractResource < Expression + has_attr 'form', RGen::MetamodelBuilder::DataTypes::Enum.new([:regular, :virtual, :exported ]), :lowerBound => 1, :defaultValueLiteral => "regular" + has_attr 'virtual', Boolean, :derived => true + has_attr 'exported', Boolean, :derived => true + + module ClassModule + def virtual_derived + form == :virtual || form == :exported + end + + def exported_derived + form == :exported + end + end + + end + + # A resource expression is used to instantiate one or many resource. Resources may optionally + # be virtual or exported, an exported resource is always virtual. + # + class ResourceExpression < AbstractResource + contains_one_uni 'type_name', Expression, :lowerBound => 1 + contains_many_uni 'bodies', ResourceBody + end + + # A resource defaults sets defaults for a resource type. This class inherits from AbstractResource + # but does only support the :regular form (this is intentional to be able to produce better error messages + # when illegal forms are applied to a model. + # + class ResourceDefaultsExpression < AbstractResource + contains_one_uni 'type_ref', QualifiedReference + contains_many_uni 'operations', AttributeOperation + end + + # A resource override overrides already set values. + # + class ResourceOverrideExpression < Expression + contains_one_uni 'resources', Expression, :lowerBound => 1 + contains_many_uni 'operations', AttributeOperation + end + + # A selector entry describes a map from matching_expr to value_expr. + # + class SelectorEntry < PopsObject + contains_one_uni 'matching_expr', Expression, :lowerBound => 1 + contains_one_uni 'value_expr', Expression, :lowerBound => 1 + end + + # A selector expression represents a mapping from a left_expr to a matching SelectorEntry. + # + class SelectorExpression < Expression + contains_one_uni 'left_expr', Expression, :lowerBound => 1 + contains_many_uni 'selectors', SelectorEntry + end + + # Create Invariant. Future suggested enhancement Puppet Types. + # + class CreateInvariantExpression < Expression + has_attr 'name', String + contains_one_uni 'message_expr', Expression, :lowerBound => 1 + contains_one_uni 'constraint_expr', Expression, :lowerBound => 1 + end + + # Create Attribute. Future suggested enhancement Puppet Types. + # + class CreateAttributeExpression < Expression + has_attr 'name', String, :lowerBound => 1 + + # Should evaluate to name of datatype (String, Integer, Float, Boolean) or an EEnum metadata + # (created by CreateEnumExpression). If omitted, the type is a String. + # + contains_one_uni 'type', Expression + contains_one_uni 'min_expr', Expression + contains_one_uni 'max_expr', Expression + contains_one_uni 'default_value', Expression + contains_one_uni 'input_transformer', Expression + contains_one_uni 'derived_expr', Expression + end + + # Create Attribute. Future suggested enhancement Puppet Types. + # + class CreateEnumExpression < Expression + has_attr 'name', String + contains_one_uni 'values', Expression + end + + # Create Type. Future suggested enhancement Puppet Types. + # + class CreateTypeExpression < Expression + has_attr 'name', String, :lowerBound => 1 + has_attr 'super_name', String + contains_many_uni 'attributes', CreateAttributeExpression + contains_many_uni 'invariants', CreateInvariantExpression + end + + # Create ResourceType. Future suggested enhancement Puppet Types. + # @todo UNFINISHED + # + class CreateResourceType < CreateTypeExpression + # TODO CreateResourceType + # - has features required by the provider - provider invariant? + # - super type must be a ResourceType + end + + # A named access expression looks up a named part. (e.g. $a.b) + # + class NamedAccessExpression < BinaryExpression; end + + # A named function definition declares and defines a new function + # Future enhancement. + # + class NamedFunctionDefinition < NamedDefinition; end + + # Future enhancements - Injection - Unfinished + # + module Injection + # A producer expression produces an instance of a type. The instance is initialized + # from an expression (or from the current scope if this expression is missing). + #-- + # new. to handle production of injections + # + class Producer < Expression + contains_one_uni 'type_name', TypeReference, :lowerBound => 1 + contains_one_uni 'instantiation_expr', Expression + end + + # A binding entry binds one capability generically or named, specifies default bindings or + # composition of other bindings. + # + class BindingEntry < PopsObject + contains_one_uni 'key', Expression + contains_one_uni 'value', Expression + end + + # Defines an optionally named binding. + # + class Binding < Expression + contains_one_uni 'title_expr', Expression + contains_many_uni 'bindings', BindingEntry + end + + # An injection provides a value bound in the effective binding scope. The injection + # is based on a type (a capability) and an optional list of instance names (i.e. an InstanceReference). + # Invariants: optional and instantiation are mutually exclusive + # + class InjectExpression < Expression + has_attr 'optional', Boolean + contains_one_uni 'binding', Expression, :lowerBound => 1 + contains_one_uni 'instantiation', Expression + end + end +end diff --git a/lib/puppet/pops/model/model_label_provider.rb b/lib/puppet/pops/model/model_label_provider.rb new file mode 100644 index 000000000..b48e7b441 --- /dev/null +++ b/lib/puppet/pops/model/model_label_provider.rb @@ -0,0 +1,75 @@ +# A provider of labels for model object, producing a human name for the model object. +# As an example, if object is an ArithmeticExpression with operator +, `#a_an(o)` produces "a '+' Expression", +# #the(o) produces "the + Expression", and #label produces "+ Expression". +# +class Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider + def initialize + @@label_visitor ||= Puppet::Pops::Visitor.new(self,"label",0,0) + end + + # Produces a label for the given objects type/operator without article. + def label o + @@label_visitor.visit(o) + end + + def label_Factory o ; label(o.current) end + def label_Array o ; "Array Object" end + def label_LiteralNumber o ; "Literal Number" end + def label_ArithmeticExpression o ; "'#{o_operator}' expression" end + def label_AccessExpression o ; "'[]' expression" end + def label_MatchExpression o ; "'#{o.operator}' expression" end + def label_CollectExpression o ; label(o.query) end + def label_ExportedQuery o ; "Exported Query" end + def label_VirtualQuery o ; "Virtual Query" end + def label_QueryExpression o ; "Collect Query" end + def label_ComparisonExpression o ; "'#{o.operator}' expression" end + def label_AndExpression o ; "'and' expression" end + def label_OrExpression o ; "'or' expression" end + def label_InExpression o ; "'in' expression" end + def label_ImportExpression o ; "'import' expression" end + def label_InstanceReferences o ; "Resource Reference" end + def label_AssignmentExpression o ; "'#{o.operator}' expression" end + def label_AttributeOperation o ; "'#{o.operator}' expression" end + def label_LiteralList o ; "Array Expression" end + def label_LiteralHash o ; "Hash Expression" end + def label_KeyedEntry o ; "Hash Entry" end + def label_LiteralBoolean o ; "Boolean" end + def label_LiteralString o ; "String" end + def label_LiteralText o ; "Text in Interpolated String" end + def label_LambdaExpression o ; "Lambda" end + def label_LiteralDefault o ; "'default' expression" end + def label_LiteralUndef o ; "'undef' expression" end + def label_LiteralRegularExpression o ; "Regular Expression" end + def label_Nop o ; "Nop Expression" end + def label_NamedAccessExpression o ; "'.' expression" end + def label_NilClass o ; "Nil Object" end + def label_NotExpression o ; "'not' expression" end + def label_VariableExpression o ; "Variable" end + def label_TextExpression o ; "Expression in Interpolated String" end + def label_UnaryMinusExpression o ; "Unary Minus" end + def label_BlockExpression o ; "Block Expression" end + def label_ConcatenatedString o ; "Double Quoted String" end + def label_HostClassDefinition o ; "Host Class Definition" end + def label_NodeDefinition o ; "Node Definition" end + def label_ResourceTypeDefinition o ; "'define' expression" end + def label_ResourceOverrideExpression o ; "Resource Override" end + def label_Parameter o ; "Parameter Definition" end + def label_ParenthesizedExpression o ; "Parenthesized Expression" end + def label_IfExpression o ; "'if' statement" end + def label_UnlessExpression o ; "'unless' Statement" end + def label_CallNamedFunctionExpression o ; "Function Call" end + def label_CallMethodExpression o ; "Method call" end + def label_CaseExpression o ; "'case' statement" end + def label_CaseOption o ; "Case Option" end + def label_RelationshipExpression o ; "'#{o.operator}' expression" end + def label_ResourceBody o ; "Resource Instance Definition" end + def label_ResourceDefaultsExpression o ; "Resource Defaults Expression" end + def label_ResourceExpression o ; "Resource Statement" end + def label_SelectorExpression o ; "Selector Expression" end + def label_SelectorEntry o ; "Selector Option" end + def label_String o ; "Ruby String" end + def label_Object o ; "Ruby Object" end + def label_QualifiedName o ; "Name" end + def label_QualifiedReference o ; "Type Name" end + +end diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb new file mode 100644 index 000000000..24d341cda --- /dev/null +++ b/lib/puppet/pops/model/model_tree_dumper.rb @@ -0,0 +1,352 @@ +# Dumps a Pops::Model in reverse polish notation; i.e. LISP style +# The intention is to use this for debugging output +# TODO: BAD NAME - A DUMP is a Ruby Serialization +# +class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper + def dump_Array o + o.collect {|e| do_dump(e) } + end + + def dump_LiteralNumber o + case o.radix + when 10 + o.value.to_s + when 8 + "0%o" % o.value + when 16 + "0x%X" % o.value + else + "bad radix:" + o.value.to_s + end + end + + def dump_LiteralValue o + o.value.to_s + end + + def dump_Factory o + do_dump(o.current) + end + + def dump_ArithmeticExpression o + [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] + end + + # x[y] prints as (slice x y) + def dump_AccessExpression o + if o.keys.size <= 1 + ["slice", do_dump(o.left_expr), do_dump(o.keys[0])] + else + ["slice", do_dump(o.left_expr), do_dump(o.keys)] + end + end + + def dump_MatchesExpression o + [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_CollectExpression o + result = ["collect", do_dump(o.type_expr), :indent, :break, do_dump(o.query), :indent] + o.operations do |ao| + result << :break << do_dump(ao) + end + result += [:dedent, :dedent ] + result + end + + def dump_ExportedQuery o + result = ["<<| |>>"] + result += dump_QueryExpression(o) unless is_nop?(o.expr) + result + end + + def dump_VirtualQuery o + result = ["<| |>"] + result += dump_QueryExpression(o) unless is_nop?(o.expr) + result + end + + def dump_QueryExpression o + [do_dump(o.expr)] + end + + def dump_ComparisonExpression o + [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_AndExpression o + ["&&", do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_OrExpression o + ["||", do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_InExpression o + ["in", do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_ImportExpression o + ["import"] + o.files.collect {|f| do_dump(f) } + end + + def dump_InstanceReferences o + ["instances", do_dump(o.type_name)] + o.names.collect {|n| do_dump(n) } + end + + def dump_AssignmentExpression o + [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] + end + + # Produces (name => expr) or (name +> expr) + def dump_AttributeOperation o + [o.attribute_name, o.operator, do_dump(o.value_expr)] + end + + def dump_LiteralList o + ["[]"] + o.values.collect {|x| do_dump(x)} + end + + def dump_LiteralHash o + ["{}"] + o.entries.collect {|x| do_dump(x)} + end + + def dump_KeyedEntry o + [do_dump(o.key), do_dump(o.value)] + end + + def dump_MatchExpression o + [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_LiteralString o + "'#{o.value}'" + end + + def dump_LiteralText o + o.value + end + + def dump_LambdaExpression o + result = ["lambda"] + result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 + if o.body + result << do_dump(o.body) + else + result << [] + end + result + end + + def dump_LiteralDefault o + ":default" + end + + def dump_LiteralUndef o + ":undef" + end + + def dump_LiteralRegularExpression o + "/#{o.value.source}/" + end + + def dump_Nop o + ":nop" + end + + def dump_NamedAccessExpression o + [".", do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_NilClass o + "()" + end + + def dump_NotExpression o + ['!', dump(o.expr)] + end + + def dump_VariableExpression o + "$#{dump(o.expr)}" + end + + # Interpolation (to string) shown as (str expr) + def dump_TextExpression o + ["str", do_dump(o.expr)] + end + + def dump_UnaryMinusExpression o + ['-', do_dump(o.expr)] + end + + def dump_BlockExpression o + ["block"] + o.statements.collect {|x| do_dump(x) } + end + + # Interpolated strings are shown as (cat seg0 seg1 ... segN) + def dump_ConcatenatedString o + ["cat"] + o.segments.collect {|x| do_dump(x)} + end + + def dump_HostClassDefinition o + result = ["class", o.name] + result << ["inherits", o.parent_class] if o.parent_class + result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 + if o.body + result << do_dump(o.body) + else + result << [] + end + result + end + + def dump_NodeDefinition o + result = ["node"] + result << ["matches"] + o.host_matches.collect {|m| do_dump(m) } + result << ["parent", do_dump(o.parent)] if o.parent + if o.body + result << do_dump(o.body) + else + result << [] + end + result + end + + def dump_ResourceTypeDefinition o + result = ["define", o.name] + result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 + if o.body + result << do_dump(o.body) + else + result << [] + end + result + end + + def dump_ResourceOverrideExpression o + result = ["override", do_dump(o.resources), :indent] + o.operations.each do |p| + result << :break << do_dump(p) + end + result << :dedent + result + end + + # Produces parameters as name, or (= name value) + def dump_Parameter o + if o.value + ["=", o.name, do_dump(o.value)] + else + o.name + end + end + + def dump_ParenthesizedExpression o + do_dump(o.expr) + end + + def dump_IfExpression o + result = ["if", do_dump(o.test), :indent, :break, + ["then", :indent, do_dump(o.then_expr), :dedent]] + result += + [:break, + ["else", :indent, do_dump(o.else_expr), :dedent], + :dedent] unless is_nop? o.else_expr + result + end + + def dump_UnlessExpression o + result = ["unless", do_dump(o.test), :indent, :break, + ["then", :indent, do_dump(o.then_expr), :dedent]] + result += + [:break, + ["else", :indent, do_dump(o.else_expr), :dedent], + :dedent] unless is_nop? o.else_expr + result + end + + # Produces (invoke name args...) when not required to produce an rvalue, and + # (call name args ... ) otherwise. + # + def dump_CallNamedFunctionExpression o + result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] + o.arguments.collect {|a| result << do_dump(a) } + result + end + + # def dump_CallNamedFunctionExpression o + # result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] + # o.arguments.collect {|a| result << do_dump(a) } + # result + # end + + def dump_CallMethodExpression o + result = [o.rval_required ? "call-method" : "invoke-method", do_dump(o.functor_expr)] + o.arguments.collect {|a| result << do_dump(a) } + result << do_dump(o.lambda) if o.lambda + result + end + + def dump_CaseExpression o + result = ["case", do_dump(o.test), :indent] + o.options.each do |s| + result << :break << do_dump(s) + end + result << :dedent + end + + def dump_CaseOption o + result = ["when"] + result << o.values.collect {|x| do_dump(x) } + result << ["then", do_dump(o.then_expr) ] + result + end + + def dump_RelationshipExpression o + [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] + end + + def dump_ResourceBody o + result = [do_dump(o.title), :indent] + o.operations.each do |p| + result << :break << do_dump(p) + end + result << :dedent + result + end + + def dump_ResourceDefaultsExpression o + result = ["resource-defaults", do_dump(o.type_ref), :indent] + o.operations.each do |p| + result << :break << do_dump(p) + end + result << :dedent + result + end + + def dump_ResourceExpression o + form = o.form == :regular ? '' : o.form.to_s + "-" + result = [form+"resource", do_dump(o.type_name), :indent] + o.bodies.each do |b| + result << :break << do_dump(b) + end + result << :dedent + result + end + + def dump_SelectorExpression o + ["?", do_dump(o.left_expr)] + o.selectors.collect {|x| do_dump(x) } + end + + def dump_SelectorEntry o + [do_dump(o.matching_expr), "=>", do_dump(o.value_expr)] + end + + def dump_Object o + [o.class.to_s, o.to_s] + end + + def is_nop? o + o.nil? || o.is_a?(Puppet::Pops::Model::Nop) + end +end diff --git a/lib/puppet/pops/model/tree_dumper.rb b/lib/puppet/pops/model/tree_dumper.rb new file mode 100644 index 000000000..f0a7abaa7 --- /dev/null +++ b/lib/puppet/pops/model/tree_dumper.rb @@ -0,0 +1,59 @@ +# Base class for formatted textual dump of a "model" +# +class Puppet::Pops::Model::TreeDumper + attr_accessor :indent_count + def initialize initial_indentation = 0 + @@dump_visitor ||= Puppet::Pops::Visitor.new(nil,"dump",0,0) + @indent_count = initial_indentation + end + + def dump(o) + format(do_dump(o)) + end + + def do_dump(o) + @@dump_visitor.visit_this(self, o) + end + + def indent + " " * indent_count + end + + def format(x) + result = "" + parts = format_r(x) + parts.each_index do |i| + if i > 0 + # separate with space unless previous ends with whitepsace or ( + result << ' ' if parts[i] != ")" && parts[i-1] !~ /.*(?:\s+|\()$/ && parts[i] !~ /\s+/ + end + result << parts[i] + end + result + end + + def format_r(x) + result = [] + case x + when :break + result << "\n" + indent + when :indent + @indent_count += 1 + when :dedent + @indent_count -= 1 + when Array + result << '(' + result += x.collect {|a| format_r(a) }.flatten + result << ')' + when Symbol + result << x.to_s # Allows Symbols in arrays e.g. ["text", =>, "text"] + else + result << x + end + result + end + + def is_nop? o + o.nil? || o.is_a?(Puppet::Pops::Model::Nop) + end +end diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra new file mode 100644 index 000000000..bb6a5db32 --- /dev/null +++ b/lib/puppet/pops/parser/egrammar.ra @@ -0,0 +1,723 @@ +# vim: syntax=ruby + +# Parser using the Pops model, expression based + +class Puppet::Pops::Parser::Parser + +token STRING DQPRE DQMID DQPOST +token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE +token FALSE EQUALS APPENDS 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 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 LOW + +prechigh + left HIGH + left SEMIC + left PIPE + left LPAREN + left RPAREN + left AT + left DOT + left CALL + left LBRACK + left QMARK + left LCOLLECT LLCOLLECT + right NOT + 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 + right APPENDS EQUALS + left LBRACE + left SELBRACE + left RBRACE + left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB + left TITLE_COLON + left CASE_COLON + left FARROW + left COMMA + left LOW +preclow + +rule +# Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty +program + : statements { result = Factory.block_or_expression(*val[0]) } + | nil + +# Produces a semantic model (non validated, but semantically adjusted). +statements + : syntactic_statements { result = transform_calls(val[0]) } + +# Change may have issues with nil; i.e. program is a sequence of nils/nops +# Simplified from original which had validation for top level constructs - see statement rule +# Produces Array<Model::Expression> +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 +syntactic_statement + : any_expression { result = val[0] } + | syntactic_statement COMMA any_expression { result = aryfy(val[0]).push val[2] } + +any_expression + : relationship_expression + +relationship_expression + : resource_expression =LOW { result = val[0] } + | relationship_expression IN_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship_expression IN_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship_expression OUT_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship_expression OUT_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + +#---EXPRESSION +# +# Produces Model::Expression +expression + : higher_precedence + | 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 match_rvalue { result = val[0] =~ val[2] ; loc result, val[1] } + | expression NOMATCH match_rvalue { 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] } + | 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 EQUALS expression { result = val[0].set(val[2]) ; loc result, val[1] } + | expression APPENDS expression { result = val[0].plus_set(val[2]) ; loc result, val[1] } + | expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] } + | LPAREN expression RPAREN { result = val[1].paren() ; loc result, val[0] } + +#---EXPRESSIONS +# (e.g. argument list) +# +# This expression list can not contain function calls without parentheses around arguments +# Produces Array<Model::Expression> +expressions + : expression { result = [val[0]] } + | expressions COMMA expression { result = val[0].push(val[2]) } + +# These go through a chain of left recursion, ending with primary_expression +higher_precedence + : call_function_expression + +primary_expression + : literal_expression + | variable + | call_method_with_lambda_expression + | collection_expression + | case_expression + | if_expression + | unless_expression + | definition_expression + | hostclass_expression + | node_definition_expression + +# Aleways have the same value +literal_expression + : array + | boolean + | default + | hash + | regex + | text_or_name =LOW # resolves hash key ambiguity (racc W U require this?) + | type + | undef + +text_or_name + : name { result = val[0] } + | quotedtext { result = val[0] } + +#---CALL FUNCTION +# +# Produces Model::CallNamedFunction + +call_function_expression + : primary_expression LPAREN expressions endcomma RPAREN { + result = Factory.CALL_NAMED(val[0], true, val[2]) + loc result, val[0], val[4] + } + | primary_expression LPAREN RPAREN { + result = Factory.CALL_NAMED(val[0], true, []) + loc result, val[0], val[2] + } + | primary_expression LPAREN expressions endcomma RPAREN lambda { + result = Factory.CALL_NAMED(val[0], true, val[2]) + loc result, val[0], val[4] + result.lambda = val[5] + } + | primary_expression LPAREN RPAREN lambda { + result = Factory.CALL_NAMED(val[0], true, []) + loc result, val[0], val[2] + result.lambda = val[3] + } + | primary_expression = LOW { result = val[0] } + +#---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 expressions 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] } + + # TODO: It may be of value to access named elements of types too + named_access + : expression DOT NAME { + result = val[0].dot(Factory.fqn(val[2][:value])) + loc result, val[1], val[2] + } + +#---LAMBDA +# +# This is a temporary switch while experimenting with concrete syntax +# One should be picked for inclusion in puppet. +lambda + : lambda_j8 + | lambda_ruby + +# Java8-like lambda with parameters to the left of the body +lambda_j8 + : lambda_parameter_list optional_farrow lambda_rest { + result = Factory.LAMBDA(val[0], val[2]) +# loc result, val[1] # TODO + } + +lambda_rest + : LBRACE statements RBRACE { result = val[1] } + | LBRACE RBRACE { result = nil } + +optional_farrow + : nil + | FARROW + +# Ruby-like lambda with parameters inside the body +# +lambda_ruby + : LAMBDA lambda_parameter_list statements RBRACE { + result = Factory.LAMBDA(val[1], val[2]) + loc result, val[0], val[3] + } + | LAMBDA lambda_parameter_list RBRACE { + result = Factory.LAMBDA(val[1], nil) + loc result, val[0], val[2] + } + +# Produces Array<Model::Parameter> +lambda_parameter_list +: PIPE PIPE { result = [] } +| PIPE parameters endcomma PIPE { result = val[1] } + +#---CONDITIONALS +# + +#--IF +# +# Produces Model::IfExpression +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 +# +# Changed from Puppet 3x where there is no else part on 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) + 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 +# +# Produces Model::CaseExpression +case_expression + : CASE expression LBRACE case_options RBRACE { + result = Factory.CASE(val[1], *val[3]) + loc result, val[0], val[4] + } + + # Produces Array<Model::CaseOption> + 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 case_colon LBRACE statements RBRACE { + result = Factory.WHEN(val[0], val[3]) + loc result, val[1], val[4] + } + | expressions case_colon LBRACE RBRACE = LOW { + result = Factory.WHEN(val[0], nil) + loc result, val[1], val[3] + } + + case_colon: COLON =CASE_COLON { result = val[0] } + + # 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<Model::SelectorEntry> + # + selector_entries + : selector_entry + | SELBRACE selector_entry_list endcomma RBRACE { + result = val[1] + } + + # Produces Array<Model::SelectorEntry> + 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] } + +# --- IMPORT +# IMPORT is handled as a non parenthesized call and is transformed to an ImportExpression. +# i.e. there is no special grammar for it - it is just a "call statement". + +#---RESOURCE +# +# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] + +# The resource expression parses a generalized syntax and then selects the correct +# resulting model based on the combinatoin of the LHS and what follows. +# It also handled exported and virtual resources, and the class case +# +resource_expression + : expression =LOW { + result = val[0] + } + | at expression LBRACE resourceinstances endsemi RBRACE { + result = case Factory.resource_shape(val[1]) + when :resource, :class + tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) + tmp.form = val[0] + tmp + when :defaults + error "A resource default can not be virtual or exported" + when :override + error "A resource override can not be virtual or exported" + else + error "Expression is not valid as a resource, resource-default, or resource-override" + end + loc result, val[1], val[4] + } + | at expression LBRACE attribute_operations endcomma RBRACE { + result = case Factory.resource_shape(val[1]) + when :resource, :class + error "Defaults are not virtualizable" + when :defaults + error "Defaults are not virtualizable" + when :override + error "Defaults are not virtualizable" + else + error "Expression is not valid as a resource, resource-default, or resource-override" + end + } + | expression LBRACE resourceinstances endsemi RBRACE { + result = case Factory.resource_shape(val[0]) + when :resource, :class + Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) + when :defaults + error "A resource default can not specify a resource name" + when :override + error "A resource override does not allow override of name of resource" + else + error "Expression is not valid as a resource, resource-default, or resource-override" + end + loc result, val[0], val[4] + } + | expression LBRACE attribute_operations endcomma RBRACE { + result = case Factory.resource_shape(val[0]) + when :resource, :class + # This catches deprecated syntax. + error "All resource specifications require names" + when :defaults + Factory.RESOURCE_DEFAULTS(val[0], val[2]) + when :override + # This was only done for override in original - TODO shuld it be here at all + Factory.RESOURCE_OVERRIDE(val[0], val[2]) + else + error "Expression is not valid as a resource, resource-default, or resource-override" + end + loc result, val[0], val[4] + } + | CLASS LBRACE resourceinstances endsemi RBRACE { + result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) + loc result, val[0], val[4] + } + + resourceinst + : expression title_colon attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } + + title_colon : COLON =TITLE_COLON { result = val[0] } + + resourceinstances + : resourceinst { result = [val[0]] } + | resourceinstances SEMIC resourceinst { result = val[0].push val[2] } + + # Produces Symbol corresponding to resource form + # + at + : AT { result = :virtual } + | AT AT { result = :exported } + +#---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 for source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) +# +# Produces Model::CollectExpression +# +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) +# +# Produces Array<Model::AttributeOperation> +# +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] + } + +#---DEFINE +# +# Produces Model::Definition +# +definition_expression + : DEFINE classname parameter_list LBRACE statements RBRACE { + result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) + loc result, val[0], val[5] + @lexer.indefine = false + } + | DEFINE classname parameter_list LBRACE RBRACE { + result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) + loc result, val[0], val[4] + @lexer.indefine = false + } + +#---HOSTCLASS +# ORIGINAL COMMENT: Our class gets defined in the parent namespace, not our own. +# WAT ??! This is way odd; should get its complete name, classnames do not nest +# Seems like the call to classname makes use of the name scope +# (This is uneccesary, since the parent name is known when evaluating) +# +# Produces Model::HostClassDefinition +# +hostclass_expression + : CLASS classname parameter_list classparent LBRACE statements RBRACE { + @lexer.namepop + result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) + loc result, val[0], val[6] + } + | CLASS classname parameter_list classparent LBRACE RBRACE { + @lexer.namepop + result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) + loc result, val[0], val[5] + } + + # 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 nodeparent LBRACE statements RBRACE { + result = Factory.NODE(val[1], val[2], val[4]) + loc result, val[0], val[5] + } + | NODE hostnames nodeparent LBRACE RBRACE { + result = Factory.NODE(val[1], val[2], nil) + loc result, val[0], val[4] + } + + # 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<Model::LiteralExpression> + # + 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 + : NAME { result = Factory.fqn(val[0][:value]); loc result, val[0] } + | quotedtext { result = val[0] } + | DEFAULT { result = Factory.literal(:default); loc result, val[0] } + | regex + + # Produces Expression, since hostname is an Expression + nodeparent + : nil + | INHERITS hostname { result = val[1] } + +#---NAMES AND PARAMTERS COMMON TO SEVERAL RULES +# String result +classname + : NAME { result = val[0] } + | CLASS { result = val[0] } + +# Produces Array<Model::Parameter> +parameter_list + : nil { result = [] } + | LPAREN RPAREN { result = [] } + | LPAREN parameters endcomma RPAREN { result = val[1] } + +# Produces Array<Model::Parameter> +parameters + : parameter { result = [val[0]] } + | parameters COMMA parameter { result = val[0].push(val[2]) } + +# Produces Model::Parameter +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] } + +#--RESTRICTED EXPRESSIONS +# i.e. where one could have expected an expression, but the set is limited + +# What is allowed RHS of match operators (see expression) +match_rvalue + : regex + | text_or_name + +#--VARIABLE +# +variable + : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } + +#---LITERALS (dynamic and static) +# + +array + : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } + | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } + | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] } + +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[3] } + + hashpairs + : hashpair { result = [val[0]] } + | hashpairs COMMA hashpair { result = val[0].push val[2] } + + hashpair + : text_or_name FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } + +quotedtext + : string + | dq_string + +string : STRING { 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 : expression { result = Factory.TEXT(val[0]) } + +dqtail + : dqpost { result = [val[0]] } + | dqmid dqrval { result = [val[0]] + val[1] } + +name : NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } +type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } +undef : UNDEF { result = Factory.literal(:undef); loc result, val[0] } +default : DEFAULT { result = Factory.literal(:default); loc result, val[0] } + + # Assumes lexer produces a Boolean value for booleans, or this will go wrong and produce a literal string + # with the text 'true'. + #TODO: could be changed to a specific boolean literal factory method to prevent this possible glitch. +boolean : BOOLEAN { result = Factory.literal(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 + +nil + : { result = nil} + +end + +---- header ---- +require 'puppet' +require 'puppet/util/loadedfile' +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 new file mode 100644 index 000000000..26fea92cf --- /dev/null +++ b/lib/puppet/pops/parser/eparser.rb @@ -0,0 +1,2300 @@ +# +# 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/util/loadedfile' +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', 719) + +# Make emacs happy +# Local Variables: +# mode: ruby +# End: +...end egrammar.ra/module_eval... +##### State transition tables begin ### + +clist = [ +'68,-132,112,207,220,235,344,51,53,87,88,84,79,90,234,94,-130,89,51,53', +'80,82,81,83,234,283,224,269,222,115,233,223,321,114,207,234,244,204', +'93,-198,-207,-132,86,85,54,299,72,73,75,74,77,78,68,70,71,54,-130,316', +'202,315,69,87,88,84,79,90,59,94,76,89,51,53,80,82,81,83,245,59,115,-198', +'-207,301,114,115,115,51,53,114,114,115,93,330,291,114,86,85,105,104', +'72,73,75,74,77,78,68,70,71,120,225,227,122,226,69,87,88,84,79,90,68', +'94,76,89,54,297,80,82,81,83,68,59,115,90,268,94,114,89,243,51,53,105', +'104,90,93,94,128,89,86,85,68,267,72,73,75,74,77,78,93,70,71,84,79,90', +'68,94,69,89,93,306,80,82,81,83,76,192,54,90,316,94,315,89,309,70,71', +'105,104,310,93,207,69,51,53,85,68,168,72,73,75,74,77,78,93,70,71,84', +'79,90,313,94,69,89,51,53,80,82,81,83,76,105,68,68,317,51,53,229,228', +'319,120,63,263,122,93,90,90,94,94,89,89,241,72,73,75,74,77,78,243,70', +'71,120,59,326,122,327,69,68,241,91,93,93,120,267,76,122,87,88,84,79', +'90,259,94,59,89,70,71,80,82,81,83,68,69,63,59,64,66,65,67,134,258,257', +'336,79,90,93,94,243,89,86,85,80,243,72,73,75,74,77,78,116,70,71,241', +'339,217,341,106,69,217,93,282,286,319,346,347,76,348,72,73,75,74,77', +'78,68,70,71,349,99,352,353,354,69,285,63,60,79,90,361,94,76,89,362,363', +'80,364,,,68,,,,,,,,,,,,79,90,93,94,,89,,,80,,72,73,75,74,77,78,68,70', +'71,,,,,,69,,93,,,90,,94,76,89,72,73,75,74,77,78,68,70,71,,,,,,69,,,', +'79,90,93,94,76,89,,,80,,72,73,75,74,77,78,68,70,71,,,,,,69,,93,,,90', +',94,76,89,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,93,94,76', +'89,,,80,82,81,83,68,,,,,70,71,,,,,,69,90,93,94,,89,86,85,,,72,73,75', +'74,77,78,68,70,71,,,,,,69,,93,,,90,,94,76,89,72,73,75,74,77,78,68,70', +'71,,,,,,69,,,,,90,93,94,76,89,,,,,72,73,75,74,,,,70,71,,,,,,69,,93,', +',,,,76,,72,73,75,74,,,68,70,71,,,,,,69,87,88,84,79,90,239,94,76,89,', +',80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70,71', +',,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86', +'85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89,68', +',80,82,81,83,,,,,,,,90,,94,,89,,,93,,,,86,85,,,72,73,75,74,77,78,,70', +'71,,93,,,,69,,,,,,75,74,76,,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89', +'68,,80,82,81,83,,,,,,,,90,,94,,89,,,93,,,,86,85,,,72,73,75,74,77,78', +',70,71,,93,,,,69,,,,,,75,74,76,,68,70,71,,,,,,69,87,88,84,79,90,,94', +'76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68', +'70,71,,,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93', +',,,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76', +'89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70', +'71,,,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,', +',,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89', +',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,', +',,,,69,68,,213,,,,,76,,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,', +',,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,,69,68,,212,,,,,76', +',87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72', +'73,75,74,77,78,,70,71,,,,,,69,68,,211,,,,,76,,87,88,84,79,90,,94,,89', +',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,', +',,,,69,68,,210,,,,,76,,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,', +',,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90', +',94,76,89,,197,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77', +'78,,70,71,51,53,,,47,69,48,,,,,,,76,,,,,,,,,13,,,,,,38,,44,,46,96,,45', +'58,54,,40,57,,,,55,12,,,56,51,53,11,,47,,48,,,,59,,,,,,39,,,167,,,13', +',,,,,170,187,181,188,46,182,190,183,179,177,,172,185,,,,55,12,191,186', +'184,51,53,11,,47,,48,333,,,59,,,,,189,171,,,,,,13,,,,,,38,,44,,46,42', +',45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,322,,,,,,59,,,,,,39,', +',13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,170,187,181,188,46,182,190,183,179,177,,172,185,,,,55,12,191', +'186,184,51,53,11,,47,,48,308,,,59,,,,,189,171,,,,,,13,,,,,,38,,44,,46', +'42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,', +',13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12', +'51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,199,,,,,38,,44,,46,96,,45,58', +'54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38', +',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,', +',,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', +'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', +'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,209,,,,,38,,44,,46,96,,45', +'58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,', +'38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59', +',,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', +',47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57', +'43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,', +',13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,,,56,51,53,11,,47,290,48,,,,59,,,,,,39,,,,,,13,,,,,,38', +',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,335,,,,,,59', +',,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', +',47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57', +',,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96', +',45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,273,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,', +',,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51', +'53,56,,47,11,48,271,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54', +',40,57,43,,,55,12,51,53,56,,47,11,48,265,,,,,,59,,,,,,39,,,13,,,,,,38', +',44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,', +',,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', +'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', +'12,,,56,51,53,11,,47,126,48,,,,59,,,,,,39,,,,,,13,,,,,,38,,44,,46,96', +',45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,', +',,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,', +',59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', +'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', +'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', +',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', +'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', +'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,351,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11', +'48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12', +'51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54', +',40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,356,,,,,,59,,,,', +',39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47', +'11,48,358,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43', +',,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42', +',45,58,54,61,40,57,43,,,55,12,51,53,56,,47,11,48,360,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11', +'48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55', +'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', +'54,,40,57,,,,55,12,,,56,,,11,,,,253,187,252,188,59,250,190,254,248,247', +'39,249,251,,,,,,191,186,255,253,187,252,188,,250,190,254,248,247,,249', +'251,,,189,256,,191,186,255,253,187,252,188,,250,190,254,248,247,,249', +'251,,,189,256,,191,186,255,,,,,,,,,,,,,,,,189,256' ] + racc_action_table = arr = ::Array.new(4804, 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 = [ +'164,179,42,105,118,164,316,70,70,164,164,164,164,164,208,164,177,164', +'71,71,164,164,164,164,274,217,125,208,118,42,141,125,274,42,217,141', +'180,105,164,185,184,179,164,164,70,240,164,164,164,164,164,164,163,164', +'164,71,177,271,103,271,164,163,163,163,163,163,70,163,164,163,220,220', +'163,163,163,163,180,71,96,185,184,242,96,182,282,226,226,182,282,181', +'163,282,226,181,163,163,306,306,163,163,163,163,163,163,162,163,163', +'220,127,130,220,127,163,162,162,162,162,162,97,162,163,162,226,236,162', +'162,162,162,151,220,44,97,207,97,44,97,246,48,48,199,199,151,162,151', +'48,151,162,162,161,205,162,162,162,162,162,162,97,162,162,161,161,161', +'142,161,162,161,151,260,161,161,161,161,162,92,48,142,313,142,313,142', +'264,151,151,36,36,266,161,267,151,183,183,161,160,90,161,161,161,161', +'161,161,142,161,161,160,160,160,270,160,161,160,45,45,160,160,160,160', +'161,104,150,95,272,222,222,133,133,273,183,135,200,183,160,150,95,150', +'95,150,95,277,160,160,160,160,160,160,278,160,160,45,183,279,45,280', +'160,10,214,10,150,95,222,284,160,222,10,10,10,10,10,198,10,45,10,150', +'150,10,10,10,10,159,150,62,222,7,7,7,7,60,196,194,296,159,159,10,159', +'174,159,10,10,159,298,10,10,10,10,10,10,43,10,10,173,305,113,307,37', +'10,117,159,215,219,317,319,320,10,324,159,159,159,159,159,159,158,159', +'159,325,35,331,332,334,159,218,5,1,158,158,350,158,159,158,355,357,158', +'359,,,157,,,,,,,,,,,,157,157,158,157,,157,,,157,,158,158,158,158,158', +'158,155,158,158,,,,,,158,,157,,,155,,155,158,155,157,157,157,157,157', +'157,156,157,157,,,,,,157,,,,156,156,155,156,157,156,,,156,,155,155,155', +'155,155,155,149,155,155,,,,,,155,,156,,,149,,149,155,149,156,156,156', +'156,156,156,312,156,156,,,,,,156,312,312,312,312,312,149,312,156,312', +',,312,312,312,312,154,,,,,149,149,,,,,,149,154,312,154,,154,312,312', +',,312,312,312,312,312,312,153,312,312,,,,,,312,,154,,,153,,153,312,153', +'154,154,154,154,154,154,152,154,154,,,,,,154,,,,,152,153,152,154,152', +',,,,153,153,153,153,,,,153,153,,,,,,153,,152,,,,,,153,,152,152,152,152', +',,169,152,152,,,,,,152,169,169,169,169,169,169,169,152,169,,,169,169', +'169,169,,,,,,,,,,,,,,,169,,,,169,169,,,169,169,169,169,169,169,304,169', +'169,,,,,,169,304,304,304,304,304,,304,169,304,,,304,304,304,304,,,,', +',,,,,,,,,,304,,,,304,304,,,304,304,304,304,304,304,303,304,304,,,,,', +'304,303,303,303,303,303,,303,304,303,148,,303,303,303,303,,,,,,,,148', +',148,,148,,,303,,,,303,303,,,303,303,303,303,303,303,,303,303,,148,', +',,303,,,,,,148,148,303,,295,148,148,,,,,,148,295,295,295,295,295,,295', +'148,295,147,,295,295,295,295,,,,,,,,147,,147,,147,,,295,,,,295,295,', +',295,295,295,295,295,295,,295,295,,147,,,,295,,,,,,147,147,295,,293', +'147,147,,,,,,147,293,293,293,293,293,,293,147,293,,,293,293,293,293', +',,,,,,,,,,,,,,293,,,,293,293,,,293,293,293,293,293,293,193,293,293,', +',,,,293,193,193,193,193,193,,193,293,193,,,193,193,193,193,,,,,,,,,', +',,,,,193,,,,193,193,,,193,193,193,193,193,193,289,193,193,,,,,,193,289', +'289,289,289,289,,289,193,289,,,289,289,289,289,,,,,,,,,,,,,,,289,,,', +'289,289,,,289,289,289,289,289,289,131,289,289,,,,,,289,131,131,131,131', +'131,,131,289,131,,,131,131,131,131,,,,,,,,,,,,,,,131,,,,131,131,,,131', +'131,131,131,131,131,124,131,131,,,,,,131,124,124,124,124,124,,124,131', +'124,,,124,124,124,124,,,,,,,,,,,,,,,124,,,,124,124,,,124,124,124,124', +'124,124,,124,124,,,,,,124,111,,111,,,,,124,,111,111,111,111,111,,111', +',111,,,111,111,111,111,,,,,,,,,,,,,,,111,,,,111,111,,,111,111,111,111', +'111,111,,111,111,,,,,,111,110,,110,,,,,111,,110,110,110,110,110,,110', +',110,,,110,110,110,110,,,,,,,,,,,,,,,110,,,,110,110,,,110,110,110,110', +'110,110,,110,110,,,,,,110,109,,109,,,,,110,,109,109,109,109,109,,109', +',109,,,109,109,109,109,,,,,,,,,,,,,,,109,,,,109,109,,,109,109,109,109', +'109,109,,109,109,,,,,,109,107,,107,,,,,109,,107,107,107,107,107,,107', +',107,,,107,107,107,107,,,,,,,,,,,,,,,107,,,,107,107,,,107,107,107,107', +'107,107,98,107,107,,,,,,107,98,98,98,98,98,,98,107,98,,98,98,98,98,98', +',,,,,,,,,,,,,,98,,,,98,98,,,98,98,98,98,98,98,,98,98,89,89,,,89,98,89', +',,,,,,98,,,,,,,,,89,,,,,,89,,89,,89,89,,89,89,89,,89,89,,,,89,89,,,89', +'213,213,89,,213,,213,,,,89,,,,,,89,,,89,,,213,,,,,,213,213,213,213,213', +'213,213,213,213,213,,213,213,,,,213,213,213,213,213,285,285,213,,285', +',285,285,,,213,,,,,213,213,,,,,,285,,,,,,285,,285,,285,285,,285,285', +'285,,285,285,285,,,285,285,275,275,285,,275,285,275,275,,,,,,285,,,', +',,285,,,275,,,,,,275,,275,,275,275,,275,275,275,,275,275,,,,275,275', +'72,72,275,,72,275,72,,,,,,,275,,,,,,275,,,72,,,,,,72,,72,,72,72,,72', +'72,72,,72,72,,,,72,72,73,73,72,,73,72,73,,,,,,,72,,,,,,72,,,73,,,,,', +'73,,73,,73,73,,73,73,73,,73,73,,,,73,73,74,74,73,,74,73,74,,,,,,,73', +',,,,,73,,,74,,,,,,74,,74,,74,74,,74,74,74,,74,74,,,,74,74,75,75,74,', +'75,74,75,,,,,,,74,,,,,,74,,,75,,,,,,75,,75,,75,75,,75,75,75,,75,75,', +',,75,75,76,76,75,,76,75,76,,,,,,,75,,,,,,75,,,76,,,,,,76,,76,,76,76', +',76,76,76,,76,76,,,,76,76,77,77,76,,77,76,77,,,,,,,76,,,,,,76,,,77,', +',,,,77,,77,,77,77,,77,77,77,,77,77,,,,77,77,78,78,77,,78,77,78,,,,,', +',77,,,,,,77,,,78,,,,,,78,,78,,78,78,,78,78,78,,78,78,,,,78,78,79,79', +'78,,79,78,79,,,,,,,78,,,,,,78,,,79,,,,,,79,,79,,79,79,,79,79,79,,79', +'79,,,,79,79,80,80,79,,80,79,80,,,,,,,79,,,,,,79,,,80,,,,,,80,,80,,80', +'80,,80,80,80,,80,80,,,,80,80,81,81,80,,81,80,81,,,,,,,80,,,,,,80,,,81', +',,,,,81,,81,,81,81,,81,81,81,,81,81,,,,81,81,82,82,81,,82,81,82,,,,', +',,81,,,,,,81,,,82,,,,,,82,,82,,82,82,,82,82,82,,82,82,,,,82,82,83,83', +'82,,83,82,83,,,,,,,82,,,,,,82,,,83,,,,,,83,,83,,83,83,,83,83,83,,83', +'83,,,,83,83,84,84,83,,84,83,84,,,,,,,83,,,,,,83,,,84,,,,,,84,,84,,84', +'84,,84,84,84,,84,84,,,,84,84,85,85,84,,85,84,85,,,,,,,84,,,,,,84,,,85', +',,,,,85,,85,,85,85,,85,85,85,,85,85,,,,85,85,86,86,85,,86,85,86,,,,', +',,85,,,,,,85,,,86,,,,,,86,,86,,86,86,,86,86,86,,86,86,,,,86,86,167,167', +'86,,167,86,167,,,,,,,86,,,,,,86,,,167,,,,,,167,,167,,167,167,,167,167', +'167,,167,167,,,,167,167,88,88,167,,88,167,88,,,,,,,167,,,,,,167,,,88', +',,,,,88,,88,,88,88,,88,88,88,,88,88,,,,88,88,68,68,88,,68,88,68,,,,', +',,88,,,,,,88,,,68,,,,,,68,,68,,68,68,,68,68,68,,68,68,,,,68,68,268,268', +'68,,268,68,268,,,,,,,68,,,,,,68,,,268,,,,,,268,,268,,268,268,,268,268', +'268,,268,268,,,,268,268,91,91,268,,91,268,91,,,,,,,268,,,,,,268,,,91', +',,,,,91,91,91,91,91,91,91,91,91,91,,91,91,,,,91,91,91,91,91,263,263', +'91,,263,,263,263,,,91,,,,,91,91,,,,,,263,,,,,,263,,263,,263,263,,263', +'263,263,,263,263,263,,,263,263,93,93,263,,93,263,93,,,,,,,263,,,,,,263', +',,93,,,,,,93,,93,,93,93,,93,93,93,,93,93,,,,93,93,94,94,93,,94,93,94', +',,,,,,93,,,,,,93,,,94,,,,,,94,,94,,94,94,,94,94,94,,94,94,,,,94,94,259', +'259,94,,259,94,259,,,,,,,94,,,,,,94,,,259,,,,,,259,,259,,259,259,,259', +'259,259,,259,259,,,,259,259,245,245,259,,245,259,245,,,,,,,259,,,,,', +'259,,,245,,,,,,245,,245,,245,245,,245,245,245,,245,245,,,,245,245,244', +'244,245,,244,245,244,,,,,,,245,,,,,,245,,,244,,,,,,244,,244,,244,244', +',244,244,244,,244,244,,,,244,244,67,67,244,,67,244,67,,,,,,,244,,,,', +',244,,,67,,,,,,67,,67,,67,67,,67,67,67,,67,67,67,,,67,67,99,99,67,,99', +'67,99,,,,,,,67,,,,,,67,,,99,99,,,,,99,,99,,99,99,,99,99,99,,99,99,,', +',99,99,241,241,99,,241,99,241,,,,,,,99,,,,,,99,,,241,,,,,,241,,241,', +'241,241,,241,241,241,,241,241,,,,241,241,235,235,241,,235,241,235,,', +',,,,241,,,,,,241,,,235,,,,,,235,,235,,235,235,,235,235,235,,235,235', +',,,235,235,234,234,235,,234,235,234,,,,,,,235,,,,,,235,,,234,,,,,,234', +',234,,234,234,,234,234,234,,234,234,,,,234,234,106,106,234,,106,234', +'106,,,,,,,234,,,,,,234,,,106,106,,,,,106,,106,,106,106,,106,106,106', +',106,106,,,,106,106,66,66,106,,66,106,66,,,,,,,106,,,,,,106,,,66,,,', +',,66,,66,,66,66,,66,66,66,,66,66,66,,,66,66,65,65,66,,65,66,65,,,,,', +',66,,,,,,66,,,65,,,,,,65,,65,,65,65,,65,65,65,,65,65,65,,,65,65,64,64', +'65,,64,65,64,,,,,,,65,,,,,,65,,,64,,,,,,64,,64,,64,64,,64,64,64,,64', +'64,64,,,64,64,63,63,64,,63,64,63,,,,,,,64,,,,,,64,,,63,,,,,,63,,63,', +'63,63,,63,63,63,,63,63,63,,,63,63,112,112,63,,112,63,112,,,,,,,63,,', +',,,63,,,112,,,,,,112,,112,,112,112,,112,112,112,,112,112,,,,112,112', +'232,232,112,,232,112,232,,,,,,,112,,,,,,112,,,232,,,,,,232,,232,,232', +'232,,232,232,232,,232,232,,,,232,232,227,227,232,,227,232,227,,,,,,', +'232,,,,,,232,,,227,,,,,,227,,227,,227,227,,227,227,227,,227,227,,,,227', +'227,,,227,223,223,227,,223,223,223,,,,227,,,,,,227,,,,,,223,,,,,,223', +',223,,223,223,,223,223,223,,223,223,,,,223,223,286,286,223,,286,223', +'286,286,,,,,,223,,,,,,223,,,286,,,,,,286,,286,,286,286,,286,286,286', +',286,286,286,,,286,286,69,69,286,,69,286,69,,,,,,,286,,,,,,286,,,69', +',,,,,69,,69,,69,69,,69,69,69,,69,69,,,,69,69,212,212,69,,212,69,212', +',,,,,,69,,,,,,69,,,212,,,,,,212,,212,,212,212,,212,212,212,,212,212', +',,,212,212,211,211,212,,211,212,211,211,,,,,,212,,,,,,212,,,211,,,,', +',211,,211,,211,211,,211,211,211,,211,211,211,,,211,211,61,61,211,,61', +'211,61,,,,,,,211,,,,,,211,,,61,,,,,,61,,61,,61,61,,61,61,61,,61,61,61', +',,61,61,210,210,61,,210,61,210,210,,,,,,61,,,,,,61,,,210,,,,,,210,,210', +',210,210,,210,210,210,,210,210,210,,,210,210,203,203,210,,203,210,203', +'203,,,,,,210,,,,,,210,,,203,,,,,,203,,203,,203,203,,203,203,203,,203', +'203,203,,,203,203,52,52,203,,52,203,52,,,,,,,203,,,,,,203,,,52,,,,,', +'52,,52,,52,52,,52,52,52,,52,52,,,,52,52,172,172,52,,172,52,172,,,,,', +',52,,,,,,52,,,172,,,,,,172,,172,,172,172,,172,172,172,,172,172,,,,172', +'172,,,172,47,47,172,,47,47,47,,,,172,,,,,,172,,,,,,47,,,,,,47,,47,,47', +'47,,47,47,47,,47,47,,,,47,47,297,297,47,,297,47,297,,,,,,,47,,,,,,47', +',,297,,,,,,297,,297,,297,297,,297,297,297,,297,297,,,,297,297,171,171', +'297,,171,297,171,,,,,,,297,,,,,,297,,,171,,,,,,171,,171,,171,171,,171', +'171,171,,171,171,,,,171,171,170,170,171,,170,171,170,,,,,,,171,,,,,', +'171,,,170,,,,,,170,,170,,170,170,,170,170,170,,170,170,,,,170,170,41', +'41,170,,41,170,41,,,,,,,170,,,,,,170,,,41,,,,,,41,,41,,41,41,,41,41', +'41,,41,41,,,,41,41,40,40,41,,40,41,40,,,,,,,41,,,,,,41,,,40,,,,,,40', +',40,,40,40,,40,40,40,,40,40,,,,40,40,39,39,40,,39,40,39,,,,,,,40,,,', +',,40,,,39,,,,,,39,,39,,39,39,,39,39,39,,39,39,,,,39,39,38,38,39,,38', +'39,38,,,,,,,39,,,,,,39,,,38,,,,,,38,,38,,38,38,,38,38,38,,38,38,,,,38', +'38,315,315,38,,315,38,315,,,,,,,38,,,,,,38,,,315,,,,,,315,,315,,315', +'315,,315,315,315,,315,315,,,,315,315,327,327,315,,327,315,327,327,,', +',,,315,,,,,,315,,,327,,,,,,327,,327,,327,327,,327,327,327,,327,327,327', +',,327,327,13,13,327,,13,327,13,,,,,,,327,,,,,,327,,,13,,,,,,13,,13,', +'13,13,,13,13,13,,13,13,,,,13,13,12,12,13,,12,13,12,,,,,,,13,,,,,,13', +',,12,,,,,,12,,12,,12,12,,12,12,12,,12,12,,,,12,12,11,11,12,,11,12,11', +',,,,,,12,,,,,,12,,,11,,,,,,11,,11,,11,11,,11,11,11,,11,11,,,,11,11,344', +'344,11,,344,11,344,344,,,,,,11,,,,,,11,,,344,,,,,,344,,344,,344,344', +',344,344,344,,344,344,344,,,344,344,346,346,344,,346,344,346,346,,,', +',,344,,,,,,344,,,346,,,,,,346,,346,,346,346,,346,346,346,,346,346,346', +',,346,346,4,4,346,,4,346,4,,,,,,,346,,,,,,346,,,4,,,,,,4,,4,,4,4,,4', +'4,4,4,4,4,4,,,4,4,347,347,4,,347,4,347,347,,,,,,4,,,,,,4,,,347,,,,,', +'347,,347,,347,347,,347,347,347,,347,347,347,,,347,347,0,0,347,,0,347', +'0,,,,,,,347,,,,,,347,,,0,,,,,,0,,0,,0,0,,0,0,0,,0,0,0,,,0,0,87,87,0', +',87,0,87,,,,,,,0,,,,,,0,,,87,,,,,,87,,87,,87,87,,87,87,87,,87,87,,,', +'87,87,,,87,,,87,,,,192,192,192,192,87,192,192,192,192,192,87,192,192', +',,,,,192,192,192,238,238,238,238,,238,238,238,238,238,,238,238,,,192', +'192,,238,238,238,243,243,243,243,,243,243,243,243,243,,243,243,,,238', +'238,,243,243,243,,,,,,,,,,,,,,,,243,243' ] + racc_action_check = arr = ::Array.new(4804, 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 = [ + 4621, 340, nil, nil, 4529, 327, nil, 219, nil, nil, + 247, 4391, 4345, 4299, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 309, 115, 288, 4161, 4115, + 4069, 4023, -6, 262, 95, 208, nil, 3839, 135, nil, + nil, nil, 3744, nil, nil, nil, nil, nil, nil, nil, + 285, 3606, 267, 3189, 3143, 3097, 3051, 2775, 2358, 3468, + 5, 16, 1576, 1622, 1668, 1714, 1760, 1806, 1852, 1898, + 1944, 1990, 2036, 2082, 2128, 2174, 2220, 4667, 2312, 1386, + 154, 2450, 165, 2545, 2591, 213, 43, 112, 1327, 2821, + nil, nil, nil, 47, 149, -31, 3005, 1275, nil, 1214, + 1153, 1092, 3235, 286, nil, nil, nil, 290, -8, nil, + nil, nil, nil, nil, 1031, 19, nil, 99, nil, nil, + 98, 979, nil, 219, nil, 215, nil, nil, nil, nil, + nil, 23, 156, nil, nil, nil, nil, 771, 701, 427, + 212, 122, 527, 503, 475, 375, 399, 347, 323, 271, + 186, 142, 98, 46, -6, nil, nil, 2266, nil, 579, + 3977, 3931, 3790, 268, 281, nil, nil, 5, nil, -10, + 25, 54, 48, 187, 29, 28, nil, nil, nil, nil, + nil, nil, 4694, 875, 242, nil, 264, nil, 255, 71, + 220, nil, nil, 3698, nil, 137, nil, 117, 2, nil, + 3652, 3560, 3514, 1435, 214, 280, nil, 0, 330, 309, + 68, nil, 219, 3376, nil, nil, 83, 3327, nil, nil, + nil, nil, 3281, nil, 2959, 2913, 111, nil, 4715, nil, + 36, 2867, 72, 4736, 2729, 2683, 124, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 2637, + 142, nil, nil, 2499, 171, nil, 117, 153, 2404, nil, + 197, 26, 211, 194, 12, 1530, nil, 197, 232, 240, + 243, nil, 49, nil, 247, 1484, 3422, nil, nil, 927, + nil, nil, nil, 823, nil, 753, 279, 3885, 286, nil, + nil, nil, nil, 683, 631, 300, 28, 302, nil, nil, + nil, nil, 451, 145, nil, 4207, -2, 287, nil, 311, + 312, nil, nil, nil, 313, 323, nil, 4253, nil, nil, + nil, 309, 326, nil, 327, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 4437, nil, 4483, 4575, nil, nil, + 334, nil, nil, nil, nil, 338, nil, 339, nil, 341, + nil, nil, nil, nil, nil ] + +racc_action_default = [ + -209, -210, -1, -2, -3, -4, -7, -9, -10, -15, + -109, -210, -210, -210, -43, -44, -45, -46, -47, -48, + -49, -50, -51, -52, -53, -54, -55, -56, -57, -58, + -59, -60, -61, -62, -63, -68, -69, -73, -210, -210, + -210, -210, -210, -119, -210, -210, -164, -210, -210, -174, + -175, -176, -210, -178, -185, -186, -187, -188, -189, -190, + -210, -210, -6, -210, -210, -210, -210, -210, -210, -210, + -210, -210, -210, -210, -210, -210, -210, -210, -210, -210, + -210, -210, -210, -210, -210, -210, -210, -210, -210, -210, + -210, -127, -122, -209, -209, -27, -210, -34, -210, -210, + -70, -75, -76, -209, -210, -210, -210, -210, -86, -210, + -210, -210, -210, -209, -153, -154, -120, -209, -209, -145, + -147, -148, -149, -150, -41, -210, -167, -210, -170, -171, + -210, -182, -177, -210, 365, -5, -8, -11, -12, -13, + -14, -210, -17, -18, -162, -163, -19, -20, -21, -22, + -23, -24, -25, -26, -28, -29, -30, -31, -32, -33, + -35, -36, -37, -38, -210, -39, -104, -210, -74, -210, + -202, -208, -196, -193, -191, -117, -128, -185, -131, -189, + -210, -199, -197, -205, -187, -188, -195, -200, -201, -203, + -204, -206, -127, -126, -210, -125, -210, -40, -191, -65, + -210, -80, -81, -210, -84, -191, -158, -161, -210, -72, + -210, -210, -210, -127, -193, -209, -155, -210, -210, -210, + -210, -151, -210, -210, -165, -168, -210, -210, -179, -180, + -181, -183, -210, -16, -210, -210, -191, -106, -127, -116, + -210, -194, -210, -192, -210, -210, -191, -130, -132, -196, + -197, -198, -199, -202, -205, -207, -208, -123, -124, -192, + -210, -67, -77, -210, -210, -83, -210, -192, -210, -71, + -210, -89, -210, -95, -210, -210, -99, -193, -191, -210, + -210, -139, -210, -156, -191, -210, -210, -146, -152, -42, + -166, -169, -172, -173, -184, -108, -210, -192, -191, -112, + -118, -113, -129, -133, -134, -210, -64, -210, -79, -82, + -85, -159, -160, -89, -88, -210, -210, -95, -94, -210, + -210, -103, -98, -100, -210, -210, -114, -210, -140, -141, + -142, -210, -210, -136, -210, -144, -105, -107, -115, -121, + -66, -78, -87, -90, -210, -93, -210, -210, -110, -111, + -210, -138, -157, -135, -143, -210, -92, -210, -97, -210, + -102, -137, -91, -96, -101 ] + +racc_goto_table = [ + 2, 117, 100, 95, 97, 98, 3, 132, 129, 166, + 174, 240, 130, 205, 314, 318, 123, 121, 215, 173, + 1, 276, 218, 320, 287, 62, 288, 242, 194, 196, + 107, 109, 110, 111, 145, 145, 125, 143, 146, 124, + 214, 144, 144, 236, 131, 137, 138, 139, 140, 275, + 300, 260, 279, 238, 343, 302, 342, 141, 266, 345, + 124, 142, 262, 200, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 135, 169, 323, 193, 193, 237, 198, 296, + 280, 124, 328, 219, 203, 208, 311, 127, 124, 305, + 165, 136, 231, 232, 169, 230, nil, nil, nil, 201, + nil, 246, nil, nil, nil, 324, nil, nil, nil, 216, + nil, nil, nil, 216, 221, 284, nil, nil, nil, nil, + nil, 325, 278, nil, nil, nil, nil, 331, 117, nil, + nil, 277, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 338, nil, nil, 123, 121, nil, 298, nil, 164, + nil, nil, 107, 109, 110, 261, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 292, 294, nil, nil, + 130, 123, 121, 123, 121, nil, nil, nil, nil, nil, + nil, nil, nil, 264, 124, 169, nil, nil, nil, nil, + 270, 272, nil, nil, nil, 289, nil, 337, nil, 293, + nil, 281, nil, nil, 131, nil, 289, 295, nil, nil, + nil, nil, nil, 169, nil, nil, 303, 304, nil, 329, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 289, nil, nil, nil, nil, nil, nil, nil, nil, + 312, nil, nil, 307, nil, nil, nil, 124, nil, nil, + nil, nil, 340, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 332, 334, nil, nil, 164, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 107, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 350, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 355, nil, 357, 359 ] + +racc_goto_check = [ + 2, 65, 37, 9, 9, 9, 3, 78, 74, 52, + 57, 56, 31, 45, 47, 48, 30, 35, 66, 55, + 1, 50, 66, 51, 71, 5, 71, 36, 61, 61, + 9, 9, 9, 9, 31, 31, 11, 12, 12, 9, + 55, 30, 30, 53, 9, 7, 7, 7, 7, 49, + 58, 36, 56, 59, 46, 62, 47, 11, 36, 48, + 9, 9, 44, 43, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 5, 9, 50, 9, 9, 52, 11, 36, + 67, 9, 68, 70, 42, 11, 72, 73, 9, 36, + 13, 6, 79, 80, 9, 82, nil, nil, nil, 3, + nil, 57, nil, nil, nil, 56, nil, nil, nil, 3, + nil, nil, nil, 3, 3, 45, nil, nil, nil, nil, + nil, 36, 57, nil, nil, nil, nil, 36, 65, nil, + nil, 55, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 36, nil, nil, 30, 35, nil, 57, nil, 9, + nil, nil, 9, 9, 9, 37, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 74, 78, nil, nil, + 31, 30, 35, 30, 35, nil, nil, nil, nil, nil, + nil, nil, nil, 2, 9, 9, nil, nil, nil, nil, + 2, 2, nil, nil, nil, 9, nil, 52, nil, 9, + nil, 3, nil, nil, 9, nil, 9, 9, nil, nil, + nil, nil, nil, 9, nil, nil, 9, 9, nil, 65, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, + 9, nil, nil, 2, nil, nil, nil, 9, nil, nil, + nil, nil, 37, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 2, 2, nil, nil, 9, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 9, 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, nil, nil, nil, + nil, nil, nil, nil, 2, nil, 2, 2 ] + +racc_goto_pointer = [ + nil, 20, 0, 6, nil, 21, 38, -19, nil, -8, + nil, -11, -33, 11, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + -29, -36, nil, nil, nil, -28, -147, -34, nil, nil, + nil, nil, -10, -40, -138, -92, -261, -257, -258, -163, + -191, -251, -80, -124, nil, -72, -162, -81, -191, -116, + nil, -65, -188, nil, nil, -43, -95, -125, -190, nil, + -25, -196, -171, 49, -40, nil, nil, nil, -45, -31, + -30, nil, -28 ] + +racc_goto_default = [ + nil, nil, nil, 195, 4, 5, 6, 7, 8, 10, + 9, 274, nil, nil, 14, 35, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, nil, nil, 36, 37, + 101, 102, 103, nil, nil, nil, 108, nil, nil, nil, + nil, nil, nil, nil, 41, nil, nil, nil, 175, nil, + 92, nil, 176, 180, 178, 113, nil, nil, nil, 118, + nil, 119, 206, nil, nil, 49, 50, 52, nil, nil, + nil, 133, nil ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 78, :_reduce_1, + 1, 78, :_reduce_none, + 1, 79, :_reduce_3, + 1, 81, :_reduce_4, + 3, 81, :_reduce_5, + 2, 81, :_reduce_6, + 1, 82, :_reduce_7, + 3, 82, :_reduce_8, + 1, 83, :_reduce_none, + 1, 84, :_reduce_10, + 3, 84, :_reduce_11, + 3, 84, :_reduce_12, + 3, 84, :_reduce_13, + 3, 84, :_reduce_14, + 1, 86, :_reduce_none, + 4, 86, :_reduce_16, + 3, 86, :_reduce_17, + 3, 86, :_reduce_18, + 3, 86, :_reduce_19, + 3, 86, :_reduce_20, + 3, 86, :_reduce_21, + 3, 86, :_reduce_22, + 3, 86, :_reduce_23, + 3, 86, :_reduce_24, + 3, 86, :_reduce_25, + 3, 86, :_reduce_26, + 2, 86, :_reduce_27, + 3, 86, :_reduce_28, + 3, 86, :_reduce_29, + 3, 86, :_reduce_30, + 3, 86, :_reduce_31, + 3, 86, :_reduce_32, + 3, 86, :_reduce_33, + 2, 86, :_reduce_34, + 3, 86, :_reduce_35, + 3, 86, :_reduce_36, + 3, 86, :_reduce_37, + 3, 86, :_reduce_38, + 3, 86, :_reduce_39, + 3, 86, :_reduce_40, + 1, 88, :_reduce_41, + 3, 88, :_reduce_42, + 1, 87, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 92, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 93, :_reduce_none, + 1, 108, :_reduce_62, + 1, 108, :_reduce_63, + 5, 91, :_reduce_64, + 3, 91, :_reduce_65, + 6, 91, :_reduce_66, + 4, 91, :_reduce_67, + 1, 91, :_reduce_68, + 1, 95, :_reduce_69, + 2, 95, :_reduce_70, + 4, 115, :_reduce_71, + 3, 115, :_reduce_72, + 1, 115, :_reduce_73, + 3, 116, :_reduce_74, + 1, 114, :_reduce_none, + 1, 114, :_reduce_none, + 3, 117, :_reduce_77, + 3, 121, :_reduce_78, + 2, 121, :_reduce_79, + 1, 120, :_reduce_none, + 1, 120, :_reduce_none, + 4, 118, :_reduce_82, + 3, 118, :_reduce_83, + 2, 119, :_reduce_84, + 4, 119, :_reduce_85, + 2, 98, :_reduce_86, + 5, 123, :_reduce_87, + 4, 123, :_reduce_88, + 0, 124, :_reduce_none, + 2, 124, :_reduce_90, + 4, 124, :_reduce_91, + 3, 124, :_reduce_92, + 6, 99, :_reduce_93, + 5, 99, :_reduce_94, + 0, 125, :_reduce_none, + 4, 125, :_reduce_96, + 3, 125, :_reduce_97, + 5, 97, :_reduce_98, + 1, 126, :_reduce_99, + 2, 126, :_reduce_100, + 5, 127, :_reduce_101, + 4, 127, :_reduce_102, + 1, 128, :_reduce_103, + 1, 90, :_reduce_none, + 4, 90, :_reduce_105, + 1, 130, :_reduce_106, + 3, 130, :_reduce_107, + 3, 129, :_reduce_108, + 1, 85, :_reduce_109, + 6, 85, :_reduce_110, + 6, 85, :_reduce_111, + 5, 85, :_reduce_112, + 5, 85, :_reduce_113, + 5, 85, :_reduce_114, + 4, 135, :_reduce_115, + 1, 136, :_reduce_116, + 1, 132, :_reduce_117, + 3, 132, :_reduce_118, + 1, 131, :_reduce_119, + 2, 131, :_reduce_120, + 6, 96, :_reduce_121, + 2, 96, :_reduce_122, + 3, 137, :_reduce_123, + 3, 137, :_reduce_124, + 1, 138, :_reduce_none, + 1, 138, :_reduce_none, + 0, 134, :_reduce_127, + 1, 134, :_reduce_128, + 3, 134, :_reduce_129, + 1, 140, :_reduce_none, + 1, 140, :_reduce_none, + 1, 140, :_reduce_none, + 3, 139, :_reduce_133, + 3, 139, :_reduce_134, + 6, 100, :_reduce_135, + 5, 100, :_reduce_136, + 7, 101, :_reduce_137, + 6, 101, :_reduce_138, + 1, 144, :_reduce_none, + 2, 144, :_reduce_140, + 1, 145, :_reduce_none, + 1, 145, :_reduce_none, + 6, 102, :_reduce_143, + 5, 102, :_reduce_144, + 1, 146, :_reduce_145, + 3, 146, :_reduce_146, + 1, 148, :_reduce_147, + 1, 148, :_reduce_148, + 1, 148, :_reduce_149, + 1, 148, :_reduce_none, + 1, 147, :_reduce_none, + 2, 147, :_reduce_152, + 1, 142, :_reduce_153, + 1, 142, :_reduce_154, + 1, 143, :_reduce_155, + 2, 143, :_reduce_156, + 4, 143, :_reduce_157, + 1, 122, :_reduce_158, + 3, 122, :_reduce_159, + 3, 149, :_reduce_160, + 1, 149, :_reduce_161, + 1, 89, :_reduce_none, + 1, 89, :_reduce_none, + 1, 94, :_reduce_164, + 3, 103, :_reduce_165, + 4, 103, :_reduce_166, + 2, 103, :_reduce_167, + 3, 106, :_reduce_168, + 4, 106, :_reduce_169, + 2, 106, :_reduce_170, + 1, 150, :_reduce_171, + 3, 150, :_reduce_172, + 3, 151, :_reduce_173, + 1, 112, :_reduce_none, + 1, 112, :_reduce_none, + 1, 152, :_reduce_176, + 2, 153, :_reduce_177, + 1, 154, :_reduce_178, + 1, 156, :_reduce_179, + 1, 157, :_reduce_180, + 2, 155, :_reduce_181, + 1, 158, :_reduce_182, + 1, 159, :_reduce_183, + 2, 159, :_reduce_184, + 1, 111, :_reduce_185, + 1, 109, :_reduce_186, + 1, 110, :_reduce_187, + 1, 105, :_reduce_188, + 1, 104, :_reduce_189, + 1, 107, :_reduce_190, + 0, 113, :_reduce_none, + 1, 113, :_reduce_192, + 0, 133, :_reduce_none, + 1, 133, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 1, 141, :_reduce_none, + 0, 80, :_reduce_209 ] + +racc_reduce_n = 210 + +racc_shift_n = 365 + +racc_token_table = { + false => 0, + :error => 1, + :STRING => 2, + :DQPRE => 3, + :DQMID => 4, + :DQPOST => 5, + :LBRACK => 6, + :RBRACK => 7, + :LBRACE => 8, + :RBRACE => 9, + :SYMBOL => 10, + :FARROW => 11, + :COMMA => 12, + :TRUE => 13, + :FALSE => 14, + :EQUALS => 15, + :APPENDS => 16, + :LESSEQUAL => 17, + :NOTEQUAL => 18, + :DOT => 19, + :COLON => 20, + :LLCOLLECT => 21, + :RRCOLLECT => 22, + :QMARK => 23, + :LPAREN => 24, + :RPAREN => 25, + :ISEQUAL => 26, + :GREATEREQUAL => 27, + :GREATERTHAN => 28, + :LESSTHAN => 29, + :IF => 30, + :ELSE => 31, + :DEFINE => 32, + :ELSIF => 33, + :VARIABLE => 34, + :CLASS => 35, + :INHERITS => 36, + :NODE => 37, + :BOOLEAN => 38, + :NAME => 39, + :SEMIC => 40, + :CASE => 41, + :DEFAULT => 42, + :AT => 43, + :LCOLLECT => 44, + :RCOLLECT => 45, + :CLASSREF => 46, + :NOT => 47, + :OR => 48, + :AND => 49, + :UNDEF => 50, + :PARROW => 51, + :PLUS => 52, + :MINUS => 53, + :TIMES => 54, + :DIV => 55, + :LSHIFT => 56, + :RSHIFT => 57, + :UMINUS => 58, + :MATCH => 59, + :NOMATCH => 60, + :REGEX => 61, + :IN_EDGE => 62, + :OUT_EDGE => 63, + :IN_EDGE_SUB => 64, + :OUT_EDGE_SUB => 65, + :IN => 66, + :UNLESS => 67, + :PIPE => 68, + :LAMBDA => 69, + :SELBRACE => 70, + :LOW => 71, + :HIGH => 72, + :CALL => 73, + :MODULO => 74, + :TITLE_COLON => 75, + :CASE_COLON => 76 } + +racc_nt_base = 77 + +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", + "LBRACK", + "RBRACK", + "LBRACE", + "RBRACE", + "SYMBOL", + "FARROW", + "COMMA", + "TRUE", + "FALSE", + "EQUALS", + "APPENDS", + "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", + "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", + "LOW", + "HIGH", + "CALL", + "MODULO", + "TITLE_COLON", + "CASE_COLON", + "$start", + "program", + "statements", + "nil", + "syntactic_statements", + "syntactic_statement", + "any_expression", + "relationship_expression", + "resource_expression", + "expression", + "higher_precedence", + "expressions", + "match_rvalue", + "selector_entries", + "call_function_expression", + "primary_expression", + "literal_expression", + "variable", + "call_method_with_lambda_expression", + "collection_expression", + "case_expression", + "if_expression", + "unless_expression", + "definition_expression", + "hostclass_expression", + "node_definition_expression", + "array", + "boolean", + "default", + "hash", + "regex", + "text_or_name", + "type", + "undef", + "name", + "quotedtext", + "endcomma", + "lambda", + "call_method_expression", + "named_access", + "lambda_j8", + "lambda_ruby", + "lambda_parameter_list", + "optional_farrow", + "lambda_rest", + "parameters", + "if_part", + "else", + "unless_else", + "case_options", + "case_option", + "case_colon", + "selector_entry", + "selector_entry_list", + "at", + "resourceinstances", + "endsemi", + "attribute_operations", + "resourceinst", + "title_colon", + "collect_query", + "optional_query", + "attribute_operation", + "attribute_name", + "keyword", + "classname", + "parameter_list", + "classparent", + "classnameordefault", + "hostnames", + "nodeparent", + "hostname", + "parameter", + "hashpairs", + "hashpair", + "string", + "dq_string", + "dqpre", + "dqrval", + "dqpost", + "dqmid", + "text_expression", + "dqtail" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 57) + def _reduce_1(val, _values, result) + result = Factory.block_or_expression(*val[0]) + result + end +.,., + +# reduce 2 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 62) + def _reduce_3(val, _values, result) + result = transform_calls(val[0]) + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 68) + def _reduce_4(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 69) + def _reduce_5(val, _values, result) + result = val[0].push val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 70) + def _reduce_6(val, _values, result) + result = val[0].push val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 74) + def _reduce_7(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 75) + def _reduce_8(val, _values, result) + result = aryfy(val[0]).push val[2] + result + end +.,., + +# reduce 9 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 81) + def _reduce_10(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 82) + def _reduce_11(val, _values, result) + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 83) + def _reduce_12(val, _values, result) + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 84) + def _reduce_13(val, _values, result) + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 85) + def _reduce_14(val, _values, result) + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result + end +.,., + +# reduce 15 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 92) + def _reduce_16(val, _values, result) + result = val[0][*val[2]] ; loc result, val[0], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 93) + def _reduce_17(val, _values, result) + result = val[0].in val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 94) + def _reduce_18(val, _values, result) + result = val[0] =~ val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 95) + def _reduce_19(val, _values, result) + result = val[0].mne val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 96) + def _reduce_20(val, _values, result) + result = val[0] + val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 97) + def _reduce_21(val, _values, result) + result = val[0] - val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 98) + def _reduce_22(val, _values, result) + result = val[0] / val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 99) + def _reduce_23(val, _values, result) + result = val[0] * val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 100) + def _reduce_24(val, _values, result) + result = val[0] % val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 101) + def _reduce_25(val, _values, result) + result = val[0] << val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 102) + def _reduce_26(val, _values, result) + result = val[0] >> val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 103) + def _reduce_27(val, _values, result) + result = val[1].minus() ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 104) + def _reduce_28(val, _values, result) + result = val[0].ne val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 105) + def _reduce_29(val, _values, result) + result = val[0] == val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 106) + def _reduce_30(val, _values, result) + result = val[0] > val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 107) + def _reduce_31(val, _values, result) + result = val[0] >= val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 108) + def _reduce_32(val, _values, result) + result = val[0] < val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 109) + def _reduce_33(val, _values, result) + result = val[0] <= val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 110) + def _reduce_34(val, _values, result) + result = val[1].not ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 111) + def _reduce_35(val, _values, result) + result = val[0].and val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 112) + def _reduce_36(val, _values, result) + result = val[0].or val[2] ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 113) + def _reduce_37(val, _values, result) + result = val[0].set(val[2]) ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 114) + def _reduce_38(val, _values, result) + result = val[0].plus_set(val[2]) ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 115) + def _reduce_39(val, _values, result) + result = val[0].select(*val[2]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 116) + def _reduce_40(val, _values, result) + result = val[1].paren() ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 124) + def _reduce_41(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 125) + def _reduce_42(val, _values, result) + result = val[0].push(val[2]) + result + end +.,., + +# reduce 43 omitted + +# reduce 44 omitted + +# reduce 45 omitted + +# reduce 46 omitted + +# reduce 47 omitted + +# reduce 48 omitted + +# reduce 49 omitted + +# reduce 50 omitted + +# reduce 51 omitted + +# reduce 52 omitted + +# reduce 53 omitted + +# reduce 54 omitted + +# reduce 55 omitted + +# reduce 56 omitted + +# reduce 57 omitted + +# reduce 58 omitted + +# reduce 59 omitted + +# reduce 60 omitted + +# reduce 61 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 155) + def _reduce_62(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 156) + def _reduce_63(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 164) + def _reduce_64(val, _values, result) + result = Factory.CALL_NAMED(val[0], true, val[2]) + loc result, val[0], val[4] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 168) + def _reduce_65(val, _values, result) + result = Factory.CALL_NAMED(val[0], true, []) + loc result, val[0], val[2] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 172) + def _reduce_66(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', 177) + def _reduce_67(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', 181) + def _reduce_68(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 186) + def _reduce_69(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 187) + def _reduce_70(val, _values, result) + result = val[0]; val[0].lambda = val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 190) + def _reduce_71(val, _values, result) + result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 191) + def _reduce_72(val, _values, result) + result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 192) + def _reduce_73(val, _values, result) + result = Factory.CALL_METHOD(val[0], []); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 197) + def _reduce_74(val, _values, result) + result = val[0].dot(Factory.fqn(val[2][:value])) + loc result, val[1], val[2] + + result + end +.,., + +# reduce 75 omitted + +# reduce 76 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 212) + def _reduce_77(val, _values, result) + result = Factory.LAMBDA(val[0], val[2]) +# loc result, val[1] # TODO + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 217) + def _reduce_78(val, _values, result) + result = val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 218) + def _reduce_79(val, _values, result) + result = nil + result + end +.,., + +# reduce 80 omitted + +# reduce 81 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 228) + def _reduce_82(val, _values, result) + result = Factory.LAMBDA(val[1], val[2]) + loc result, val[0], val[3] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 232) + def _reduce_83(val, _values, result) + result = Factory.LAMBDA(val[1], nil) + loc result, val[0], val[2] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 238) + def _reduce_84(val, _values, result) + result = [] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 239) + def _reduce_85(val, _values, result) + result = val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 249) + def _reduce_86(val, _values, result) + result = val[1] + loc(result, val[0], val[1]) + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 256) + def _reduce_87(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', 260) + def _reduce_88(val, _values, result) + result = Factory.IF(val[0], nil, val[3]) + loc(result, val[0], (val[3] ? val[3] : val[2])) + + result + end +.,., + +# reduce 89 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 268) + def _reduce_90(val, _values, result) + result = val[1] + loc(result, val[0], val[1]) + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 272) + def _reduce_91(val, _values, result) + result = Factory.block_or_expression(*val[2]) + loc result, val[0], val[3] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 276) + def _reduce_92(val, _values, result) + result = nil # don't think a nop is needed here either + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 285) + def _reduce_93(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', 289) + def _reduce_94(val, _values, result) + result = Factory.UNLESS(val[1], nil, nil) + loc result, val[0], val[4] + + result + end +.,., + +# reduce 95 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 299) + def _reduce_96(val, _values, result) + result = Factory.block_or_expression(*val[2]) + loc result, val[0], val[3] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 303) + def _reduce_97(val, _values, result) + result = nil # don't think a nop is needed here either + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 311) + def _reduce_98(val, _values, result) + result = Factory.CASE(val[1], *val[3]) + loc result, val[0], val[4] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 317) + def _reduce_99(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 318) + def _reduce_100(val, _values, result) + result = val[0].push val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 323) + def _reduce_101(val, _values, result) + result = Factory.WHEN(val[0], val[3]) + loc result, val[1], val[4] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 327) + def _reduce_102(val, _values, result) + result = Factory.WHEN(val[0], nil) + loc result, val[1], val[3] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 331) + def _reduce_103(val, _values, result) + result = val[0] + result + end +.,., + +# reduce 104 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 342) + def _reduce_105(val, _values, result) + result = val[1] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 347) + def _reduce_106(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 348) + def _reduce_107(val, _values, result) + result = val[0].push val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 353) + def _reduce_108(val, _values, result) + result = Factory.MAP(val[0], val[2]) ; loc result, val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 369) + def _reduce_109(val, _values, result) + result = val[0] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 372) + def _reduce_110(val, _values, result) + result = case Factory.resource_shape(val[1]) + when :resource, :class + tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) + tmp.form = val[0] + tmp + when :defaults + error "A resource default can not be virtual or exported" + when :override + error "A resource override can not be virtual or exported" + else + error "Expression is not valid as a resource, resource-default, or resource-override" + end + loc result, val[1], val[4] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 387) + def _reduce_111(val, _values, result) + result = case Factory.resource_shape(val[1]) + when :resource, :class + error "Defaults are not virtualizable" + when :defaults + error "Defaults are not virtualizable" + when :override + error "Defaults are not virtualizable" + else + error "Expression is not valid as a resource, resource-default, or resource-override" + end + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 399) + def _reduce_112(val, _values, result) + result = case Factory.resource_shape(val[0]) + when :resource, :class + Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) + when :defaults + error "A resource default can not specify a resource name" + when :override + error "A resource override does not allow override of name of resource" + else + error "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', 412) + def _reduce_113(val, _values, result) + result = case Factory.resource_shape(val[0]) + when :resource, :class + # This catches deprecated syntax. + error "All resource specifications require names" + when :defaults + Factory.RESOURCE_DEFAULTS(val[0], val[2]) + when :override + # This was only done for override in original - TODO shuld it be here at all + Factory.RESOURCE_OVERRIDE(val[0], val[2]) + else + error "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', 427) + def _reduce_114(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', 432) + def _reduce_115(val, _values, result) + result = Factory.RESOURCE_BODY(val[0], val[2]) + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 434) + def _reduce_116(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 437) + def _reduce_117(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 438) + def _reduce_118(val, _values, result) + result = val[0].push val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 443) + def _reduce_119(val, _values, result) + result = :virtual + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 444) + def _reduce_120(val, _values, result) + result = :exported + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 456) + def _reduce_121(val, _values, result) + result = Factory.COLLECT(val[0], val[1], val[3]) + loc result, val[0], val[5] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 460) + def _reduce_122(val, _values, result) + result = Factory.COLLECT(val[0], val[1], []) + loc result, val[0], val[1] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 465) + def _reduce_123(val, _values, result) + result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 466) + def _reduce_124(val, _values, result) + result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] + result + end +.,., + +# reduce 125 omitted + +# reduce 126 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 479) + def _reduce_127(val, _values, result) + result = [] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 480) + def _reduce_128(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 481) + def _reduce_129(val, _values, result) + result = val[0].push(val[2]) + result + end +.,., + +# reduce 130 omitted + +# reduce 131 omitted + +# reduce 132 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 497) + def _reduce_133(val, _values, result) + result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) + loc result, val[0], val[2] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 501) + def _reduce_134(val, _values, result) + result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) + loc result, val[0], val[2] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 511) + def _reduce_135(val, _values, result) + result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) + loc result, val[0], val[5] + @lexer.indefine = false + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 516) + def _reduce_136(val, _values, result) + result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) + loc result, val[0], val[4] + @lexer.indefine = false + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 531) + def _reduce_137(val, _values, result) + @lexer.namepop + result = 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', 536) + def _reduce_138(val, _values, result) + @lexer.namepop + result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) + loc result, val[0], val[5] + + result + end +.,., + +# reduce 139 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 544) + def _reduce_140(val, _values, result) + result = val[1] + result + end +.,., + +# reduce 141 omitted + +# reduce 142 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 561) + def _reduce_143(val, _values, result) + result = Factory.NODE(val[1], val[2], val[4]) + loc result, val[0], val[5] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 565) + def _reduce_144(val, _values, result) + result = Factory.NODE(val[1], val[2], nil) + loc result, val[0], val[4] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 575) + def _reduce_145(val, _values, result) + result = [result] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 576) + def _reduce_146(val, _values, result) + result = val[0].push(val[2]) + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 581) + def _reduce_147(val, _values, result) + result = Factory.fqn(val[0][:value]); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 582) + def _reduce_148(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 583) + def _reduce_149(val, _values, result) + result = Factory.literal(:default); loc result, val[0] + result + end +.,., + +# reduce 150 omitted + +# reduce 151 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 589) + def _reduce_152(val, _values, result) + result = val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 594) + def _reduce_153(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 595) + def _reduce_154(val, _values, result) + result = val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 599) + def _reduce_155(val, _values, result) + result = [] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 600) + def _reduce_156(val, _values, result) + result = [] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 601) + def _reduce_157(val, _values, result) + result = val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 605) + def _reduce_158(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 606) + def _reduce_159(val, _values, result) + result = val[0].push(val[2]) + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 610) + def _reduce_160(val, _values, result) + result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 611) + def _reduce_161(val, _values, result) + result = Factory.PARAM(val[0][:value]); loc result, val[0] + result + end +.,., + +# reduce 162 omitted + +# reduce 163 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 624) + def _reduce_164(val, _values, result) + result = Factory.fqn(val[0][:value]).var ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 630) + def _reduce_165(val, _values, result) + result = Factory.LIST(val[1]); loc result, val[0], val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 631) + def _reduce_166(val, _values, result) + result = Factory.LIST(val[1]); loc result, val[0], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 632) + def _reduce_167(val, _values, result) + result = Factory.literal([]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 635) + def _reduce_168(val, _values, result) + result = Factory.HASH(val[1]); loc result, val[0], val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 636) + def _reduce_169(val, _values, result) + result = Factory.HASH(val[1]); loc result, val[0], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 637) + def _reduce_170(val, _values, result) + result = Factory.literal({}) ; loc result, val[0], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 640) + def _reduce_171(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 641) + def _reduce_172(val, _values, result) + result = val[0].push val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 644) + def _reduce_173(val, _values, result) + result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] + result + end +.,., + +# reduce 174 omitted + +# reduce 175 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 650) + def _reduce_176(val, _values, result) + result = Factory.literal(val[0][:value]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 651) + def _reduce_177(val, _values, result) + result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 652) + def _reduce_178(val, _values, result) + result = Factory.literal(val[0][:value]); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 653) + def _reduce_179(val, _values, result) + result = Factory.literal(val[0][:value]); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 654) + def _reduce_180(val, _values, result) + result = Factory.literal(val[0][:value]); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 655) + def _reduce_181(val, _values, result) + result = [val[0]] + val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 656) + def _reduce_182(val, _values, result) + result = Factory.TEXT(val[0]) + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 659) + def _reduce_183(val, _values, result) + result = [val[0]] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 660) + def _reduce_184(val, _values, result) + result = [val[0]] + val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 662) + def _reduce_185(val, _values, result) + result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 663) + def _reduce_186(val, _values, result) + result = Factory.QREF(val[0][:value]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 664) + def _reduce_187(val, _values, result) + result = Factory.literal(:undef); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 665) + def _reduce_188(val, _values, result) + result = Factory.literal(:default); loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 670) + def _reduce_189(val, _values, result) + result = Factory.literal(val[0][:value]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 673) + def _reduce_190(val, _values, result) + result = Factory.literal(val[0][:value]); loc result, val[0] + result + end +.,., + +# reduce 191 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 679) + def _reduce_192(val, _values, result) + result = nil + result + end +.,., + +# reduce 193 omitted + +# reduce 194 omitted + +# reduce 195 omitted + +# reduce 196 omitted + +# reduce 197 omitted + +# reduce 198 omitted + +# reduce 199 omitted + +# reduce 200 omitted + +# reduce 201 omitted + +# reduce 202 omitted + +# reduce 203 omitted + +# reduce 204 omitted + +# reduce 205 omitted + +# reduce 206 omitted + +# reduce 207 omitted + +# reduce 208 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 702) + def _reduce_209(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/grammar.ra b/lib/puppet/pops/parser/grammar.ra new file mode 100644 index 000000000..7352bdbfb --- /dev/null +++ b/lib/puppet/pops/parser/grammar.ra @@ -0,0 +1,746 @@ +# vim: syntax=ruby + +# Parser using the Pops model +# This grammar is a half step between the current 3.1. grammar and egrammar. +# FIXME! Keep as reference until egrammar is proven to work. + +class Puppet::Pops::Impl::Parser::Parser + +token STRING DQPRE DQMID DQPOST +token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE +token FALSE EQUALS APPENDS LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT +token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN +token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN +token NAME SEMIC CASE DEFAULT AT 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 + +prechigh + left DOT +# left LBRACE +# left LCOLLECT LLCOLLECT + right NOT + nonassoc UMINUS + left IN MATCH NOMATCH + left TIMES DIV + left MINUS PLUS + left LSHIFT RSHIFT + left NOTEQUAL ISEQUAL + left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL + left AND + left OR +# left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB +preclow + +rule +# Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty +program + : statements { result = Factory.block_or_expression(*val[0]) } + | nil + +# Change may have issues with nil; i.e. program is a sequence of nils/nops +# Simplified from original which had validation for top level constructs - see statement rule +# Produces Array<Model::Expression> +statements + : statement { result = [val[0]]} + | statements statement { result = val[0].push val[1] } + +# Removed validation construct regarding "top level statements" as it did not seem to catch all problems +# and relied on a "top-level-ness" encoded in the abstract syntax tree objects +# +# The main list of valid statements +# Produces Model::Expression +# +statement + : resource + | virtual_resource + | collection + | assignment + | casestatement + | if_expression + | unless_expression + | import + | call_named_function + | definition + | hostclass + | nodedef + | resource_override + | append + | relationship + | call_method_with_lambda + +keyword + : AND + | CASE + | CLASS + | DEFAULT + | DEFINE + | ELSE + | ELSIF + | IF + | IN + | IMPORT + | INHERITS + | NODE + | OR + | UNDEF + | UNLESS + +# Produces Model::RelationshipExpression +relationship + : relationship_side edge relationship_side { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship edge relationship_side { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + +# Produces Model::Expression +relationship_side + : resource + | resourceref + | collection + | variable + | quotedtext + | selector + | casestatement + | hasharrayaccesses + +# Produces String +edge + : IN_EDGE + | OUT_EDGE + | IN_EDGE_SUB + | OUT_EDGE_SUB + +# Produces Model::CallNamedFunctionExpression +call_named_function + : NAME LPAREN expressions RPAREN { result = Factory.CALL_NAMED(val[0][:value], false, val[2]) ; loc result, val[0], val[3] } + | NAME LPAREN expressions COMMA RPAREN { result = Factory.CALL_NAMED(val[0][:value], false, val[2]) ; loc result, val[0], val[4] } + | NAME LPAREN RPAREN { result = Factory.CALL_NAMED(val[0][:value], false, []) ; loc result, val[0], val[2] } + | NAME func_call_args { result = Factory.CALL_NAMED(val[0][:value], false, val[1]) ; loc result, val[0] } + +call_method_with_lambda + : call_method { result = val[0] } + | call_method lambda { result = val[0]; val[0].lambda = val[1] } + +call_method + : named_access LPAREN expressions 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 { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] } + +named_access + : named_access_lval DOT NAME { + result = val[0].dot(Factory.fqn(val[2][:value])) + loc result, val[1], val[2] + } + +# Obviously not ideal, it is not possible to use literal array or hash as lhs +# These must be assigned to a variable - this is also an issue in other places +# +named_access_lval + : variable + | hasharrayaccesses + | selector + | quotedtext + | call_named_rval_function + +lambda + : LAMBDA lambda_parameter_list statements RBRACE { + result = Factory.LAMBDA(val[1], val[2]) + loc result, val[0], val[3] + } + | LAMBDA lambda_parameter_list RBRACE { + result = Factory.LAMBDA(val[1], nil) + loc result, val[0], val[2] + } +# Produces Array<Model::Parameter> +lambda_parameter_list + : PIPE PIPE { result = [] } + | PIPE parameters endcomma PIPE { result = val[1] } + +# Produces Array<Model::Expression> +func_call_args + : rvalue { result = [val[0]] } + | func_call_args COMMA rvalue { result = val[0].push(val[2]) } + +# Produces Array<Model::Expression> +expressions + : expression { result = [val[0]] } + | expressions comma expression { result = val[0].push(val[2]) } + + +# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] +resource + : classname LBRACE resourceinstances endsemi RBRACE { + result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) + loc result, val[0], val[4] + } + | classname LBRACE attribute_operations endcomma RBRACE { + # This is a deprecated syntax. + # It also fails hard - TODO: create model and validate this case + error "All resource specifications require names" + } + | type LBRACE attribute_operations endcomma RBRACE { + # a defaults setting for a type + result = Factory.RESOURCE_DEFAULTS(val[0], val[2]) + loc result, val[0], val[4] + } + +# Override a value set elsewhere in the configuration. +# Produces Model::ResourceOverrideExpression +resource_override + : resourceref LBRACE attribute_operations endcomma RBRACE { + @lexer.commentpop + result = Factory.RESOURCE_OVERRIDE(val[0], val[2]) + loc result, val[0], val[4] + } + +# Exported and virtual resources; these don't get sent to the client +# unless they get collected elsewhere in the db. +# The original had validation here; checking if storeconfigs is on; this is moved to a validation step +# Also, validation was performed if an attempt was made to virtualize or export a resource defaults +# this is also now deferred to validation +# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] +virtual_resource + : at resource { + val[1].form = val[0] # :virtual, :exported, (or :regular) + result = val[1] + } + +# Produces Symbol corresponding to resource form +at + : AT { result = :virtual } + | AT AT { result = :exported } + +# A collection statement. Currently supports no arguments at all, but eventually +# will, I assume. +# +# Produces Model::CollectExpression +# +collection + : type collect_query LBRACE attribute_operations endcomma RBRACE { + @lexer.commentpop + result = Factory.COLLECT(val[0].value.downcase, val[1], val[3]) + loc result, val[0], val[5] + } + | type collect_query { + result = Factory.COLLECT(val[0].value.downcase, 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] } + +# ORIGINAL COMMENT: A mini-language for handling collection comparisons. This is organized +# to avoid the need for precedence indications. +# (New implementation is slightly different; and when finished, it may be possible to streamline the +# grammar - the difference is mostly in evaluation, not in grammar) +# +optional_query + : nil + | query + +# ORIGINAL: Had a weird list structure where AND and OR where at the same level, and hence, there was the +# need to keep track of where parenthesis were (to get order correct). +# +# This is now not needed as AND has higher precedence than OR, and parenthesis are low in precedence + +query + : predicate_lval ISEQUAL expression { result = (val[0] == val[2]) ; loc result, val[1] } + | predicate_lval NOTEQUAL expression { result = (val[0].ne(val[2])) ; loc result, val[1] } + | LPAREN query RPAREN { result = val[1] } + | query AND query { result = val[0].and(val[2]) ; loc result, val[1] } + | query OR query { result = val[0].or(val[2]) ; loc result, val[1] } + + +# Produces Model::VariableExpression, or Model::QualifiedName +predicate_lval + : variable + | name + +resourceinst + : resourcename COLON attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } + +resourceinstances + : resourceinst { result = [val[0]] } + | resourceinstances SEMIC resourceinst { result = val[0].push val[2] } + + +resourcename + : quotedtext + | name + | type + | selector + | variable + | array + | hasharrayaccesses + +# Assignment, only assignment to variable is legal, but parser builds expression for [] = anyway to +# enable a better error message +assignment + : VARIABLE EQUALS expression { result = Factory.var(Factory.fqn(val[0][:value])).set(val[2]) ; loc result, val[1] } + | hasharrayaccess EQUALS expression { result val[0].set(val[2]); loc result, val[1] } + +append + : VARIABLE APPENDS expression { result = Factory.var(val[0][:value]).plus_set(val[1]) ; loc result, val[1] } + +# Produces Array<Model::AttributeOperation> +attribute_operations + : { result = [] } + | attribute_operation { result = [val[0]] } + | attribute_operations COMMA attribute_operation { result = val[0].push(val[2]) } + +# Produces String +attribute_name + : NAME + | keyword + | BOOLEAN + +# Several grammar issues here: the addparam did not allow keyword and booleans as names. +# In this version, the wrong combinations are validated instead of producing syntax errors +# (Can give nicer error message +> is not applicable to...) +# WAT - Boolean as attribute name? +# 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] + } + +# Produces Model::CallNamedFunction +call_named_rval_function + : NAME LPAREN expressions RPAREN { result = Factory.CALL_NAMED(val[0][:value], true, val[2]) ; loc result, val[0], val[3] } + | NAME LPAREN RPAREN { result = Factory.CALL_NAMED(val[0][:value], true, []) ; loc result, val[0], val[2] } + +quotedtext + : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } + | 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] } +text_expression : expression { result = Factory.TEXT(val[0]) } + +dqrval + : text_expression dqtail { result = [val[0]] + val[1] } + +dqtail + : dqpost { result = [val[0]] } + | dqmid dqrval { result = [val[0]] + val[1] } + + +# Reference to Resource (future also reference to other instances of other types than Resources). +# First form (lower case name) is deprecated (deprecation message handled in validation). Note that +# this requires use of token NAME since a rule call to name causes shift reduce conflict with +# a function call NAME NAME (calling function with NAME as argument e.g. foo bar). +# +# Produces InstanceReference +resourceref + : NAME LBRACK expressions RBRACK { + # Would want to use rule name here, but can't (need a NAME with higher precedence), so must + # create a QualifiedName instance here for NAME + result = Factory.INSTANCE(Factory.QNAME_OR_NUMBER(val[0][:value]), val[2]); + loc result, val[0], val[2][-1] + } + | type LBRACK expressions RBRACK { + result = Factory.INSTANCE(val[0], val[2]); + loc result, val[0], val[2][-1] + } + +# Changed from Puppet 3x where there is no else part on unless +# +unless_expression + : UNLESS expression LBRACE statements RBRACE unless_else { + @lexer.commentpop + 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 { + @lexer.commentpop + result = Factory.UNLESS(val[1], nil, nil) + 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 { + @lexer.commentpop + result = Factory.block_or_expression(*val[2]) + loc result, val[0], val[3] + } + | ELSE LBRACE RBRACE { + @lexer.commentpop + result = nil # don't think a nop is needed here either + } + +# Produces Model::IfExpression +if_expression + : IF if_expression_part { + result = val[1] + } + +# Produces Model::IfExpression +if_expression_part + : expression LBRACE statements RBRACE else { + @lexer.commentpop + 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_expression_part { result = val[1] } + | ELSE LBRACE statements RBRACE { + @lexer.commentpop + result = Factory.block_or_expression(*val[2]) + loc result, val[0], val[3] + } + | ELSE LBRACE RBRACE { + @lexer.commentpop + result = nil # don't think a nop is needed here either + } + +# Produces Model::Expression +expression + : rvalue + | hash + | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } + | expression MATCH match_rvalue { result = val[0] =~ val[2] ; loc result, val[1] } + | expression NOMATCH match_rvalue { 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 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] } + | 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] } + | LPAREN expression RPAREN { result = val[1] ; } + | call_method_with_lambda + +match_rvalue + : regex + | quotedtext + +# Produces Model::CaseExpression +casestatement + : CASE expression LBRACE case_options RBRACE { + @lexer.commentpop + result = Factory.CASE(val[1], *val[3]) + loc result, val[0], val[4] + } + +# Produces Array<Model::CaseOption> +case_options + : case_option { result = [val[0]] } + | case_options case_option { result = val[0].push val[1] } + +# Produced Model::CaseOption (aka When) +case_option + : case_values COLON LBRACE statements RBRACE { + @lexer.commentpop + result = Factory.WHEN(val[0], val[3]) + loc result, val[1], val[4] + } + | case_values COLON LBRACE RBRACE { + @lexer.commentpop + result = Factory.WHEN(val[0], nil) + loc result, val[1], val[3] + } + +# Produces Array<Expression> mostly literals +case_values + : selectable { result = [val[0]] } + | case_values COMMA selectable { result = val[0].push val[2] } + +# Produces Model::SelectorExpression +selector + : selectable QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[1] } + +# Produces Array<Model::SelectorEntry> +selector_entries + : selector_entry { result = [val[0]] } + | LBRACE selector_entry_list endcomma RBRACE { + @lexer.commentpop + result = val[1] + } + +# Produces Array<Model::SelectorEntry> +selector_entry_list + : selector_entry { result = [val[0]] } + | selector_entry_list COMMA selector_entry { result = val[0].push val[2] } + +# Produces a Model::SelectorEntry +selector_entry + : selectable FARROW rvalue { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } + +# Produces Model::Expression (most of the literals) +selectable + : name + | type + | quotedtext + | variable + | call_named_rval_function + | boolean + | undef + | hasharrayaccess + | default + | regex + + + +# Produces nil (noop) +import + : IMPORT strings { + error "Import not supported in this version of the parser", \ + :line => stmt.context[:line], :file => stmt.context[:file] + result = nil + } + +# IMPORT (T.B DEPRECATED IN PUPPET WHEN IT HAS BEEN FIGURED OUT HOW TO SUPPORT +# THE THINGS IMPORTS ARE USED FOR. +# BOLDLY DECIDED TO SKIP THIS COMPLETELY IN THIS IMPLEMENTATION - will trigger an error +# +# These are only used for importing, no interpolation +string + : STRING { result = [val[0][:value]] } + +strings + : string + | strings COMMA string { result = val[0].push val[2] } + +# Produces Model::Definition +definition + : DEFINE classname parameter_list LBRACE statements RBRACE { + @lexer.commentpop + result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) + loc result, val[0], val[5] + @lexer.indefine = false + } + | DEFINE classname parameter_list LBRACE RBRACE { + @lexer.commentpop + result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) + loc result, val[0], val[4] + @lexer.indefine = false + } + +# ORIGINAL COMMENT: Our class gets defined in the parent namespace, not our own. +# WAT ??! This is way odd; should get its complete name, classnames do not nest +# Seems like the call to classname makes use of the name scope +# (This is uneccesary, since the parent name is known when evaluating) +# +# Produces Model::HostClassDefinition +# +hostclass + : CLASS classname parameter_list classparent LBRACE statements RBRACE { + @lexer.commentpop + @lexer.namepop + result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) + loc result, val[0], val[6] + } + | CLASS classname parameter_list classparent LBRACE RBRACE { + @lexer.commentpop + @lexer.namepop + result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) + loc result, val[0], val[5] + } + +# Produces Model::NodeDefinition +nodedef + : NODE hostnames nodeparent LBRACE statements RBRACE { + @lexer.commentpop + result = Factory.NODE(val[1], val[2], val[4]) + loc result, val[0], val[5] + } + | NODE hostnames nodeparent LBRACE RBRACE { + @lexer.commentpop + result = Factory.NODE(val[1], val[2], nil) + loc result, val[0], val[4] + } + +# String result +classname + : NAME { result = val[0] } + | CLASS { result = val[0] } + +# 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<Model::LiteralExpression> +# +hostnames + : nodename { result = [result] } + | hostnames COMMA nodename { result = val[0].push(val[2]) } + +# Produces Model::LiteralExpression +# +nodename + : hostname + +# Produces a LiteralExpression (string, :default, or regexp) +hostname + : NAME { result = Factory.fqn(val[0][:value]); loc result, val[0] } + | STRING { result = Factory.literal(val[0][:value]); loc result, val[0] } + | DEFAULT { result = Factory.literal(:default); loc result, val[0] } + | regex + + +# Produces Array<Model::Parameter> +parameter_list + : nil { result = [] } + | LPAREN RPAREN { result = [] } + | LPAREN parameters endcomma RPAREN { result = val[1] } + +# Produces Array<Model::Parameter> +parameters + : parameter { result = [val[0]] } + | parameters COMMA parameter { result = val[0].push(val[2]) } + +# Produces Model::Parameter +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] } + +# Produces Expression, since hostname is an Expression +nodeparent + : nil + | INHERITS hostname { result = val[1] } + +# 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 + +rvalue + : quotedtext + | name + | type + | boolean + | selector + | variable + | array + | hasharrayaccesses + | resourceref + | call_named_rval_function + | undef + +array + : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } + | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } + | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] } + + +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[3] } + +hashpairs + : hashpair { result = [val[0]] } + | hashpairs COMMA hashpair { result = val[0].push val[2] } + +hashpair + : key FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } + +key + : NAME { result = Factory.literal(val[0][:value]) ; loc result, val[0] } + | quotedtext { result = val[0] } + +# NOTE: Limitation that LHS is a variable, means that it is not possible to do foo(10)[2] without +# using an intermediate variable +# +hasharrayaccess + : variable LBRACK expression RBRACK { result = val[0][val[2]]; loc result, val[0], val[3] } + +hasharrayaccesses + : hasharrayaccess + | hasharrayaccesses LBRACK expression RBRACK { result = val[0][val[2]] ; loc result, val[1], val[3] } + +# Produces Model::VariableExpression +variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } +undef : UNDEF { result = Factory.literal(:undef); loc result, val[0] } +name : NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } +type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } + +default + : DEFAULT { result = Factory.literal(:default); loc result, val[0] } + +boolean + # Assumes lexer produces a Boolean value for booleans, or this will go wrong (e.g. produce. LiteralString) + : BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } + +regex + : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } + +# ---Special markers & syntactic sugar + +# WAT !!!! this means array can be [1=>2=>3], func (1=>2=>3), and other retarded constructs +# TODO: Remove the FARROW (investigate if there is any validity) +comma + : FARROW + | COMMA + +endcomma + : # + | COMMA { result = nil } + +endsemi + : # + | SEMIC + +nil + : { result = nil} + +## Empty list - not really needed? TODO: Check if this can be removed +#empty_list +# : { result = [] } + +end + +---- header ---- +require 'puppet' +require 'puppet/util/loadedfile' +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/lexer.rb b/lib/puppet/pops/parser/lexer.rb new file mode 100644 index 000000000..106f407ba --- /dev/null +++ b/lib/puppet/pops/parser/lexer.rb @@ -0,0 +1,842 @@ +# the scanner/lexer + +require 'forwardable' +require 'strscan' +require 'puppet' +require 'puppet/util/methodhelper' + +module Puppet + class LexError < RuntimeError; end +end + +class Puppet::Pops::Parser::Lexer + extend Forwardable + + attr_reader :file, :lexing_context, :token_queue + + attr_reader :locator + + attr_accessor :indefine + alias :indefine? :indefine + + def lex_error msg + raise Puppet::LexError.new(msg) + end + + class Token + ALWAYS_ACCEPTABLE = Proc.new { |context| true } + + include Puppet::Util::MethodHelper + + attr_accessor :regex, :name, :string, :skip, :skip_text + alias skip? skip + + # @param string_or_regex[String] a literal string token matcher + # @param string_or_regex[Regexp] a regular expression token text matcher + # @param name [String] the token name (what it is known as in the grammar) + # @param options [Hash] see {#set_options} + # + def initialize(string_or_regex, name, options = {}) + if string_or_regex.is_a?(String) + @name, @string = name, string_or_regex + @regex = Regexp.new(Regexp.escape(string_or_regex)) + else + @name, @regex = name, string_or_regex + end + + set_options(options) + @acceptable_when = ALWAYS_ACCEPTABLE + end + + # @return [String] human readable token reference; the String if literal, else the token name + def to_s + string or @name.to_s + end + + # @return [Boolean] if the token is acceptable in the given context or not. + # this implementation always returns true. + # @param context [Hash] ? ? ? + # + def acceptable?(context={}) + @acceptable_when.call(context) + end + + + # Defines when the token is able to match. + # This provides context that cannot be expressed otherwise, such as feature flags. + # + # @param block [Proc] a proc that given a context returns a boolean + def acceptable_when(block) + @acceptable_when = block + end + end + + # Maintains a list of tokens. + class TokenList + extend Forwardable + + attr_reader :regex_tokens, :string_tokens + def_delegator :@tokens, :[] + # Adds a new token to the set of recognized tokens + # @param name [String] the token name + # @param regex [Regexp, String] source text token matcher, a litral string or regular expression + # @param options [Hash] see {Token::set_options} + # @param block [Proc] optional block set as the created tokens `convert` method + # @raise [ArgumentError] if the token with the given name is already defined + # + def add_token(name, regex, options = {}, &block) + raise(ArgumentError, "Token #{name} already exists") if @tokens.include?(name) + token = Token.new(regex, name, options) + @tokens[token.name] = token + if token.string + @string_tokens << token + @tokens_by_string[token.string] = token + else + @regex_tokens << token + end + + token.meta_def(:convert, &block) if block_given? + + token + end + + # Creates an empty token list + # + def initialize + @tokens = {} + @regex_tokens = [] + @string_tokens = [] + @tokens_by_string = {} + end + + # Look up a token by its literal (match) value, rather than name. + # @param string [String, nil] the literal match string to obtain a {Token} for, or nil if it does not exist. + def lookup(string) + @tokens_by_string[string] + end + + # Adds tokens from a hash where key is a matcher (literal string or regexp) and the + # value is the token's name + # @param hash [Hash<{String => Symbol}, Hash<{Regexp => Symbol}] map token text matcher to token name + # @return [void] + # + def add_tokens(hash) + hash.each do |regex, name| + add_token(name, regex) + end + end + + # Sort literal (string-) tokens by length, so we know once we match, we're done. + # This helps avoid the O(n^2) nature of token matching. + # The tokens are sorted in place. + # @return [void] + def sort_tokens + @string_tokens.sort! { |a, b| b.string.length <=> a.string.length } + end + + # Yield each token name and value in turn. + def each + @tokens.each {|name, value| yield name, value } + end + end + + TOKENS = TokenList.new + TOKENS.add_tokens( + '[' => :LBRACK, + ']' => :RBRACK, + # '{' => :LBRACE, # Specialized to handle lambda + '}' => :RBRACE, + '(' => :LPAREN, + ')' => :RPAREN, + '=' => :EQUALS, + '+=' => :APPENDS, + '==' => :ISEQUAL, + '>=' => :GREATEREQUAL, + '>' => :GREATERTHAN, + '<' => :LESSTHAN, + '<=' => :LESSEQUAL, + '!=' => :NOTEQUAL, + '!' => :NOT, + ',' => :COMMA, + '.' => :DOT, + ':' => :COLON, + '@' => :AT, + '|' => :PIPE, + '<<|' => :LLCOLLECT, + '|>>' => :RRCOLLECT, + '->' => :IN_EDGE, + '<-' => :OUT_EDGE, + '~>' => :IN_EDGE_SUB, + '<~' => :OUT_EDGE_SUB, + '<|' => :LCOLLECT, + '|>' => :RCOLLECT, + ';' => :SEMIC, + '?' => :QMARK, + '\\' => :BACKSLASH, + '=>' => :FARROW, + '+>' => :PARROW, + '+' => :PLUS, + '-' => :MINUS, + '/' => :DIV, + '*' => :TIMES, + '%' => :MODULO, + '<<' => :LSHIFT, + '>>' => :RSHIFT, + '=~' => :MATCH, + '!~' => :NOMATCH, + %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF, + "<string>" => :STRING, + "<dqstring up to first interpolation>" => :DQPRE, + "<dqstring between two interpolations>" => :DQMID, + "<dqstring after final interpolation>" => :DQPOST, + "<boolean>" => :BOOLEAN, + "<lambda start>" => :LAMBDA, # A LBRACE followed by '|' + "<select start>" => :SELBRACE # A QMARK followed by '{' + ) + + module Contextual + QUOTE_TOKENS = [:DQPRE,:DQMID] + REGEX_INTRODUCING_TOKENS = [:NODE,:LBRACE, :SELBRACE, :RBRACE,:MATCH,:NOMATCH,:COMMA] + + NOT_INSIDE_QUOTES = Proc.new do |context| + !QUOTE_TOKENS.include? context[:after] + end + + INSIDE_QUOTES = Proc.new do |context| + QUOTE_TOKENS.include? context[:after] + end + + IN_REGEX_POSITION = Proc.new do |context| + REGEX_INTRODUCING_TOKENS.include? context[:after] + end + + IN_STRING_INTERPOLATION = Proc.new do |context| + context[:string_interpolation_depth] > 0 + end + + DASHED_VARIABLES_ALLOWED = Proc.new do |context| + Puppet[:allow_variables_with_dashes] + end + + VARIABLE_AND_DASHES_ALLOWED = Proc.new do |context| + Contextual::DASHED_VARIABLES_ALLOWED.call(context) and TOKENS[:VARIABLE].acceptable?(context) + end + end + + # LBRACE needs look ahead to differentiate between '{' and a '{' + # followed by a '|' (start of lambda) The racc grammar can only do one + # token lookahead. + # + TOKENS.add_token :LBRACE, /\{/ do | lexer, value | + if lexer.match?(/[ \t\r]*\|/) + [TOKENS[:LAMBDA], value] + elsif lexer.lexing_context[:after] == :QMARK + [TOKENS[:SELBRACE], value] + else + [TOKENS[:LBRACE], value] + end + end + + # Numbers are treated separately from names, so that they may contain dots. + TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value| + lexer.assert_numeric(value) + [TOKENS[:NAME], value] + end + TOKENS[:NUMBER].acceptable_when Contextual::NOT_INSIDE_QUOTES + + TOKENS.add_token :NAME, %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} do |lexer, value| + # A name starting with a number must be a valid numeric string (not that + # NUMBER token captures those names that do not comply with the name rule. + if value =~ /^[0-9].*$/ + lexer.assert_numeric(value) + end + + string_token = self + # we're looking for keywords here + if tmp = KEYWORDS.lookup(value) + string_token = tmp + if [:TRUE, :FALSE].include?(string_token.name) + value = eval(value) + string_token = TOKENS[:BOOLEAN] + end + end + [string_token, value] + end + [:NAME, :CLASSREF].each do |name_token| + TOKENS[name_token].acceptable_when Contextual::NOT_INSIDE_QUOTES + end + + TOKENS.add_token :COMMENT, %r{#.*}, :skip => true do |lexer,value| + value.sub!(/# ?/,'') + [self, value] + end + + TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :skip => true do |lexer, value| + value.sub!(/^\/\* ?/,'') + value.sub!(/ ?\*\/$/,'') + [self,value] + end + + TOKENS.add_token :REGEX, %r{/[^/\n]*/} do |lexer, value| + # Make sure we haven't matched an escaped / + while value[-2..-2] == '\\' + other = lexer.scan_until(%r{/}) + value += other + end + regex = value.sub(%r{\A/}, "").sub(%r{/\Z}, '').gsub("\\/", "/") + [self, Regexp.new(regex)] + end + TOKENS[:REGEX].acceptable_when Contextual::IN_REGEX_POSITION + + TOKENS.add_token :RETURN, "\n", :skip => true, :skip_text => true + + TOKENS.add_token :SQUOTE, "'" do |lexer, value| + [TOKENS[:STRING], lexer.slurpstring(value,["'"],:ignore_invalid_escapes).first ] + end + + DQ_initial_token_types = {'$' => :DQPRE,'"' => :STRING} + DQ_continuation_token_types = {'$' => :DQMID,'"' => :DQPOST} + + TOKENS.add_token :DQUOTE, /"/ do |lexer, value| + lexer.tokenize_interpolated_string(DQ_initial_token_types) + end + + TOKENS.add_token :DQCONT, /\}/ do |lexer, value| + lexer.tokenize_interpolated_string(DQ_continuation_token_types) + end + TOKENS[:DQCONT].acceptable_when Contextual::IN_STRING_INTERPOLATION + + TOKENS.add_token :DOLLAR_VAR_WITH_DASH, %r{\$(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value| + lexer.warn_if_variable_has_hyphen(value) + + [TOKENS[:VARIABLE], value[1..-1]] + end + TOKENS[:DOLLAR_VAR_WITH_DASH].acceptable_when Contextual::DASHED_VARIABLES_ALLOWED + + TOKENS.add_token :DOLLAR_VAR, %r{\$(::)?(\w+::)*\w+} do |lexer, value| + [TOKENS[:VARIABLE],value[1..-1]] + end + + TOKENS.add_token :VARIABLE_WITH_DASH, %r{(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value| + lexer.warn_if_variable_has_hyphen(value) + # If the varname (following $, or ${ is followed by (, it is a function call, and not a variable + # reference. + # + if lexer.match?(%r{[ \t\r]*\(}) + [TOKENS[:NAME],value] + else + [TOKENS[:VARIABLE], value] + end + end + TOKENS[:VARIABLE_WITH_DASH].acceptable_when Contextual::VARIABLE_AND_DASHES_ALLOWED + + TOKENS.add_token :VARIABLE, %r{(::)?(\w+::)*\w+} do |lexer, value| + # If the varname (following $, or ${ is followed by (, it is a function call, and not a variable + # reference. + # + if lexer.match?(%r{[ \t\r]*\(}) + [TOKENS[:NAME],value] + else + [TOKENS[:VARIABLE],value] + end + + end + TOKENS[:VARIABLE].acceptable_when Contextual::INSIDE_QUOTES + + TOKENS.sort_tokens + + @@pairs = { + "{" => "}", + "(" => ")", + "[" => "]", + "<|" => "|>", + "<<|" => "|>>", + "|" => "|" + } + + KEYWORDS = TokenList.new + KEYWORDS.add_tokens( + "case" => :CASE, + "class" => :CLASS, + "default" => :DEFAULT, + "define" => :DEFINE, + # "import" => :IMPORT, + "if" => :IF, + "elsif" => :ELSIF, + "else" => :ELSE, + "inherits" => :INHERITS, + "node" => :NODE, + "and" => :AND, + "or" => :OR, + "undef" => :UNDEF, + "false" => :FALSE, + "true" => :TRUE, + "in" => :IN, + "unless" => :UNLESS + ) + + def clear + initvars + end + + def expected + return nil if @expected.empty? + name = @expected[-1] + TOKENS.lookup(name) or lex_error "Internal Lexer Error: Could not find expected token #{name}" + end + + # scan the whole file + # basically just used for testing + def fullscan + array = [] + + self.scan { |token, str| + # Ignore any definition nesting problems + @indefine = false + array.push([token,str]) + } + array + end + + def file=(file) + @file = file + contents = File.exists?(file) ? File.read(file) : "" + @scanner = StringScanner.new(contents) + @locator = Locator.new(contents, multibyte?) + end + + def_delegator :@token_queue, :shift, :shift_token + + def find_string_token + # We know our longest string token is three chars, so try each size in turn + # until we either match or run out of chars. This way our worst-case is three + # tries, where it is otherwise the number of string token we have. Also, + # the lookups are optimized hash lookups, instead of regex scans. + # + s = @scanner.peek(3) + token = TOKENS.lookup(s[0,3]) || TOKENS.lookup(s[0,2]) || TOKENS.lookup(s[0,1]) + [ token, token && @scanner.scan(token.regex) ] + end + + # Find the next token that matches a regex. We look for these first. + def find_regex_token + best_token = nil + best_length = 0 + + # I tried optimizing based on the first char, but it had + # a slightly negative affect and was a good bit more complicated. + TOKENS.regex_tokens.each do |token| + if length = @scanner.match?(token.regex) and token.acceptable?(lexing_context) + # We've found a longer match + if length > best_length + best_length = length + best_token = token + end + end + end + + return best_token, @scanner.scan(best_token.regex) if best_token + end + + # Find the next token, returning the string and the token. + def find_token + shift_token || find_regex_token || find_string_token + end + + def initialize + @multibyte = init_multibyte + initvars + end + + def assert_numeric(value) + if value =~ /^0[xX].*$/ + lex_error (positioned_message("Not a valid hex number #{value}")) unless value =~ /^0[xX][0-9A-Fa-f]+$/ + elsif value =~ /^0[^.].*$/ + lex_error(positioned_message("Not a valid octal number #{value}")) unless value =~ /^0[0-7]+$/ + else + lex_error(positioned_message("Not a valid decimal number #{value}")) unless value =~ /0?\d+(?:\.\d+)?(?:[eE]-?\d+)?/ + end + end + + # Returns true if ruby version >= 1.9.3 since regexp supports multi-byte matches and expanded + # character categories like [[:blank:]]. + # + # This implementation will fail if there are more than 255 minor or micro versions of ruby + # + def init_multibyte + numver = RUBY_VERSION.split(".").collect {|s| s.to_i } + return true if (numver[0] << 16 | numver[1] << 8 | numver[2]) >= (1 << 16 | 9 << 8 | 3) + false + end + + def multibyte? + @multibyte + end + + def initvars + @previous_token = nil + @scanner = nil + @file = nil + + # AAARRGGGG! okay, regexes in ruby are bloody annoying + # no one else has "\n" =~ /\s/ + + if multibyte? + # Skip all kinds of space, and CR, but not newlines + @skip = %r{[[:blank:]\r]+} + else + @skip = %r{[ \t\r]+} + end + + @namestack = [] + @token_queue = [] + @indefine = false + @expected = [] + @lexing_context = { + :after => nil, + :start_of_line => true, + :offset => 0, # byte offset before where token starts + :end_offset => 0, # byte offset after scanned token + :string_interpolation_depth => 0 + } + end + + # Make any necessary changes to the token and/or value. + def munge_token(token, value) + # A token may already have been munged (converted and positioned) + # + return token, value if value.is_a? Hash + + skip if token.skip_text + + return if token.skip + + token, value = token.convert(self, value) if token.respond_to?(:convert) + + return unless token + + return if token.skip + + # If the conversion performed the munging/positioning + return token, value if value.is_a? Hash + + pos_hash = position_in_source + pos_hash[:value] = value + + # Add one to pos, first char on line is 1 + return token, pos_hash + end + + # Returns a hash with the current position in source based on the current lexing context + # + def position_in_source + pos = @locator.pos_on_line(lexing_context[:offset]) + offset = @locator.char_offset(lexing_context[:offset]) + length = @locator.char_length(lexing_context[:offset], lexing_context[:end_offset]) + start_line = @locator.line_for_offset(lexing_context[:offset]) + + return { :line => start_line, :pos => pos, :offset => offset, :length => length} + end + + def pos + @locator.pos_on_line(lexing_context[:offset]) + end + + # Handling the namespace stack + def_delegator :@namestack, :pop, :namepop + + # This value might have :: in it, but we don't care -- it'll be handled + # normally when joining, and when popping we want to pop this full value, + # however long the namespace is. + def_delegator :@namestack, :<<, :namestack + + # Collect the current namespace. + def namespace + @namestack.join("::") + end + + def_delegator :@scanner, :rest + # this is the heart of the lexer + def scan + #Puppet.debug("entering scan") + lex_error "Internal Error: No string or file given to lexer to process." unless @scanner + + # Skip any initial whitespace. + skip + + until token_queue.empty? and @scanner.eos? do + yielded = false + offset = @scanner.pos + matched_token, value = find_token + end_offset = @scanner.pos + + # error out if we didn't match anything at all + lex_error "Could not match #{@scanner.rest[/^(\S+|\s+|.*)/]}" unless matched_token + + newline = matched_token.name == :RETURN + + lexing_context[:start_of_line] = newline + lexing_context[:offset] = offset + lexing_context[:end_offset] = end_offset + + final_token, token_value = munge_token(matched_token, value) + # update end position since munging may have moved the end offset + lexing_context[:end_offset] = @scanner.pos + + unless final_token + skip + next + end + + lexing_context[:after] = final_token.name unless newline + lexing_context[:string_interpolation_depth] += 1 if final_token.name == :DQPRE + lexing_context[:string_interpolation_depth] -= 1 if final_token.name == :DQPOST + + value = token_value[:value] + + if match = @@pairs[value] and final_token.name != :DQUOTE and final_token.name != :SQUOTE + @expected << match + elsif exp = @expected[-1] and exp == value and final_token.name != :DQUOTE and final_token.name != :SQUOTE + @expected.pop + end + + yield [final_token.name, token_value] + + if @previous_token + namestack(value) if @previous_token.name == :CLASS and value != '{' + + if @previous_token.name == :DEFINE + if indefine? + msg = "Cannot nest definition #{value} inside #{@indefine}" + self.indefine = false + raise Puppet::ParseError, msg + end + + @indefine = value + end + end + @previous_token = final_token + skip + end + # Cannot reset @scanner to nil here - it is needed to answer questions about context after + # completed parsing. + # Seems meaningless to do this. Everything will be gc anyway. + #@scanner = nil + + # This indicates that we're done parsing. + yield [false,false] + end + + # Skip any skipchars in our remaining string. + def skip + @scanner.skip(@skip) + end + + def match? r + @scanner.match?(r) + end + + # Provide some limited access to the scanner, for those + # tokens that need it. + def_delegator :@scanner, :scan_until + + # we've encountered the start of a string... + # slurp in the rest of the string and return it + def slurpstring(terminators,escapes=%w{ \\ $ ' " r n t s }+["\n"],ignore_invalid_escapes=false) + # we search for the next quote that isn't preceded by a + # backslash; the caret is there to match empty strings + last = @scanner.matched + tmp_offset = @scanner.pos + str = @scanner.scan_until(/([^\\]|^|[^\\])([\\]{2})*[#{terminators}]/) || lex_error(positioned_message("Unclosed quote after #{format_quote(last)} followed by '#{followed_by}'")) + str.gsub!(/\\(.)/m) { + ch = $1 + if escapes.include? ch + case ch + when 'r'; "\r" + when 'n'; "\n" + when 't'; "\t" + when 's'; " " + when "\n"; '' + else ch + end + else + Puppet.warning(positioned_message("Unrecognized escape sequence '\\#{ch}'")) unless ignore_invalid_escapes + "\\#{ch}" + end + } + [ str[0..-2],str[-1,1] ] + end + + # Formats given message by appending file, line and position if available. + def positioned_message msg + result = [msg] + result << "in file #{file}" if file + result << "at line #{line}:#{pos}" if line + result.join(" ") + end + + # Returns "<eof>" if at end of input, else the following 5 characters with \n \r \t escaped + def followed_by + return "<eof>" if @scanner.eos? + result = @scanner.rest[0,5] + "..." + result.gsub!("\t", '\t') + result.gsub!("\n", '\n') + result.gsub!("\r", '\r') + result + end + + def format_quote q + if q == "'" + '"\'"' + else + "'#{q}'" + end + end + + def tokenize_interpolated_string(token_type,preamble='') + # Expecting a (possibly empty) stretch of text terminated by end of string ", a variable $, or expression ${ + # The length of this part includes the start and terminating characters. + value,terminator = slurpstring('"$') + + # Advanced after '{' if this is in expression ${} interpolation + braced = terminator == '$' && @scanner.scan(/\{/) + # make offset to end_ofset be the length of the pre expression string including its start and terminating chars + lexing_context[:end_offset] = @scanner.pos + + token_queue << [TOKENS[token_type[terminator]],position_in_source().merge!({:value => preamble+value})] + variable_regex = if Puppet[:allow_variables_with_dashes] + TOKENS[:VARIABLE_WITH_DASH].regex + else + TOKENS[:VARIABLE].regex + end + if terminator != '$' or braced + return token_queue.shift + end + + tmp_offset = @scanner.pos + if var_name = @scanner.scan(variable_regex) + lexing_context[:offset] = tmp_offset + lexing_context[:end_offset] = @scanner.pos + warn_if_variable_has_hyphen(var_name) + # If the varname after ${ is followed by (, it is a function call, and not a variable + # reference. + # + if braced && @scanner.match?(%r{[ \t\r]*\(}) + token_queue << [TOKENS[:NAME], position_in_source().merge!({:value=>var_name})] + else + token_queue << [TOKENS[:VARIABLE],position_in_source().merge!({:value=>var_name})] + end + lexing_context[:offset] = @scanner.pos + tokenize_interpolated_string(DQ_continuation_token_types) + else + tokenize_interpolated_string(token_type, replace_false_start_with_text(terminator)) + end + end + + def replace_false_start_with_text(appendix) + last_token = token_queue.pop + value = last_token.last + if value.is_a? Hash + value[:value] + appendix + else + value + appendix + end + end + + # just parse a string, not a whole file + def string=(string) + @scanner = StringScanner.new(string) + @locator = Locator.new(string, multibyte?) + end + + def warn_if_variable_has_hyphen(var_name) + if var_name.include?('-') + Puppet.deprecation_warning("Using `-` in variable names is deprecated at #{file || '<string>'}:#{line}. See http://links.puppetlabs.com/puppet-hyphenated-variable-deprecation") + end + end + + # Returns the line number (starting from 1) for the current position + # in the scanned text (at the end of the last produced, but not necessarily + # consumed. + # + def line + return 1 unless lexing_context && locator + locator.line_for_offset(lexing_context[:end_offset]) + end + + # Helper class that keeps track of where line breaks are located and can answer questions about positions. + # + class Locator + attr_reader :line_index + attr_reader :string + + # Create a locator based on a content string, and a boolean indicating if ruby version support multi-byte strings + # or not. + # + def initialize(string, multibyte) + @string = string + @multibyte = multibyte + compute_line_index + end + + # Returns whether this a ruby version that supports multi-byte strings or not + # + def multibyte? + @multibyte + end + + # Computes the start offset for each line. + # + def compute_line_index + scanner = StringScanner.new(@string) + result = [0] # first line starts at 0 + while scanner.scan_until(/\n/) + result << scanner.pos + end + @line_index = result + end + + # Returns the line number (first line is 1) for the given offset + def line_for_offset(offset) + if line_nbr = line_index.index {|x| x > offset} + return line_nbr + end + # If not found it is after last + return line_index.size + end + + # Returns the offset on line (first offset on a line is 0). + # + def offset_on_line(offset) + line_offset = line_index[line_for_offset(offset)-1] + if multibyte? + @string.byteslice(line_offset, offset-line_offset).length + else + offset - line_offset + end + end + + # Returns the position on line (first position on a line is 1) + def pos_on_line(offset) + offset_on_line(offset) +1 + end + + # Returns the character offset for a given byte offset + def char_offset(byte_offset) + if multibyte? + @string.byteslice(0, byte_offset).length + else + byte_offset + end + end + + # Returns the length measured in number of characters from the given start and end byte offseta + def char_length(offset, end_offset) + if multibyte? + @string.byteslice(offset, end_offset - offset).length + else + end_offset - offset + end + end + end +end diff --git a/lib/puppet/pops/parser/makefile b/lib/puppet/pops/parser/makefile new file mode 100644 index 000000000..f521747cb --- /dev/null +++ b/lib/puppet/pops/parser/makefile @@ -0,0 +1,13 @@ + +#parser.rb: grammar.ra +# racc -o$@ grammar.ra +# +#grammar.output: grammar.ra +# racc -v -o$@ grammar.ra +# + +eparser.rb: egrammar.ra + racc -o$@ egrammar.ra + +egrammar.output: egrammar.ra + racc -v -o$@ egrammar.ra
\ No newline at end of file diff --git a/lib/puppet/pops/parser/parser_support.rb b/lib/puppet/pops/parser/parser_support.rb new file mode 100644 index 000000000..db35aad01 --- /dev/null +++ b/lib/puppet/pops/parser/parser_support.rb @@ -0,0 +1,203 @@ +require 'puppet/parser/functions' +require 'puppet/parser/files' +require 'puppet/resource/type_collection' +require 'puppet/resource/type_collection_helper' +require 'puppet/resource/type' +require 'monitor' + +# Supporting logic for the parser. +# This supporting logic has slightly different responsibilities compared to the original Puppet::Parser::Parser. +# It is only concerned with parsing. +# +class Puppet::Pops::Parser::Parser + # Note that the name of the contained class and the file name (currently parser_support.rb) + # needs to be different as the class is generated by Racc, and this file (parser_support.rb) is included as a mix in + # + + # Simplify access to the Model factory + # Note that the parser/parser support does not have direct knowledge about the Model. + # All model construction/manipulation is made by the Factory. + # + Factory = Puppet::Pops::Model::Factory + Model = Puppet::Pops::Model + + include Puppet::Resource::TypeCollectionHelper + + attr_accessor :lexer + + # Returns the token text of the given lexer token, or nil, if token is nil + def token_text t + return t if t.nil? + t = t.current if t.respond_to?(:current) + return t.value if t.is_a? Model::QualifiedName + + # else it is a lexer token + t[:value] + end + + # Produces the fully qualified name, with the full (current) namespace for a given name. + # + # This is needed because class bodies are lazily evaluated and an inner class' container(s) may not + # have been evaluated before some external reference is made to the inner class; its must therefore know its complete name + # before evaluation-time. + # + def classname(name) + [@lexer.namespace, name].join("::").sub(/^::/, '') + end + + # Reinitializes variables (i.e. creates a new lexer instance + # + def clear + initvars + end + + # Raises a Parse error. + def error(message, options = {}) + except = Puppet::ParseError.new(message) + except.line = options[:line] || @lexer.line + except.file = options[:file] || @lexer.file + except.pos = options[:pos] || @lexer.pos + + raise except + end + + # Parses a file expected to contain pp DSL logic. + def parse_file(file) + unless FileTest.exist?(file) + unless file =~ /\.pp$/ + file = file + ".pp" + end + end + @lexer.file = file + _parse() + end + + def initialize() + # Since the parser is not responsible for importing (removed), and does not perform linking, + # and there is no syntax that requires knowing if something referenced exists, it is safe + # to assume that no environment is needed when parsing. (All that comes later). + # + initvars + end + + # Initializes the parser support by creating a new instance of {Puppet::Pops::Parser::Lexer} + # @return [void] + # + def initvars + @lexer = Puppet::Pops::Parser::Lexer.new + end + + # This is a callback from the generated grammar (when an error occurs while parsing) + # TODO Picks up origin information from the lexer, probably needs this from the caller instead + # (for code strings, and when start line is not line 1 in a code string (or file), etc.) + # + def on_error(token,value,stack) + if token == 0 # denotes end of file + value = 'end of file' + else + value = "'#{value[:value]}'" + end + error = "Syntax error at #{value}" + + # The 'expected' is only of value at end of input, otherwise any parse error involving a + # start of a pair will be reported as expecting the close of the pair - e.g. "$x.each |$x {", would + # report that "seeing the '{', the '}' is expected. That would be wrong. + # Real "expected" tokens are very difficult to compute (would require parsing of racc output data). Output of the stack + # could help, but can require extensive backtracking and produce many options. + # + if token == 0 && brace = @lexer.expected + error += "; expected '#{brace}'" + end + + except = Puppet::ParseError.new(error) + except.line = @lexer.line + except.file = @lexer.file if @lexer.file + except.pos = @lexer.pos + + raise except + end + + # Parses a String of pp DSL code. + # @todo make it possible to pass a given origin + # + def parse_string(code) + @lexer.string = code + _parse() + end + + # Mark the factory wrapped model object with location information + # @todo the lexer produces :line for token, but no offset or length + # @return [Puppet::Pops::Model::Factory] the given factory + # @api private + # + def loc(factory, start_token, end_token = nil) + factory.record_position(sourcepos(start_token), sourcepos(end_token)) + end + + # Associate documentation with the factory wrapped model object. + # @return [Puppet::Pops::Model::Factory] the given factory + # @api private + def doc factory, doc_string + factory.doc = doc_string + end + + def sourcepos(o) + if !o + Puppet::Pops::Adapters::SourcePosAdapter.new + elsif o.is_a? Puppet::Pops::Model::Factory + # It is a built model element with loc set returns start at pos 0 + o.loc + else + loc = Puppet::Pops::Adapters::SourcePosAdapter.new + # It must be a token + loc.line = o[:line] + loc.pos = o[:pos] + loc.offset = o[:offset] + loc.length = o[:length] + loc + end + end + + def aryfy(o) + o = [o] unless o.is_a?(Array) + o + end + + # Transforms an array of expressions containing literal name expressions to calls if followed by an + # expression, or expression list + # + def transform_calls(expressions) + Factory.transform_calls(expressions) + end + + # Performs the parsing and returns the resulting model. + # The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}. + # + # TODO: Drop support for parsing a ruby file this way (should be done where it is decided + # which file to load/run (i.e. loaders), and initial file to run + # TODO: deal with options containing origin (i.e. parsing a string from externally known location). + # TODO: should return the model, not a Hostclass + # + # @api private + # + def _parse() + begin + @yydebug = false + main = yyparse(@lexer,:scan) + # #Commented out now because this hides problems in the racc grammar while developing + # # TODO include this when test coverage is good enough. + # rescue Puppet::ParseError => except + # except.line ||= @lexer.line + # except.file ||= @lexer.file + # except.pos ||= @lexer.pos + # raise except + # rescue => except + # raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, @lexer.pos, except) + end + main.record_origin(@lexer.file) if main + return main + ensure + @lexer.clear + end + +end diff --git a/lib/puppet/pops/patterns.rb b/lib/puppet/pops/patterns.rb new file mode 100644 index 000000000..d18384fcb --- /dev/null +++ b/lib/puppet/pops/patterns.rb @@ -0,0 +1,35 @@ +# The Patterns module contains common regular expression patters for the Puppet DSL language +module Puppet::Pops::Patterns + + # NUMERIC matches hex, octal, decimal, and floating point and captures three parts + # 0 = entire matched number, leading and trailing whitespace included + # 1 = hexadecimal number + # 2 = non hex integer portion, possibly with leading 0 (octal) + # 3 = floating point part, starts with ".", decimals and optional exponent + # + # Thus, a hex number has group 1 value, an octal value has group 2 (if it starts with 0), and no group 3 + # and a floating point value has group 2 and group 3. + # + NUMERIC = %r{^\s*(?:(0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))\s*$} + + # ILLEGAL_P3_1_HOSTNAME matches if a hostname contains illegal characters. + # This check does not prevent pathological names like 'a....b', '.....', "---". etc. + ILLEGAL_HOSTNAME_CHARS = %r{[^-\w.]} + + # NAME matches a name the same way as the lexer. + # This name includes hyphen, which may be illegal in variables, and names in general. + NAME = %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} + + # CLASSREF_EXT matches a class reference the same way as the lexer - i.e. the external source form + # where each part must start with a capital letter A-Z. + # This name includes hyphen, which may be illegal in some cases. + # + CLASSREF_EXT = %r{((::){0,1}[A-Z][-\w]*)+} + + # CLASSREF matches a class reference the way it is represented internall in the + # model (i.e. in lower case). + # This name includes hyphen, which may be illegal in some cases. + # + CLASSREF = %r{((::){0,1}[a-z][-\w]*)+} + +end diff --git a/lib/puppet/pops/utils.rb b/lib/puppet/pops/utils.rb new file mode 100644 index 000000000..104a26951 --- /dev/null +++ b/lib/puppet/pops/utils.rb @@ -0,0 +1,104 @@ +# Provides utility methods +module Puppet::Pops::Utils + # Can the given o be converted to numeric? (or is numeric already) + # Accepts a leading '::' + # Returns a boolean if the value is numeric + # If testing if value can be converted it is more efficient to call {#to_n} or {#to_n_with_radix} directly + # and check if value is nil. + def self.is_numeric?(o) + case o + when Numeric, Integer, Fixnum, Float + !!o + else + !!Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o.to_s)) + end + end + + # To LiteralNumber with radix, or nil if not a number. + # If the value is already a number it is returned verbatim with a radix of 10. + # @param o [String, Number] a string containing a number in octal, hex, integer (decimal) or floating point form + # @return [Array<Number, Integer>, nil] array with converted number and radix, or nil if not possible to convert + # @api public + # + def self.to_n_with_radix o + begin + case o + when String + match = Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o)) + if !match + nil + elsif match[3].to_s.length > 0 + # Use default radix (default is decimal == 10) for floats + [Float(match[0]), 10] + else + # Set radix (default is decimal == 10) + radix = 10 + if match[1].to_s.length > 0 + radix = 16 + elsif match[2].to_s.length > 0 && match[2][0] == '0' + radix = 8 + end + [Integer(match[0], radix), radix] + end + when Numeric, Fixnum, Integer, Float + # Impossible to calculate radix, assume decimal + [o, 10] + else + nil + end + rescue ArgumentError + nil + end + end + + # To Numeric (or already numeric) + # Returns nil if value is not numeric, else an Integer or Float + # A leading '::' is accepted (and ignored) + # + def self.to_n o + begin + case o + when String + match = Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o)) + if !match + nil + elsif match[3].to_s.length > 0 + Float(match[0]) + else + Integer(match[0]) + end + when Numeric, Fixnum, Integer, Float + o + else + nil + end + rescue ArgumentError + nil + end + end + + # is the name absolute (i.e. starts with ::) + def self.is_absolute? name + name.start_with? "::" + end + + def self.name_to_segments name + name.split("::") + end + + def self.relativize_name name + is_absolute?(name) ? name[2..-1] : name + end + + # Finds an adapter for o or for one of its containers, or nil, if none of the containers + # was adapted with the given adapter. + # This method can only be used with objects that respond to `:eContainer`. + # with true, and Adaptable#adapters. + # + def self.find_adapter(o, adapter) + return nil unless o + a = adapter.get(o) + return a if a + return find_adapter(o.eContainer, adapter) + end +end diff --git a/lib/puppet/pops/validation.rb b/lib/puppet/pops/validation.rb new file mode 100644 index 000000000..5e9aa2b19 --- /dev/null +++ b/lib/puppet/pops/validation.rb @@ -0,0 +1,297 @@ +# A module with base functionality for validation of a model. +# +# * 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 + # 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)`. + # + class SeverityProducer + # Creates a new instance where all issues are diagnosed as :error unless overridden. + # + def initialize + # If diagnose is not set, the default is returned by the block + @severities = Hash.new :error + end + + # Returns the severity of the given issue. + # @returns [Symbol] severity level :error, :warning, or :ignore + # + def severity issue + assert_issue(issue) + @severities[issue] + end + + 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). + # + def []= issue, level + assert_issue(issue) + assert_severity(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. + # @returns [Boolean] this implementation returns true for errors and warnings + # + def should_report? issue + diagnose = self[issue] + diagnose == :error || diagnose == :warning || diagnose == :deprecation + end + + 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 + + def assert_severity level + raise Puppet::DevError.new("Illegal severity level: #{option}") unless [:ignore, :warning, :error, :deprecation].include? 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. + arguments[:detail] ||= '' + + origin_adapter = Puppet::Pops::Utils.find_adapter(semantic, Puppet::Pops::Adapters::OriginAdapter) + file = origin_adapter ? origin_adapter.origin : nil + source_pos = Puppet::Pops::Utils.find_adapter(semantic, Puppet::Pops::Adapters::SourcePosAdapter) + severity = @severity_producer.severity(issue) + @acceptor.accept(Diagnostic.new(severity, issue, file, source_pos, arguments)) + 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 + @exception = exception + 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 + line = diagnostic.source_pos.line + pos = diagnostic.source_pos.pos + 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 + line = diagnostic.source_pos.line + pos = diagnostic.source_pos.pos + 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 diagnstic 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 != :ignored} + 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 to the set of diagnostics + def accept(diagnostic) + self.send(diagnostic.severity, diagnostic) + end + + private + + def ignore diagnostic + @diagnostics << diagnostic + end + + def error diagnostic + @diagnostics << diagnostic + @error_count += 1 + end + + def warning diagnostic + @diagnostics << diagnostic + @warning_count += 1 + end + + def deprecation diagnostic + warning diagnostic + end + end +end diff --git a/lib/puppet/pops/validation/checker3_1.rb b/lib/puppet/pops/validation/checker3_1.rb new file mode 100644 index 000000000..69df726e1 --- /dev/null +++ b/lib/puppet/pops/validation/checker3_1.rb @@ -0,0 +1,551 @@ +# A Validator validates a model. +# +# Validation is performed on each model element in isolation. Each method should validate the model element's state +# but not validate its referenced/contained elements except to check their validity in their respective role. +# The intent is to drive the validation with a tree iterator that visits all elements in a model. +# +# +# TODO: Add validation of multiplicities - this is a general validation that can be checked for all +# Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check). +# This is however mostly valuable when validating model to model transformations, and is therefore T.B.D +# +class Puppet::Pops::Validation::Checker3_1 + Issues = Puppet::Pops::Issues + Model = Puppet::Pops::Model + + attr_reader :acceptor + # Initializes the validator with a diagnostics producer. This object must respond to + # `:will_accept?` and `:accept`. + # + def initialize(diagnostics_producer) + @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0) + @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0) + @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 1) + @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1) + @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0) + @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1) + @@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 1, 1) + + @acceptor = diagnostics_producer + end + + # Validates the entire model by visiting each model element and calling `check`. + # The result is collected (or acted on immediately) by the configured diagnostic provider/acceptor + # given when creating this Checker. + # + def validate(model) + # tree iterate the model, and call check for each element + check(model) + model.eAllContents.each {|m| check(m) } + end + + # Performs regular validity check + def check(o) + @@check_visitor.visit_this(self, o) + end + + # Performs check if this is a vaid hostname expression + def hostname(o, semantic) + @@hostname_visitor.visit_this(self, o, semantic) + end + + # Performs check if this is valid as a query + def query(o) + @@query_visitor.visit_this(self, o) + end + + # Performs check if this is valid as a relationship side + def relation(o, container) + @@relation_visitor.visit_this(self, o, container) + end + + # Performs check if this is valid as a rvalue + def rvalue(o) + @@rvalue_visitor.visit_this(self, o) + end + + # Performs check if this is valid as a container of a definition (class, define, node) + def top(o, definition) + @@top_visitor.visit_this(self, o, definition) + end + + # Checks the LHS of an assignment (is it assignable?). + # If args[0] is true, assignment via index is checked. + # + def assign(o, *args) + @@assignment_visitor.visit_this(self, o, *args) + end + + #---ASSIGNMENT CHECKS + + def assign_VariableExpression(o, *args) + varname_string = varname_to_s(o.expr) + if varname_string =~ /^[0-9]+$/ + acceptor.accept(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o, :varname => 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, *args) + # 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, *args) + # 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(args[0] ? 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) + # Check multiplicity of keys + case o.left_expr + when Model::QualifiedName + # allows many keys, but the name should really be a QualifiedReference + acceptor.accept(Issues::DEPRECATED_NAME_AS_TYPE, o, :name => o.value) + when Model::QualifiedReference + # ok, allows many - this is a resource reference + + else + # i.e. for any other expression that may produce an array or hash + if o.keys.size > 1 + acceptor.accept(Issues::UNSUPPORTED_RANGE, o, :count => o.keys.size) + end + if o.keys.size < 1 + acceptor.accept(Issues::MISSING_INDEX, o) + end + end + end + + def check_AssignmentExpression(o) + assign(o.left_expr) + rvalue(o.right_expr) + 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_BinaryExpression(o) + rvalue(o.left_expr) + rvalue(o.right_expr) + end + + def check_CallNamedFunctionExpression(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_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) + # There should only be one LiteralDefault case option value + # TODO: Implement this check + 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 + + # for 'class' and 'define' + def check_NamedDefinition(o) + top(o.eContainer, o) + if (acceptor.will_accept? Issues::NAME_WITH_HYPHEN) && o.name.include?('-') + acceptor.accept(Issues::NAME_WITH_HYPHEN, o, {:name => o.name}) + end + end + + def check_ImportExpression(o) + o.files.each do |f| + unless f.is_a? Model::LiteralString + acceptor.accept(Issues::ILLEGAL_EXPRESSION, f, :feature => 'file name', :container => o) + end + end + end + + def check_InstanceReference(o) + # TODO: Original warning is : + # Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") + # This model element is not used in the egrammar. + # Either implement checks or deprecate the use of InstanceReference (the same is acheived by + # transformation of AccessExpression when used where an Instance/Resource reference is allowed. + # + end + + # Restrictions on hash key are because of the strange key comparisons/and merge rules in the AST evaluation + # (Even the allowed ones are handled in a strange way). + # + def transform_KeyedEntry(o) + case o.key + when Model::QualifiedName + when Model::LiteralString + when Model::LiteralNumber + when Model::ConcatenatedString + else + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer) + end + end + + # A Lambda is a Definition, but it may appear in other scopes that top scope (Which check_Definition asserts). + # + def check_LambdaExpression(o) + end + + def check_NodeDefinition(o) + # Check that hostnames are valid hostnames (or regular expressons) + hostname(o.host_matches, o) + top(o.eContainer, o) + end + + # Asserts that value is a valid QualifiedName. No additional checking is made, objects that use + # a QualifiedName as a name should check the validity - this since a QualifiedName is used as a BARE WORD + # and then additional chars may be valid (like a hyphen). + # + def check_QualifiedName(o) + # Is this a valid qualified name? + if o.value !~ Puppet::Pops::Patterns::NAME + acceptor.accept(Issues::ILLEGAL_NAME, o, {:name=>o.value}) + end + 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}) + elsif (acceptor.will_accept? Issues::NAME_WITH_HYPHEN) && o.value.include?('-') + acceptor.accept(Issues::NAME_WITH_HYPHEN, o, {:name => o.value}) + end + end + + def check_QueryExpression(o) + rvalue(o.expr) if o.expr # is optional + end + + def relation_Object(o, rel_expr) + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature => o.eContainingFeature, :container => rel_expr}) + end + + def relation_AccessExpression(o, rel_expr); end + + def relation_CollectExpression(o, rel_expr); end + + def relation_VariableExpression(o, rel_expr); end + + def relation_LiteralString(o, rel_expr); end + + def relation_ConcatenatedStringExpression(o, rel_expr); end + + def relation_SelectorExpression(o, rel_expr); end + + def relation_CaseExpression(o, rel_expr); end + + def relation_ResourceExpression(o, rel_expr); end + + def relation_RelationshipExpression(o, rel_expr); end + + def check_Parameter(o) + if o.name =~ /^[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, o) + relation(o.right_expr, o) + end + + def check_ResourceExpression(o) + # A resource expression must have a lower case NAME as its type e.g. 'file { ... }' + unless o.type_name.is_a? Model::QualifiedName + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_name, :feature => 'resource type', :container => o) + end + + # This is a runtime check - the model is valid, but will have runtime issues when evaluated + # and storeconfigs is not set. + if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.exported + acceptor.accept(Issues::RT_NO_STORECONFIGS_EXPORT, o) + end + end + + def check_ResourceDefaultsExpression(o) + if o.form && o.form != :regular + acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) + end + end + + # Transformation of SelectorExpression is limited to certain types of expressions. + # This is probably due to constraints in the old grammar rather than any real concerns. + def select_SelectorExpression(o) + case o.left_expr + when Model::CallNamedFunctionExpression + when Model::AccessExpression + when Model::VariableExpression + when Model::ConcatenatedString + else + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.left_expr, :feature => 'left operand', :container => o) + end + end + + def check_UnaryExpression(o) + rvalue(o.expr) + end + + def check_UnlessExpression(o) + # TODO: Unless may not have an elsif + # TODO: 3.x unless may not have an else + end + + def check_VariableExpression(o) + # The expression must be a qualified name + if !o.expr.is_a? Model::QualifiedName + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o) + else + # Note, that if it later becomes illegal with hyphen in any name, this special check + # can be skipped in favor of the check in QualifiedName, which is now not done if contained in + # a VariableExpression + name = o.expr.value + if (acceptor.will_accept? Issues::VAR_WITH_HYPHEN) && name.include?('-') + acceptor.accept(Issues::VAR_WITH_HYPHEN, o, {:name => name}) + end + end + end + + #--- HOSTNAME CHECKS + + # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName + def hostname_Array(o, semantic) + o.each {|x| hostname x, semantic } + end + + def hostname_String(o, semantic) + # 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) + hostname_String(o.value.to_s, o) + end + + def hostname_ConcatenatedString(o, semantic) + # 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]) + end + end + + def hostname_QualifiedName(o, semantic) + hostname_String(o.value.to_s, o) + end + + def hostname_QualifiedReference(o, semantic) + hostname_String(o.value.to_s, o) + end + + def hostname_LiteralNumber(o, semantic) + # always ok + end + + def hostname_LiteralDefault(o, semantic) + # always ok + end + + def hostname_LiteralRegularExpression(o, semantic) + # always ok + end + + def hostname_Object(o, semantic) + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>'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_ImportExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_BlockExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_CaseExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_IfExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_UnlessExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_ResourceExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_ResourceDefaultsExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end + + def rvalue_ResourceOverrideExpression(o); acceptor.accept(Issues::NOT_RVALUE, 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 + + # 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 + + #--- 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/pops/validation/validator_factory_3_1.rb b/lib/puppet/pops/validation/validator_factory_3_1.rb new file mode 100644 index 000000000..ee5ca3e8d --- /dev/null +++ b/lib/puppet/pops/validation/validator_factory_3_1.rb @@ -0,0 +1,41 @@ +# Configures validation suitable for 3.1 + iteration +# +class Puppet::Pops::Validation::ValidatorFactory_3_1 + Issues = Puppet::Pops::Issues + + # Produces a validator with the given acceptor as the recipient of produced diagnostics. + # + def validator acceptor + checker(diagnostic_producer(acceptor)) + end + + # Produces the diagnostics producer to use given an acceptor as the recipient of produced diagnostics + # + def diagnostic_producer acceptor + Puppet::Pops::Validation::DiagnosticProducer.new(acceptor, severity_producer(), label_provider()) + end + + # Produces the checker to use + def checker diagnostic_producer + Puppet::Pops::Validation::Checker3_1.new(diagnostic_producer) + end + + # Produces the label provider to use + def label_provider + Puppet::Pops::Model::ModelLabelProvider.new() + end + + # Produces the severity producer to use + def severity_producer + p = Puppet::Pops::Validation::SeverityProducer.new + + # Configure each issue that should **not** be an error + # + p[Issues::RT_NO_STORECONFIGS_EXPORT] = :warning + p[Issues::RT_NO_STORECONFIGS] = :warning + p[Issues::NAME_WITH_HYPHEN] = :deprecation + p[Issues::DEPRECATED_NAME_AS_TYPE] = :deprecation + + p + end +end diff --git a/lib/puppet/pops/visitable.rb b/lib/puppet/pops/visitable.rb new file mode 100644 index 000000000..2f72e37a1 --- /dev/null +++ b/lib/puppet/pops/visitable.rb @@ -0,0 +1,6 @@ +# Visitable is a mix-in module that makes a class visitable by a Visitor +module Puppet::Pops::Visitable + def accept(visitor, *arguments) + visitor.visit(self, *arguments) + end +end diff --git a/lib/puppet/pops/visitor.rb b/lib/puppet/pops/visitor.rb new file mode 100644 index 000000000..d5526ac8b --- /dev/null +++ b/lib/puppet/pops/visitor.rb @@ -0,0 +1,50 @@ +# A Visitor performs delegation to a given receiver based on the configuration of the Visitor. +# A new visitor is created with a given receiver, a method prefix, min, and max argument counts. +# e.g. +# vistor = Visitor.new(self, "visit_from", 1, 1) +# will make the visitor call "self.visit_from_CLASS(x)" where CLASS is resolved to the given +# objects class, or one of is ancestors, the first class for which there is an implementation of +# a method will be selected. +# +# Raises RuntimeError if there are too few or too many arguments, or if the receiver is not +# configured to handle a given visiting object. +# +class Puppet::Pops::Visitor + attr_reader :receiver, :message, :min_args, :max_args, :cache + def initialize(receiver, message, min_args=0, max_args=nil) + raise ArgumentError.new("min_args must be >= 0") if min_args < 0 + raise ArgumentError.new("max_args must be >= min_args or nil") if max_args && max_args < min_args + + @receiver = receiver + @message = message + @min_args = min_args + @max_args = max_args + @cache = Hash.new + end + + # Visit the configured receiver + def visit(thing, *args) + visit_this(@receiver, thing, *args) + end + + # Visit an explicit receiver + def visit_this(receiver, thing, *args) + raise "Visitor Error: Too few arguments passed. min = #{@min_args}" unless args.length >= @min_args + if @max_args + raise "Visitor Error: Too many arguments passed. max = #{@max_args}" unless args.length <= @max_args + end + if method_name = @cache[thing.class] + return receiver.send(method_name, thing, *args) + else + thing.class.ancestors().each do |ancestor| + method_name = :"#{@message}_#{ancestor.name.split("::").last}" + # DEBUG OUTPUT + # puts "Visitor checking: #{receiver.class}.#{method_name}, responds to: #{@receiver.respond_to? method_name}" + next unless receiver.respond_to? method_name + @cache[thing.class] = method_name + return receiver.send(method_name, thing, *args) + end + end + raise "Visitor Error: the configured receiver (#{receiver.class}) can't handle instance of: #{thing.class}" + end +end diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb index 083d68a9d..44a0b611d 100644 --- a/lib/puppet/property.rb +++ b/lib/puppet/property.rb @@ -18,11 +18,11 @@ require 'puppet/parameter' # value _(should)_ is read and written with the methods {#value} and {#value=} which delegate to # {#should} and {#should=}, i.e. when a property is used like any other parameter, it is the _should_ value # that is operated on. -# +# # All resource type properties in the puppet system are derived from this class. # # The intention is that new parameters are created by using the DSL method {Puppet::Type.newproperty}. -# +# # @abstract # @note Properties of Types are expressed using subclasses of this class. Such a class describes one # named property of a particular Type (as opposed to describing a type of property in general). This @@ -35,12 +35,12 @@ require 'puppet/parameter' # # @todo Describe meta-parameter shadowing. This concept can not be understood by just looking at the descriptions # of the methods involved. -# +# # @see Puppet::Type # @see Puppet::Parameter # # @api public -# +# class Puppet::Property < Puppet::Parameter require 'puppet/property/ensure' @@ -52,9 +52,9 @@ class Puppet::Property < Puppet::Parameter # The noop mode for this property. # By setting a property's noop mode to `true`, any management of this property is inhibited. Calculation - # and reporting still takes place, but if a change of the underlying managed entity's state + # and reporting still takes place, but if a change of the underlying managed entity's state # should take place it will not be carried out. This noop - # setting overrides the overall `Puppet[:noop]` mode as well as the noop mode in the _associated resource_ + # setting overrides the overall `Puppet[:noop]` mode as well as the noop mode in the _associated resource_ # attr_writer :noop @@ -63,21 +63,21 @@ class Puppet::Property < Puppet::Parameter # reads or writes this attribute. # ??? Probably Unused attr_accessor :unmanaged - + # @return [Symbol] The name of the property as given when the property was created. - # + # attr_reader :name # @!attribute [rw] array_matching # @comment note that $#46; is a period - char code require to not terminate sentence. # The `is` vs. `should` array matching mode; `:first`, or `:all`. - # + # # @comment there are two blank chars after the symbols to cause a break - do not remove these. - # * `:first` + # * `:first` # This is primarily used for single value properties. When matched against an array of values # a match is true if the `is` value matches any of the values in the `should` array. When the `is` value # is also an array, the matching is performed against the entire array as the `is` value. - # * `:all` + # * `:all` # : This is primarily used for multi-valued properties. When matched against an array of # `should` values, the size of `is` and `should` must be the same, and all values in `is` must match # a value in `should`. @@ -104,7 +104,7 @@ class Puppet::Property < Puppet::Parameter end # Looks up a value's name among valid values, to enable option lookup with result as a key. - # @param name [Object] the parameter value to match against valid values (names). + # @param name [Object] the parameter value to match against valid values (names). # @return {Symbol, Regexp} a value matching predicate # @api private # @@ -142,6 +142,10 @@ class Puppet::Property < Puppet::Parameter # was possible to specify a value of `:before` or `:after` for the purpose of calling # both the block and the provider. Use of these deprecated options will now raise an exception later # in the process when the _is_ value is set (see #set). + # @option options [Symbol] :invalidate_refreshes Indicates a change on this property should invalidate and + # remove any scheduled refreshes (from notify or subscribe) targeted at the same resource. For example, if + # a change in this property takes into account any changes that a scheduled refresh would have performed, + # then the scheduled refresh would be deleted. # @option options [Object] any Any other option is treated as a call to a setter having the given # option name (e.g. `:required_features` calls `required_features=` with the option's value as an # argument). @@ -160,7 +164,7 @@ class Puppet::Property < Puppet::Parameter value end - # Calls the provider setter method for this property with the given value as argument. + # Calls the provider setter method for this property with the given value as argument. # @return [Object] what the provider returns when calling a setter for this property's name # @raise [Puppet::Error] when the provider can not handle this property. # @see #set @@ -254,18 +258,23 @@ class Puppet::Property < Puppet::Parameter # Produces an event describing a change of this property. # In addition to the event attributes set by the resource type, this method adds: - # + # # * `:name` - the event_name # * `:desired_value` - a.k.a _should_ or _wanted value_ # * `:property` - reference to this property # * `:source_description` - the _path_ (?? See todo) + # * `:invalidate_refreshes` - if scheduled refreshes should be invalidated # # @todo What is the intent of this method? What is the meaning of the :source_description passed in the # options to the created event? - # @return [Puppet::Transaction::Event] the created event + # @return [Puppet::Transaction::Event] the created event # @see Puppet::Type#event def event - resource.event :name => event_name, :desired_value => should, :property => self, :source_description => path + attrs = { :name => event_name, :desired_value => should, :property => self, :source_description => path } + if should and value = self.class.value_collection.match?(should) + attrs[:invalidate_refreshes] = true if value.invalidate_refreshes + end + resource.event attrs end # @todo What is this? @@ -274,9 +283,9 @@ class Puppet::Property < Puppet::Parameter # Initializes a Property the same way as a Parameter and handles the special case when a property is shadowing a meta-parameter. # @todo There is some special initialization when a property is not a metaparameter but - # Puppet::Type.metaparamclass(for this class's name) is not nil - if that is the case a + # Puppet::Type.metaparamclass(for this class's name) is not nil - if that is the case a # setup_shadow is performed for that class. - # + # # @param hash [Hash] options passed to the super initializer {Puppet::Parameter#initialize} # @note New properties of a type should be created via the DSL method {Puppet::Type.newproperty}. # @see Puppet::Parameter#initialize description of Parameter initialize options. @@ -372,11 +381,11 @@ class Puppet::Property < Puppet::Parameter # Checks if the given current and desired values are equal. # This default implementation performs this check in a backwards compatible way where - # the equality of the two values is checked, and then the equality of current with desired + # the equality of the two values is checked, and then the equality of current with desired # converted to a string. - # + # # A derived implementation may override this method to perform a property specific equality check. - # + # # The intent of this method is to provide an equality check suitable for checking if the property # value is in sync or not. It is typically called from {#insync?}. # @@ -438,7 +447,7 @@ class Puppet::Property < Puppet::Parameter # the _associated resource_ and finally in Puppet[:noop]. # @todo This logic is different than Parameter#noop in that the resource noop mode overrides # the property's mode - in parameter it is the other way around. Bug or feature? - # + # def noop # This is only here to make testing easier. if @resource.respond_to?(:noop?) @@ -457,7 +466,7 @@ class Puppet::Property < Puppet::Parameter # same name as this property (i.e. if the property name is 'gid', a call to the # 'provider.gid' is expected to return the current value. # @return [Object] what the provider returns as the current value of the property - # + # def retrieve provider.send(self.class.name) end @@ -517,10 +526,10 @@ class Puppet::Property < Puppet::Parameter # If the _array matching mode_ {#match_all?} is true, an array of the wanted values in unmunged format # is returned, else the first value in the array of wanted values in unmunged format is returned. # @return [Array<Object>, Object, nil] Array of values if {#match_all?} else a single value, or nil if there are no - # wanted values. + # wanted values. # @raise [Puppet::DevError] if the wanted value is non nil and not an array # - # @note This method will potentially return different values than the original values as they are + # @note This method will potentially return different values than the original values as they are # converted via munging/unmunging. If the original values are wanted, call {#shouldorig}. # # @see #shouldorig @@ -565,8 +574,8 @@ class Puppet::Property < Puppet::Parameter # Synchronizes the current value _(is)_ and the wanted value _(should)_ by calling {#set}. # @raise [Puppet::DevError] if {#should} is nil - # @todo The implementation of this method is somewhat inefficient as it computes the should - # array twice. + # @todo The implementation of this method is somewhat inefficient as it computes the should + # array twice. def sync devfail "Got a nil value for should" unless should set(should) @@ -587,7 +596,7 @@ class Puppet::Property < Puppet::Parameter # @raise [ArgumentError] if a required feature is not present # @return [void] # @api private - # + # def validate_features_per_value(value) if features = self.class.value_option(self.class.value_name(value), :required_features) features = Array(features) diff --git a/lib/puppet/property/ensure.rb b/lib/puppet/property/ensure.rb index 3c9f98d85..22ba211e3 100644 --- a/lib/puppet/property/ensure.rb +++ b/lib/puppet/property/ensure.rb @@ -67,10 +67,10 @@ class Puppet::Property::Ensure < Puppet::Property # The existence of the resource is checked by first consulting the provider (if it responds to # `:exists`), and secondly the resource. A a value of `:present` or `:absent` is returned # depending on if the managed entity exists or not. - # + # # @return [Symbol] a value of `:present` or `:absent` depending on if it exists or not # @raise [Puppet::DevError] if neither the provider nor the resource responds to `:exists` - # + # def retrieve # XXX This is a problem -- whether the object exists or not often # depends on the results of other properties, yet we're the first property diff --git a/lib/puppet/property/ordered_list.rb b/lib/puppet/property/ordered_list.rb index 65cd16953..28c56510e 100644 --- a/lib/puppet/property/ordered_list.rb +++ b/lib/puppet/property/ordered_list.rb @@ -6,7 +6,7 @@ module Puppet # The maintained order is the order defined by the 'current' set of values (i.e. the # original order is not disrupted). Any additions are added after the current values # in their given order). - # + # # For an unordered list see {Puppet::Property::List}. # class OrderedList < List diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index 5edebb53e..43e14eeec 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -14,7 +14,7 @@ # lifecycle (creates, destroys), or alters some value reflected backed by a property). # * **Flush** - is a hook that is called once per resource when everything has been applied. The intent is # that an implementation may defer modification of the current state typically done in property setters -# and instead record information that allows flush to perform the changes more efficiently. +# and instead record information that allows flush to perform the changes more efficiently. # * **Execution Methods** - The execution methods provides access to execution of arbitrary commands. # As a convenience execution methods are available on both the instance and the class of a provider since a # lot of provider logic switch between these contexts fairly freely. @@ -30,8 +30,7 @@ # # @note Class level methods are only called once to configure the provider (when the type is created), and not # for each resource the provider is operating on. -# The instance methods are however called for each resource. -# +# The instance methods are however called for each resource. # # @api public # @@ -52,11 +51,11 @@ class Puppet::Provider # Include the util module so we have access to things like 'which' include Puppet::Util, Puppet::Util::Docs include Puppet::Util::Logging - + # @return [String] The name of the provider attr_accessor :name - # + # # @todo Original = _"The source parameter exists so that providers using the same # source can specify this, so reading doesn't attempt to read the # same package multiple times."_ This seems to be a package type specific attribute. Is this really @@ -72,19 +71,19 @@ class Puppet::Provider # @api private # @deprecated This attribute is available for backwards compatibility reasons. attr_reader :model - + # @todo What is this type? A reference to a Puppet::Type ? # @return [Puppet::Type] the resource type (that this provider is ... WHAT?) # attr_accessor :resource_type - + # @!attribute [r] doc # The (full) documentation for this provider class. The documentation for the provider class itself # should be set with the DSL method {desc=}. Setting the documentation with with {doc=} has the same effect # as setting it with {desc=} (only the class documentation part is set). In essence this means that # there is no getter for the class documentation part (since the getter returns the full # documentation when there are additional contributors). - # + # # @return [String] Returns the full documentation for the provider. # @see Puppet::Utils::Docs # @comment This is puzzling ... a write only doc attribute??? The generated setter never seems to be @@ -96,17 +95,15 @@ class Puppet::Provider # {doc} attribute). # # @dsl type - # # attr_writer :doc - end # @todo original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_, why is it # both here (instance) and at class level? Is this a different model? # @return [???] model is WHAT? attr_reader :model - + # @return [???] This resource is what? Is an instance of a provider attached to one particular Puppet::Resource? # attr_accessor :resource @@ -168,11 +165,11 @@ class Puppet::Provider # Confines this provider to be suitable only on hosts where the given commands are present. # Also see {Puppet::Provider::Confiner#confine} for other types of confinement of a provider by use of other types of # predicates. - # + # # @note It is preferred if the commands are not entered with absolute paths as this allows puppet # to search for them using the PATH variable. # - # @param command_specs [Hash{String => String}] Map of name to command that the provider will + # @param command_specs [Hash{String => String}] Map of name to command that the provider will # be executing on the system. Each command is specified with a name and the path of the executable. # @return [void] # @see optional_commands @@ -188,7 +185,7 @@ class Puppet::Provider # is lazy (when a resource is evaluated) and the absence of commands # that will be present after other resources have been applied no longer needs to be specified as # optional. - # @param [Hash{String => String}] command_specs Named commands that the provider will + # @param [Hash{String => String}] command_specs Named commands that the provider will # be executing on the system. Each command is specified with a name and the path of the executable. # (@see #has_command) # @see commands @@ -202,8 +199,8 @@ class Puppet::Provider # Creates a convenience method for invocation of a command. # - # This generates a Provider method that allows easy execution of the command. The generated - # method may take arguments that will be passed through to the executable as the command line arguments + # This generates a Provider method that allows easy execution of the command. The generated + # method may take arguments that will be passed through to the executable as the command line arguments # when it is invoked. # # @example Use it like this: @@ -223,7 +220,7 @@ class Puppet::Provider # @yield [ ] A block that configures the command (see {Puppet::Provider::Command}) # @comment a yield [ ] produces {|| ...} in the signature, do not remove the space. # @note the name ´has_command´ looks odd in an API context, but makes more sense when seen in the internal - # DSL context where a Provider is declaratively defined. + # DSL context where a Provider is declaratively defined. # def self.has_command(name, path, &block) name = name.intern @@ -325,9 +322,9 @@ class Puppet::Provider # # The _ancestors_ is the Ruby Module::ancestors method and the number of classes returned is used # to boost the score. The intent is that if two providers are equal, but one is more "derived" than the other - # (i.e. includes more classes), it should win because it is more specific). + # (i.e. includes more classes), it should win because it is more specific). # @note Because of how this value is - # calculated there could be surprising side effects if a provider included an excessive amount of classes. + # calculated there could be surprising side effects if a provider included an excessive amount of classes. # def self.specificity # This strange piece of logic attempts to figure out how many parent providers there @@ -356,9 +353,9 @@ class Puppet::Provider # # An implementation of this method should only cache the values of properties # if they are discovered as part of the process for finding existing resources. - # Resource properties that require additional commands (than those used to determine existence/identity) + # Resource properties that require additional commands (than those used to determine existence/identity) # should be implemented in their respective getter method. (This is important from a performance perspective; - # it may be expensive to compute, as well as wasteful as all discovered resources may perhaps not be managed). + # it may be expensive to compute, as well as wasteful as all discovered resources may perhaps not be managed). # # An implementation may return an empty list (naturally with the effect that it is not possible to query # for manageable entities). @@ -370,7 +367,7 @@ class Puppet::Provider # @return [Array<Puppet::Provider>] a list of providers referencing the system entities # @abstract this method must be implemented by a subclass and this super method should never be called as it raises an exception. # @raise [Puppet::DevError] Error indicating that the method should have been implemented by subclass. - # @see prefetch + # @see prefetch def self.instances raise Puppet::DevError, "Provider #{self.name} has not defined the 'instances' class method" end @@ -406,7 +403,7 @@ class Puppet::Provider # The generated methods may be overridden by more advanced implementations if something # else than a straight forward getter/setter pair of methods is required. # (i.e. define such overriding methods after this method has been called) - # + # # An implementor of a provider that makes use of `prefetch` and `flush` can use this method since it uses # the internal `@property_hash` variable to store values. An implementation would then update the system # state on a call to `flush` based on the current values in the `@property_hash`. @@ -447,7 +444,7 @@ class Puppet::Provider private_class_method :create_class_and_instance_method # @return [String] Returns the data source, which is the provider name if no other source has been set. - # @todo Unclear what "the source" is used for? + # @todo Unclear what "the source" is used for? def self.source @source ||= self.name end @@ -508,7 +505,7 @@ class Puppet::Provider end end - # Clears this provider instance to allow GC to clean up. + # Clears this provider instance to allow GC to clean up. def clear @resource = nil @model = nil @@ -532,8 +529,8 @@ class Puppet::Provider # it is remembered for further operations. If a hash is used it becomes the internal `@property_hash` # structure of the provider - this hash holds the current state property values of system entities # as they are being discovered by querying or other operations (typically getters). - # - # @todo The use of a hash as a parameter needs a better exaplanation; why is this done? What is the intent? + # + # @todo The use of a hash as a parameter needs a better exaplanation; why is this done? What is the intent? # @param resource [Puppet::Resource, Hash] optional resource or hash # def initialize(resource = nil) @@ -596,7 +593,7 @@ class Puppet::Provider # Otherwise, order by the providers class name. return self.class.name <=> other.class.name end - + # @comment Document prefetch here as it does not exist anywhere else (called from transaction if implemented) # @!method self.prefetch(resource_hash) # @abstract A subclass may implement this - it is not implemented in the Provider class @@ -605,13 +602,12 @@ class Puppet::Provider # fetched state (i.e. what is returned from the {instances} method). # @param resources_hash [Hash<{String => Puppet::Resource}>] map from name to resource of resources to prefetch # @return [void] - + # @comment Document flush here as it does not exist anywhere (called from transaction if implemented) # @!method flush() # @abstract A subclass may implement this - it is not implemented in the Provider class # This method may be implemented by a provider in order to flush properties that has not been individually # applied to the managed entity's current state. # @return [void] - end diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb index 3cc4b5170..dce452a0e 100755 --- a/lib/puppet/provider/aixobject.rb +++ b/lib/puppet/provider/aixobject.rb @@ -1,6 +1,6 @@ # # Common code for AIX providers. This class implements basic structure for -# AIX resources. +# AIX resources. # Author:: Hector Rivas Gandara <keymon@gmail.com> # class Puppet::Provider::AixObject < Puppet::Provider @@ -31,9 +31,9 @@ class Puppet::Provider::AixObject < Puppet::Provider # It is a list of hashes # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name - # :to Optional. Method name that adapts puppet property to aix command value. + # :to Optional. Method name that adapts puppet property to aix command value. # :from Optional. Method to adapt aix command line value to puppet property. Optional - class << self + class << self attr_accessor :attribute_mapping end @@ -49,7 +49,7 @@ class Puppet::Provider::AixObject < Puppet::Provider } end @attribute_mapping_to - end + end # Mapping from AIX attribute to Puppet property. def self.attribute_mapping_from @@ -64,7 +64,7 @@ class Puppet::Provider::AixObject < Puppet::Provider end @attribute_mapping_from end - + # This functions translates a key and value using the given mapping. # Mapping can be nil (no translation) or a hash with this format # {:key => new_key, :method => translate_method} @@ -72,7 +72,7 @@ class Puppet::Provider::AixObject < Puppet::Provider def translate_attr(key, value, mapping) return [key, value] unless mapping return nil unless mapping[key] - + if mapping[key][:method] new_value = method(mapping[key][:method]).call(value) else @@ -80,7 +80,7 @@ class Puppet::Provider::AixObject < Puppet::Provider end [mapping[key][:key], new_value] end - + # Loads an AIX attribute (key=value) and stores it in the given hash with # puppet semantics. It translates the pair using the given mapping. # @@ -94,13 +94,13 @@ class Puppet::Provider::AixObject < Puppet::Provider true elsif mapping[key][:method].nil? objectinfo[mapping[key][:key]] = value - elsif + elsif objectinfo[mapping[key][:key]] = method(mapping[key][:method]).call(value) end - + return objectinfo end - + # Gets the given command line argument for the given key and value, # using the given mapping to translate key and value. # All the objectinfo hash (@resource or @property_hash) is passed. @@ -109,7 +109,7 @@ class Puppet::Provider::AixObject < Puppet::Provider # and default behaviour is return the arguments as key=value pairs. # Subclasses must reimplement this if more complex operations/arguments # are needed - # + # def get_arguments(key, value, mapping, objectinfo) if mapping.nil? new_key = key @@ -121,7 +121,7 @@ class Puppet::Provider::AixObject < Puppet::Provider elsif mapping[key][:method].nil? new_key = mapping[key][:key] new_value = value - elsif + elsif new_key = mapping[key][:key] new_value = method(mapping[key][:method]).call(value) end @@ -130,18 +130,18 @@ class Puppet::Provider::AixObject < Puppet::Provider new_value = Array(new_value).join(',') if new_key - return [ "#{new_key}=#{new_value}" ] + return [ "#{new_key}=#{new_value}" ] else return [] end end - + # Convert the provider properties (hash) to AIX command arguments # (list of strings) # This function will translate each value/key and generate the argument using # the get_arguments function. def hash2args(hash, mapping=self.class.attribute_mapping_to) - return "" unless hash + return "" unless hash arg_list = [] hash.each {|key, val| arg_list += self.get_arguments(key, val, mapping, hash) @@ -151,8 +151,8 @@ class Puppet::Provider::AixObject < Puppet::Provider # Parse AIX command attributes from the output of an AIX command, that # which format is a list of space separated of key=value pairs: - # "uid=100 groups=a,b,c". - # It returns an hash. + # "uid=100 groups=a,b,c". + # It returns an hash. # # If a mapping is provided, the keys are translated as defined in the # mapping hash. And only values included in mapping will be added @@ -163,7 +163,7 @@ class Puppet::Provider::AixObject < Puppet::Provider attrs = [] if !str or (attrs = str.split()).empty? return nil - end + end attrs.each { |i| if i.include? "=" # Ignore if it does not include '=' @@ -174,7 +174,7 @@ class Puppet::Provider::AixObject < Puppet::Provider continue end key = key_str.to_sym - + properties = self.load_attribute(key, val, mapping, properties) end } @@ -195,16 +195,15 @@ class Puppet::Provider::AixObject < Puppet::Provider attrs = [] if !str or (attrs = str.split(':')).empty? return nil - end + end attrs.each { |val| key = key_list.shift.downcase.to_sym properties = self.load_attribute(key, val, mapping, properties) } properties.empty? ? nil : properties - end - + # Default parsing function for AIX commands. # It will choose the method depending of the first line. # For the colon separated list it will: @@ -213,7 +212,7 @@ class Puppet::Provider::AixObject < Puppet::Provider def parse_command_output(output, mapping=self.class.attribute_mapping_from) lines = output.split("\n") # if it begins with #something:... is a colon separated list. - if lines[0] =~ /^#.*:/ + if lines[0] =~ /^#.*:/ self.parse_colon_list(lines[1], lines[0][1..-1].split(':'), mapping) else self.parse_attr_list(lines[0], mapping) @@ -233,7 +232,7 @@ class Puppet::Provider::AixObject < Puppet::Provider @objectosinfo = self.parse_command_output(execute(self.lscmd), nil) rescue Puppet::ExecutionFailure => detail # Print error if needed. FIXME: Do not check the user here. - Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" + Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" end end @objectinfo @@ -252,17 +251,20 @@ class Puppet::Provider::AixObject < Puppet::Provider # List all elements of given type. It works for colon separated commands and # list commands. # It returns a list of names. - def list_all + def self.list_all names = [] begin - output = execute(self.lsallcmd()).split('\n') - (output.select{ |l| l != /^#/ }).each { |v| - name = v.split(/[ :]/) + output = execute([self.command(:list), 'ALL']) + + output = output.split("\n").select{ |line| line != /^#/ } + + output.each do |line| + name = line.split(/[ :]/)[0] names << name if not name.empty? - } + end rescue Puppet::ExecutionFailure => detail # Print error if needed - Puppet.debug "aix.list_all(): Could not get all resources of type #{@resource.class.name}: #{detail}" + Puppet.debug "aix.list_all(): Could not get all resources of type #{@resource.class.name}: #{detail}" end names end @@ -271,7 +273,7 @@ class Puppet::Provider::AixObject < Puppet::Provider #------------- # Provider API # ------------ - + # Clear out the cached values. def flush @property_hash.clear if @property_hash @@ -283,12 +285,12 @@ class Puppet::Provider::AixObject < Puppet::Provider !!getinfo(true) # !! => converts to bool end - # Return all existing instances + # Return all existing instances # The method for returning a list of provider instances. Note that it returns # providers, preferably with values already filled in, not resources. def self.instances objects=[] - self.list_all().each { |entry| + self.list_all.each { |entry| objects << new(:name => entry, :ensure => :present) } objects @@ -319,7 +321,7 @@ class Puppet::Provider::AixObject < Puppet::Provider rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{detail}" end - end + end # Delete this instance of the resource def delete @@ -347,13 +349,13 @@ class Puppet::Provider::AixObject < Puppet::Provider define_method(prop.to_s + "=") { |*vals| set(prop, *vals) } unless public_method_defined?(prop.to_s + "=") end end - + # Define the needed getters and setters as soon as we know the resource type def self.resource_type=(resource_type) super mk_resource_methods end - + # Retrieve a specific value by name. def get(param) (hash = getinfo(false)) ? hash[param] : nil @@ -362,15 +364,15 @@ class Puppet::Provider::AixObject < Puppet::Provider # Set a property. def set(param, value) @property_hash[param.intern] = value - + if getinfo().nil? - # This is weird... + # This is weird... raise Puppet::Error, "Trying to update parameter '#{param}' to '#{value}' for a resource that does not exists #{@resource.class.name} #{@resource.name}: #{detail}" end if value == getinfo()[param.to_sym] return end - + #self.class.validate(param, value) if cmd = modifycmd({param =>value}) begin @@ -379,15 +381,14 @@ class Puppet::Provider::AixObject < Puppet::Provider raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" end end - - # Refresh de info. + + # Refresh de info. hash = getinfo(true) end - + def initialize(resource) super @objectinfo = nil @objectosinfo = nil - end - + end end diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index bf73ee683..f8cd5aabc 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -171,7 +171,6 @@ Puppet::Type.type(:augeas).provide(:augeas) do aug.set("/augeas/load/Xfm/incl", resource[:incl]) restricted = true elsif glob_avail and opt_ctx - restricted = true # Optimize loading if the context is given, requires the glob function # from Augeas 0.8.2 or up ctx_path = resource[:context].sub(/^\/files(.*?)\/?$/, '\1/') diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/provider/confiner.rb index 63499e78c..a1a2fa593 100644 --- a/lib/puppet/provider/confiner.rb +++ b/lib/puppet/provider/confiner.rb @@ -36,7 +36,7 @@ module Puppet::Provider::Confiner # Checks whether this implementation is suitable for the current platform (or returns a summary # of all confines if short == false). # @return [Boolean. Hash] Returns whether the confines are all valid (if short == true), or a hash of all confines - # if short == false. + # if short == false. # @api public # def suitable?(short = true) diff --git a/lib/puppet/provider/cron/crontab.rb b/lib/puppet/provider/cron/crontab.rb index 2d8db14c6..91047af78 100755 --- a/lib/puppet/provider/cron/crontab.rb +++ b/lib/puppet/provider/cron/crontab.rb @@ -1,17 +1,6 @@ require 'puppet/provider/parsedfile' -tab = case Facter.value(:osfamily) - when "Solaris" - :suntab - when "AIX" - :aixtab - else - :crontab - end - - - -Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFile, :default_target => ENV["USER"] || "root", :filetype => tab) do +Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFile, :default_target => ENV["USER"] || "root") do commands :crontab => "crontab" text_line :comment, :match => %r{^\s*#}, :post_parse => proc { |record| @@ -22,58 +11,103 @@ Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFi text_line :environment, :match => %r{^\s*\w+=} - record_line :freebsd_special, :fields => %w{special command}, - :match => %r{^@(\w+)\s+(.+)$}, :pre_gen => proc { |record| - record[:special] = "@" + record[:special] - } + def self.filetype + tabname = case Facter.value(:osfamily) + when "Solaris" + :suntab + when "AIX" + :aixtab + else + :crontab + end + + Puppet::Util::FileType.filetype(tabname) + end - crontab = record_line :crontab, :fields => %w{minute hour monthday month weekday command}, - :match => %r{^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$}, - :optional => %w{minute hour weekday month monthday}, :absent => "*" + self::TIME_FIELDS = [:minute, :hour, :monthday, :month, :weekday] + + record_line :crontab, + :fields => %w{time command}, + :match => %r{^\s*(@\w+|\S+\s+\S+\s+\S+\s+\S+\s+\S+)\s+(.+)$}, + :absent => '*', + :block_eval => :instance do - class << crontab - def numeric_fields - fields - [:command] - end - # Do some post-processing of the parsed record. Basically just - # split the numeric fields on ','. def post_parse(record) - numeric_fields.each do |field| - if val = record[field] and val != :absent - record[field] = record[field].split(",") + time = record.delete(:time) + if match = /@(\S+)/.match(time) + # is there another way to access the constant? + Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.each { |f| record[f] = :absent } + record[:special] = match.captures[0] + elsif match = /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/.match(time) + record[:special] = :absent + Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.zip(match.captures).each do |field,value| + if value == self.absent + record[field] = :absent + else + record[field] = value.split(",") + end end + else + raise Puppet::Error, "Line got parsed as a crontab entry but cannot be handled. Please file a bug with the contents of your crontab" end + record end - # Join the fields back up based on ','. def pre_gen(record) - numeric_fields.each do |field| + if record[:special] and record[:special] != :absent + record[:special] = "@#{record[:special]}" + end + + Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.each do |field| if vals = record[field] and vals.is_a?(Array) record[field] = vals.join(",") end end + record end - - # Add name and environments as necessary. def to_line(record) str = "" str = "# Puppet Name: #{record[:name]}\n" if record[:name] - if record[:environment] and record[:environment] != :absent and record[:environment] != [:absent] - record[:environment].each do |env| - str += env + "\n" - end + if record[:environment] and record[:environment] != :absent + str += record[:environment].map {|line| "#{line}\n"}.join('') end - - if record[:special] - str += "@#{record[:special]} #{record[:command]}" + if record[:special] and record[:special] != :absent + fields = [:special, :command] else - str += join(record) + fields = Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS + [:command] end + str += record.values_at(*fields).map do |field| + if field.nil? or field == :absent + self.absent + else + field + end + end.join(self.joiner) str end end + # Look up a resource with a given name whose user matches a record target + # + # @api private + # + # @note This overrides the ParsedFile method for finding resources by name, + # so that only records for a given user are matched to resources of the + # same user so that orphaned records in other crontabs don't get falsely + # matched (#2251) + # + # @param [Hash<Symbol, Object>] record + # @param [Array<Puppet::Resource>] resources + # + # @return [Puppet::Resource, nil] The resource if found, else nil + def self.resource_for_record(record, resources) + resource = super + + if resource and record[:target] == resource[:user] + resource + end + end # Return the header placed at the top of each generated file, warning # users that modifying this file manually is probably a bad idea. @@ -84,50 +118,52 @@ Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFi # HEADER: not be deleted, as doing so could cause duplicate cron jobs.\n} end + # Regex for finding one vixie cron header. + def self.native_header_regex + /# DO NOT EDIT THIS FILE.*?Cron version.*?vixie.*?\n/m + end + + # If a vixie cron header is found, it should be dropped, cron will insert + # a new one in any case, so we need to avoid duplicates. + def self.drop_native_header + true + end + # See if we can match the record against an existing cron job. def self.match(record, resources) + # if the record is named, do not even bother (#19876) + return false if record[:name] resources.each do |name, resource| # Match the command first, since it's the most important one. - next unless record[:target] == resource.value(:target) + next unless record[:target] == resource[:target] next unless record[:command] == resource.value(:command) - # Then check the @special stuff - if record[:special] - next unless resource.value(:special) == record[:special] - end + # Now check the time fields + compare_fields = self::TIME_FIELDS + [:special] - # Then the normal fields. matched = true - record_type(record[:record_type]).fields.each do |field| - next if field == :command - next if field == :special - if record[field] and ! resource.value(field) - #Puppet.info "Cron is missing %s: %s and %s" % - # [field, record[field].inspect, resource.value(field).inspect] + compare_fields.each do |field| + # If the resource does not manage a property (say monthday) it should + # always match. If it is the other way around (e.g. resource defines + # a should value for :special but the record does not have it, we do + # not match + next unless resource[field] + unless record.include?(field) matched = false break end - if ! record[field] and resource.value(field) - #Puppet.info "Hash is missing %s: %s and %s" % - # [field, resource.value(field).inspect, record[field].inspect] - matched = false - break + if record_value = record[field] and resource_value = resource.value(field) + # The record translates '*' into absent in the post_parse hook and + # the resource type does exactly the opposite (alias :absent to *) + next if resource_value == '*' and record_value == :absent + next if resource_value == record_value end - - # Yay differing definitions of absent. - next if (record[field] == :absent and resource.value(field) == "*") - - # Everything should be in the form of arrays, not the normal text. - next if (record[field] == resource.value(field)) - #Puppet.info "Did not match %s: %s vs %s" % - # [field, resource.value(field).inspect, record[field].inspect] - matched = false + matched =false break end return resource if matched end - false end @@ -189,6 +225,10 @@ Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFi end def user=(user) + # we have to mark the target as modified first, to make sure that if + # we move a cronjob from userA to userB, userA's crontab will also + # be rewritten + mark_target_modified @property_hash[:user] = user @property_hash[:target] = user end diff --git a/lib/puppet/provider/group/groupadd.rb b/lib/puppet/provider/group/groupadd.rb index 16e564fc1..f345bdc5d 100644 --- a/lib/puppet/provider/group/groupadd.rb +++ b/lib/puppet/provider/group/groupadd.rb @@ -1,4 +1,5 @@ require 'puppet/provider/nameservice/objectadd' +require 'puppet/util/libuser' Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "Group management via `groupadd` and its ilk. The default for most platforms. @@ -13,17 +14,72 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe value.is_a? Integer end + optional_commands :localadd => "lgroupadd" + has_feature :libuser if Puppet.features.libuser? + + def exists? + return !!localgid if @resource.forcelocal? + super + end + + def gid + return localgid if @resource.forcelocal? + get(:gid) + end + + def findgroup(key, value) + group_file = "/etc/group" + group_keys = ['group_name', 'password', 'gid', 'user_list'] + index = group_keys.index(key) + File.open(group_file) do |f| + f.each_line do |line| + group = line.split(":") + if group[index] == value + f.close + return group + end + end + end + false + end + + def localgid + group = findgroup('group_name', resource[:name]) + return group[2] if group + false + end + + def check_allow_dup + # We have to manually check for duplicates when using libuser + # because by default duplicates are allowed. This check is + # to ensure consistent behaviour of the useradd provider when + # using both useradd and luseradd + if not @resource.allowdupe? and @resource.forcelocal? + if @resource.should(:gid) and findgroup('gid', @resource.should(:gid).to_s) + raise(Puppet::Error, "GID #{@resource.should(:gid).to_s} already exists, use allowdupe to force group creation") + end + elsif @resource.allowdupe? and not @resource.forcelocal? + return ["-o"] + end + [] + end + def addcmd - cmd = [command(:add)] + if @resource.forcelocal? + cmd = [command(:localadd)] + @custom_environment = Puppet::Util::Libuser.getenv + else + cmd = [command(:add)] + end + if gid = @resource.should(:gid) unless gid == :absent cmd << flag(:gid) << gid end end - cmd << "-o" if @resource.allowdupe? + cmd += check_allow_dup cmd << "-r" if @resource.system? and self.class.system_groups? cmd << @resource[:name] - cmd end end diff --git a/lib/puppet/provider/interface/cisco.rb b/lib/puppet/provider/interface/cisco.rb index 795a7f1ac..a0f82c7de 100644 --- a/lib/puppet/provider/interface/cisco.rb +++ b/lib/puppet/provider/interface/cisco.rb @@ -8,8 +8,8 @@ Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Cisco def self.lookup(device, name) interface = nil - device.command do |ng| - interface = device.interface(name) + device.command do |dev| + interface = dev.interface(name) end interface end @@ -19,8 +19,8 @@ Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Cisco end def flush - device.command do |device| - device.new_interface(name).update(former_properties, properties) + device.command do |dev| + dev.new_interface(name).update(former_properties, properties) end super end diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb index d22531fbd..d935d7d13 100755 --- a/lib/puppet/provider/mount/parsed.rb +++ b/lib/puppet/provider/mount/parsed.rb @@ -88,7 +88,7 @@ Puppet::Type.type(:mount).provide( def self.mountinstances # XXX: Will not work for mount points that have spaces in path (does fstab support this anyways?) - regex = case Facter.value(:osfamily) + regex = case Facter.value(:osfamily) when "Darwin" / on (?:\/private\/var\/automount)?(\S*)/ when "Solaris", "HP-UX" diff --git a/lib/puppet/provider/nameservice.rb b/lib/puppet/provider/nameservice.rb index 5df414680..edb098012 100644 --- a/lib/puppet/provider/nameservice.rb +++ b/lib/puppet/provider/nameservice.rb @@ -18,6 +18,7 @@ class Puppet::Provider::NameService < Puppet::Provider def initvars @checks = {} + @options = {} super end @@ -76,10 +77,9 @@ class Puppet::Provider::NameService < Puppet::Provider # This is annoying, but there really aren't that many options, # and this *is* built into Ruby. def section - unless defined?(@resource_type) + unless resource_type raise Puppet::DevError, "Cannot determine Etc section without a resource type" - end if @resource_type.name == :group @@ -164,7 +164,7 @@ class Puppet::Provider::NameService < Puppet::Provider end begin - execute(self.addcmd) + execute(self.addcmd, {:failonfail => true, :combine => true, :custom_environment => @custom_environment}) if feature?(:manages_password_age) && (cmd = passcmd) execute(cmd) end @@ -202,7 +202,23 @@ class Puppet::Provider::NameService < Puppet::Provider # Retrieve a specific value by name. def get(param) - (hash = getinfo(false)) ? hash[param] : nil + (hash = getinfo(false)) ? unmunge(param, hash[param]) : nil + end + + def munge(name, value) + if block = self.class.option(name, :munge) and block.is_a? Proc + block.call(value) + else + value + end + end + + def unmunge(name, value) + if block = self.class.option(name, :unmunge) and block.is_a? Proc + block.call(value) + else + value + end end # Retrieve what we can about our object @@ -258,13 +274,13 @@ class Puppet::Provider::NameService < Puppet::Provider def initialize(resource) super - + @custom_environment = {} @objectinfo = nil end def set(param, value) self.class.validate(param, value) - cmd = modifycmd(param, value) + cmd = modifycmd(param, munge(param, value)) raise Puppet::DevError, "Nameservice command must be an array" unless cmd.is_a?(Array) begin execute(cmd) diff --git a/lib/puppet/provider/nameservice/pw.rb b/lib/puppet/provider/nameservice/pw.rb index 74f1a9fbb..f77482692 100644 --- a/lib/puppet/provider/nameservice/pw.rb +++ b/lib/puppet/provider/nameservice/pw.rb @@ -12,7 +12,7 @@ class PW < ObjectAdd "#{@resource.class.name.to_s}mod", @resource[:name], flag(param), - value + munge(param, value) ] cmd end diff --git a/lib/puppet/provider/package/aix.rb b/lib/puppet/provider/package/aix.rb index 803296217..026a982f0 100644 --- a/lib/puppet/provider/package/aix.rb +++ b/lib/puppet/provider/package/aix.rb @@ -2,12 +2,24 @@ require 'puppet/provider/package' require 'puppet/util/package' Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package do - desc "Installation from the AIX software directory." + desc "Installation from an AIX software directory, using the AIX `installp` + command. The `source` parameter is required for this provider, and should + be set to the absolute path (on the puppet agent machine) of a directory + containing one or more BFF package files. + + The `installp` command will generate a table of contents file (named `.toc`) + in this directory, and the `name` parameter (or resource title) that you + specify for your `package` resource must match a package name that exists + in the `.toc` file. + + Note that package downgrades are *not* supported; if your resource specifies + a specific version number and there is already a newer version of the package + installed on the machine, the resource will fail with an error message." # The commands we are using on an AIX box are installed standard # (except nimclient) nimclient needs the bos.sysmgt.nim.client fileset. commands :lslpp => "/usr/bin/lslpp", - :installp => "/usr/sbin/installp" + :installp => "/usr/sbin/installp" # AIX supports versionable packages with and without a NIM server has_feature :versionable @@ -60,6 +72,12 @@ Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package d # Automatically process dependencies when installing/uninstalling # with the -g option to installp. installp "-gu", @resource[:name] + + # installp will return an exit code of zero even if it didn't uninstall + # anything... so let's make sure it worked. + unless query().nil? + self.fail "Failed to uninstall package '#{@resource[:name]}'" + end end def install(useversion = true) @@ -69,9 +87,15 @@ Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package d pkg = @resource[:name] - pkg << " #{@resource.should(:ensure)}" if (! @resource.should(:ensure).is_a? Symbol) and useversion + pkg += " #{@resource.should(:ensure)}" if (! @resource.should(:ensure).is_a? Symbol) and useversion + + output = installp "-acgwXY", "-d", source, pkg - installp "-acgwXY", "-d", source, pkg + # If the package is superseded, it means we're trying to downgrade and we + # can't do that. + if output =~ /^#{Regexp.escape(@resource[:name])}\s+.*\s+Already superseded by.*$/ + self.fail "aix package provider is unable to downgrade packages" + end end def self.pkglist(hash = {}) diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb index f30507b76..a13bbf35f 100755 --- a/lib/puppet/provider/package/gem.rb +++ b/lib/puppet/provider/package/gem.rb @@ -72,8 +72,6 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d def install(useversion = true) command = [command(:gemcmd), "install"] command << "-v" << resource[:ensure] if (! resource[:ensure].is_a? Symbol) and useversion - # Always include dependencies - command << "--include-dependencies" if source = resource[:source] begin diff --git a/lib/puppet/provider/package/macports.rb b/lib/puppet/provider/package/macports.rb index 5bff4e3eb..b410d6fc3 100755 --- a/lib/puppet/provider/package/macports.rb +++ b/lib/puppet/provider/package/macports.rb @@ -14,7 +14,7 @@ Puppet::Type.type(:package).provide :macports, :parent => Puppet::Provider::Pack confine :operatingsystem => :darwin - has_command(:port, "/opt/local/bin/port") do + has_command(:port, "/opt/local/bin/port") do environment :HOME => "/opt/local" end diff --git a/lib/puppet/provider/package/nim.rb b/lib/puppet/provider/package/nim.rb index 49d3c8b30..61d4cbcbf 100644 --- a/lib/puppet/provider/package/nim.rb +++ b/lib/puppet/provider/package/nim.rb @@ -2,11 +2,20 @@ require 'puppet/provider/package' require 'puppet/util/package' Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do - desc "Installation from NIM LPP source." + desc "Installation from an AIX NIM LPP source. The `source` parameter is required + for this provider, and should specify the name of a NIM `lpp_source` resource + that is visible to the puppet agent machine. This provider supports the + management of both BFF/installp and RPM packages. + + Note that package downgrades are *not* supported; if your resource specifies + a specific version number and there is already a newer version of the package + installed on the machine, the resource will fail with an error message." # The commands we are using on an AIX box are installed standard # (except nimclient) nimclient needs the bos.sysmgt.nim.client fileset. - commands :nimclient => "/usr/sbin/nimclient" + commands :nimclient => "/usr/sbin/nimclient", + :lslpp => "/usr/bin/lslpp", + :rpm => "rpm" # If NIM has not been configured, /etc/niminfo will not be present. # However, we have no way of knowing if the NIM server is not configured @@ -17,10 +26,34 @@ Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do attr_accessor :latest_info + + def self.srclistcmd(source) [ command(:nimclient), "-o", "showres", "-a", "installp_flags=L", "-a", "resource=#{source}" ] end + def uninstall + output = lslpp("-qLc", @resource[:name]).split(':') + # the 6th index in the colon-delimited output contains a " " for installp/BFF + # packages, and an "R" for RPMS. (duh.) + pkg_type = output[6] + + case pkg_type + when " " + installp "-gu", @resource[:name] + when "R" + rpm "-e", @resource[:name] + else + self.fail("Unrecognized AIX package type identifier: '#{pkg_type}'") + end + + # installp will return an exit code of zero even if it didn't uninstall + # anything... so let's make sure it worked. + unless query().nil? + self.fail "Failed to uninstall package '#{@resource[:name]}'" + end + end + def install(useversion = true) unless source = @resource[:source] self.fail "An LPP source location is required in 'source'" @@ -28,8 +61,220 @@ Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do pkg = @resource[:name] - pkg << " " << @resource.should(:ensure) if (! @resource.should(:ensure).is_a? Symbol) and useversion + version_specified = (useversion and (! @resource.should(:ensure).is_a? Symbol)) + + # This is unfortunate for a couple of reasons. First, because of a subtle + # difference in the command-line syntax for installing an RPM vs an + # installp/BFF package, we need to know ahead of time which type of + # package we're trying to install. This means we have to execute an + # extra command. + # + # Second, the command is easiest to deal with and runs fastest if we + # pipe it through grep on the shell. Unfortunately, the way that + # the provider `make_command_methods` metaprogramming works, we can't + # use that code path to execute the command (because it treats the arguments + # as an array of args that all apply to `nimclient`, which fails when you + # hit the `|grep`.) So here we just call straight through to P::U.execute + # with a single string argument for the full command, rather than going + # through the metaprogrammed layer. We could get rid of the grep and + # switch back to the metaprogrammed stuff, and just parse all of the output + # in Ruby... but we'd be doing an awful lot of unnecessary work. + showres_command = "/usr/sbin/nimclient -o showres -a resource=#{source} |/usr/bin/grep -p -E " + if (version_specified) + version = @resource.should(:ensure) + showres_command << "'#{Regexp.escape(pkg)}( |-)#{Regexp.escape(version)}'" + else + version = nil + showres_command << "'#{Regexp.escape(pkg)}'" + end + output = Puppet::Util.execute(showres_command) + + + if (version_specified) + package_type = determine_package_type(output, pkg, version) + else + package_type, version = determine_latest_version(output, pkg) + end + + if (package_type == nil) + errmsg = "Unable to find package '#{pkg}' " + if (version_specified) + errmsg << "with version '#{version}' " + end + errmsg << "on lpp_source '#{source}'" + self.fail errmsg + end + + # This part is a bit tricky. If there are multiple versions of the + # package available, then `version` will be set to a value, and we'll need + # to add that value to our installation command. However, if there is only + # one version of the package available, `version` will be set to `nil`, and + # we don't need to add the version string to the command. + if (version) + # Now we know if the package type is RPM or not, and we can adjust our + # `pkg` string for passing to the install command accordingly. + if (package_type == :rpm) + # RPM's expect a hyphen between the package name and the version number + version_separator = "-" + else + # installp/BFF packages expect a space between the package name and the + # version number. + version_separator = " " + end + + pkg += version_separator + version + end + + # NOTE: the installp flags here are ignored (but harmless) for RPMs + output = nimclient "-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=#{source}", "-a", "filesets=#{pkg}" + + # If the package is superseded, it means we're trying to downgrade and we + # can't do that. + case package_type + when :installp + if output =~ /^#{Regexp.escape(@resource[:name])}\s+.*\s+Already superseded by.*$/ + self.fail "NIM package provider is unable to downgrade packages" + end + when :rpm + if output =~ /^#{Regexp.escape(@resource[:name])}.* is superseded by.*$/ + self.fail "NIM package provider is unable to downgrade packages" + end + end + end + + + private + + ## UTILITY METHODS FOR PARSING `nimclient -o showres` output + + # This makes me very sad. These regexes seem pretty fragile, but + # I spent a lot of time trying to figure out a solution that didn't + # require parsing the `nimclient -o showres` output and was unable to + # do so. + HEADER_LINE_REGEX = /^([^\s]+)\s+[^@]+@@(I|R):(\1)\s+[^\s]+$/ + PACKAGE_LINE_REGEX = /^.*@@(I|R):(.*)$/ + RPM_PACKAGE_REGEX = /^(.*)-(.*-\d+) \2$/ + INSTALLP_PACKAGE_REGEX = /^(.*) (.*)$/ - nimclient "-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=#{source}", "-a", "filesets='#{pkg}'" + # Here is some sample output that shows what the above regexes will be up + # against: + # FOR AN INSTALLP PACKAGE: + # + # mypackage.foo ALL @@I:mypackage.foo _all_filesets + # @ 1.2.3.1 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.1 + # + 1.2.3.4 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.4 + # + 1.2.3.8 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.8 + # + # FOR AN RPM PACKAGE: + # + # mypackage.foo ALL @@R:mypackage.foo _all_filesets + # @@R:mypackage.foo-1.2.3-1 1.2.3-1 + # @@R:mypackage.foo-1.2.3-4 1.2.3-4 + # @@R:mypackage.foo-1.2.3-8 1.2.3-8 + + + # Parse the output of a `nimclient -o showres` command. Returns a two-dimensional + # hash, where the first-level keys are package names, the second-level keys are + # version number strings for all of the available version numbers for a package, + # and the values indicate the package type (:rpm / :installp) + def parse_showres_output(showres_output) + paragraphs = split_into_paragraphs(showres_output) + packages = {} + paragraphs.each do |para| + lines = para.split(/$/) + parse_showres_header_line(lines.shift) + lines.each do |l| + package, version, type = parse_showres_package_line(l) + packages[package] ||= {} + packages[package][version] = type + end + end + packages + end + + # This method basically just splits the multi-line input string into chunks + # based on lines that contain nothing but whitespace. It also strips any + # leading or trailing whitespace (including newlines) from the resulting + # strings and then returns them as an array. + def split_into_paragraphs(showres_output) + showres_output.split(/^\s*$/).map { |p| p.strip! } + end + + def parse_showres_header_line(line) + # This method doesn't produce any meaningful output; it's basically just + # meant to validate that the header line for the package listing output + # looks sane, so we know we're dealing with the kind of output that we + # are capable of handling. + unless line.match(HEADER_LINE_REGEX) + self.fail "Unable to parse output from nimclient showres: line does not match expected package header format:\n'#{line}'" + end + end + + def parse_installp_package_string(package_string) + unless match = package_string.match(INSTALLP_PACKAGE_REGEX) + self.fail "Unable to parse output from nimclient showres: package string does not match expected installp package string format:\n'#{package_string}'" + end + package_name = match.captures[0] + version = match.captures[1] + [package_name, version, :installp] + end + + def parse_rpm_package_string(package_string) + unless match = package_string.match(RPM_PACKAGE_REGEX) + self.fail "Unable to parse output from nimclient showres: package string does not match expected rpm package string format:\n'#{package_string}'" + end + package_name = match.captures[0] + version = match.captures[1] + [package_name, version, :rpm] + end + + def parse_showres_package_line(line) + unless match = line.match(PACKAGE_LINE_REGEX) + self.fail "Unable to parse output from nimclient showres: line does not match expected package line format:\n'#{line}'" + end + + package_type_flag = match.captures[0] + package_string = match.captures[1] + + case package_type_flag + when "I" + parse_installp_package_string(package_string) + when "R" + parse_rpm_package_string(package_string) + else + self.fail "Unrecognized package type specifier: '#{package_type_flag}' in package line:\n'#{line}'" + end + end + + # Given a blob of output from `nimclient -o showres` and a package name, + # this method checks to see if there are multiple versions of the package + # available on the lpp_source. If there are, the method returns + # [package_type, latest_version] (where package_type is one of :installp or :rpm). + # If there is only one version of the package available, it returns + # [package_type, nil], because the caller doesn't need to pass the version + # string to the command-line command if there is only one version available. + # If the package is not available at all, the method simply returns nil (instead + # of a tuple). + def determine_latest_version(showres_output, package_name) + packages = parse_showres_output(showres_output) + unless packages.has_key?(package_name) + return nil + end + if (packages[package_name].count == 1) + version = packages[package_name].keys[0] + return packages[package_name][version], nil + else + versions = packages[package_name].keys + latest_version = (versions.sort { |a, b| Puppet::Util::Package.versioncmp(b, a) })[0] + return packages[package_name][latest_version], latest_version + end + end + + def determine_package_type(showres_output, package_name, version) + packages = parse_showres_output(showres_output) + unless (packages.has_key?(package_name) and packages[package_name].has_key?(version)) + return nil + end + packages[package_name][version] end end diff --git a/lib/puppet/provider/package/opkg.rb b/lib/puppet/provider/package/opkg.rb new file mode 100755 index 000000000..619ba1d18 --- /dev/null +++ b/lib/puppet/provider/package/opkg.rb @@ -0,0 +1,77 @@ +require 'puppet/provider/package' + +Puppet::Type.type(:package).provide :opkg, :source => :opkg, :parent => Puppet::Provider::Package do + desc "Opkg packaging support. Common on OpenWrt and OpenEmbedded platforms" + + commands :opkg => "opkg" + + confine :operatingsystem => :openwrt + defaultfor :operatingsystem => :openwrt + + def self.instances + packages = [] + execpipe("#{command(:opkg)} list-installed") do |process| + regex = %r{^(\S+) - (\S+)} + fields = [:name, :ensure] + hash = {} + + process.each_line { |line| + if match = regex.match(line) + fields.zip(match.captures) { |field,value| hash[field] = value } + name = hash[:name] + hash[:provider] = self.name + packages << new(hash) + hash = {} + else + warning("Failed to match line %s" % line) + end + } + end + packages + rescue Puppet::ExecutionFailure + return nil + end + + def latest + output = opkg( "list", @resource[:name]) + matches = /^(\S+) - (\S+)/.match(output).captures + matches[1] + end + + def install + # OpenWrt package lists are ephemeral, make sure we have at least + # some entries in the list directory for opkg to use + opkg('update') if Dir.entries('/var/opkg-lists/').size <= 2 + + if @resource[:source] + opkg( '--force-overwrite', 'install', @resource[:source] ) + else + opkg( '--force-overwrite', 'install', @resource[:name] ) + end + end + + def uninstall + opkg( 'remove', @resource[:name] ) + end + + def update + self.install + end + + def query + # list out our specific package + output = opkg( 'list-installed', @resource[:name] ) + if output =~ /^(\S+) - (\S+)/ + return { :ensure => $2 } + end + nil + rescue Puppet::ExecutionFailure + return { + :ensure => :purged, + :status => 'missing', + :name => @resource[:name], + :error => 'ok', + } + end + +end diff --git a/lib/puppet/provider/package/pacman.rb b/lib/puppet/provider/package/pacman.rb index 20c75beac..33c7eb42f 100644 --- a/lib/puppet/provider/package/pacman.rb +++ b/lib/puppet/provider/package/pacman.rb @@ -115,11 +115,11 @@ Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Packag if pacman_check output = pacman "-Sp", "--print-format", "%v", @resource[:name] return output.chomp - else + else output = yaourt "-Qma", @resource[:name] output.split("\n").each do |line| return line.split[1].chomp if line =~ /^aur/ - end + end end rescue Puppet::ExecutionFailure if pacman_check and self.yaourt? diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb index ff59b6f0c..63df5cc49 100755 --- a/lib/puppet/provider/package/rpm.rb +++ b/lib/puppet/provider/package/rpm.rb @@ -23,19 +23,28 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr end end + def self.current_version + return @current_version unless @current_version.nil? + output = rpm "--version" + @current_version = output.gsub('RPM version ', '').strip + end + + # rpm < 4.1 don't support --nosignature + def self.nosignature + '--nosignature' unless Puppet::Util::Package.versioncmp(current_version, '4.1') < 0 + end + + # rpm < 4.0.2 don't support --nodigest + def self.nodigest + '--nodigest' unless Puppet::Util::Package.versioncmp(current_version, '4.0.2') < 0 + end + def self.instances packages = [] - # rpm < 4.1 don't support --nosignature - output = rpm "--version" - sig = "--nosignature" - if output =~ /RPM version (([123].*)|(4\.0.*))/ - sig = "" - end - # list out all of the packages begin - execpipe("#{command(:rpm)} -qa #{sig} --nodigest --qf '#{NEVRAFORMAT}\n'") { |process| + execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf '#{NEVRAFORMAT}\n'") { |process| # now turn each returned line into a package object process.each_line { |line| hash = nevra_to_hash(line) @@ -56,7 +65,7 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr #NOTE: Prior to a fix for issue 1243, this method potentially returned a cached value #IF YOU CALL THIS METHOD, IT WILL CALL RPM #Use get(:property) to check if cached values are available - cmd = ["-q", @resource[:name], "--nosignature", "--nodigest", "--qf", "#{NEVRAFORMAT}\n"] + cmd = ["-q", @resource[:name], "#{self.class.nosignature}", "#{self.class.nodigest}", "--qf", "#{NEVRAFORMAT}\n"] begin output = rpm(*cmd) @@ -95,13 +104,13 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr end flag = "-i" - flag = "-U" if @property_hash[:ensure] and @property_hash[:ensure] != :absent + flag = ["-U", "--oldpackage"] if @property_hash[:ensure] and @property_hash[:ensure] != :absent - rpm flag, "--oldpackage", source + rpm flag, source end def uninstall - query unless get(:arch) + query if get(:arch) == :absent nvr = "#{get(:name)}-#{get(:version)}-#{get(:release)}" arch = ".#{get(:arch)}" # If they specified an arch in the manifest, erase that Otherwise, @@ -109,10 +118,15 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr # installed and only the package name is specified (without the # arch), this will uninstall all of them on successive runs of the # client, one after the other - if @resource[:name][-arch.size, arch.size] == arch - nvr += arch - else - nvr += ".#{get(:arch)}" + + # version of RPM prior to 4.2.1 can't accept the architecture as + # part of the package name. + unless Puppet::Util::Package.versioncmp(self.class.current_version, '4.2.1') < 0 + if @resource[:name][-arch.size, arch.size] == arch + nvr += arch + else + nvr += ".#{get(:arch)}" + end end rpm "-e", nvr end diff --git a/lib/puppet/provider/package/sun.rb b/lib/puppet/provider/package/sun.rb index ec37a3791..5f15ad13e 100755 --- a/lib/puppet/provider/package/sun.rb +++ b/lib/puppet/provider/package/sun.rb @@ -49,7 +49,7 @@ Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package d end def self.instances - parse_pkginfo(execute([command(:pkginfo), '-l'])).collect do |p| + parse_pkginfo(pkginfo('-l')).collect do |p| hash = namemap(p) hash[:provider] = :sun new(hash) @@ -58,17 +58,24 @@ Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package d # Get info on a package, optionally specifying a device. def info2hash(device = nil) - cmd = [command(:pkginfo), '-l'] - cmd << '-d' << device if device - cmd << @resource[:name] - pkgs = self.class.parse_pkginfo(execute(cmd, :failonfail => false, :combine => false)) - errmsg = case pkgs.size - when 0; 'No message' - when 1; pkgs[0]['ERROR'] - end - return self.class.namemap(pkgs[0]) if errmsg.nil? - return {:ensure => :absent} if errmsg =~ /information for "#{Regexp.escape(@resource[:name])}"/ - raise Puppet::Error, "Unable to get information about package #{@resource[:name]} because of: #{errmsg}" + args = ['-l'] + args << '-d' << device if device + args << @resource[:name] + begin + pkgs = self.class.parse_pkginfo(pkginfo(*args)) + errmsg = case pkgs.size + when 0 + 'No message' + when 1 + pkgs[0]['ERROR'] + end + return self.class.namemap(pkgs[0]) if errmsg.nil? + # according to commit 41356a7 some errors do not raise an exception + # so eventhough pkginfo passed, we have to check the actual output + raise Puppet::Error, "Unable to get information about package #{@resource[:name]} because of: #{errmsg}" + rescue Puppet::ExecutionFailure + return {:ensure => :absent} + end end # Retrieve the version from the current package file. diff --git a/lib/puppet/provider/package/windows/package.rb b/lib/puppet/provider/package/windows/package.rb index d5cfd7e1f..04f0b30b8 100644 --- a/lib/puppet/provider/package/windows/package.rb +++ b/lib/puppet/provider/package/windows/package.rb @@ -1,3 +1,29 @@ +# 'puppet/type/package' is being required here to avoid a load order issue that +# manifests as 'uninitialized constant Puppet::Util::Windows::MsiPackage' or +# 'uninitialized constant Puppet::Util::Windows::Package' (or similar case +# where Puppet::Provider::Package::Windows somehow ends up pointing to +# Puppet:Util::Windows) if puppet/provider/package/windows/package is loaded +# before the puppet/type/package. +# +# Example: +# +# jpartlow@percival:~/work/puppet$ bundle exec rspec spec/unit/provider/package/windows/package_spec.rb spec/unit/provider/package/rpm_spec.rb +# Run options: exclude {:broken=>true} +# ..F..FFF........................ +# +# Failures: +# +# 1) Puppet::Util::Package::Windows::Package::each should yield each package it finds +# Failure/Error: Puppet::Provider::Package::Windows::MsiPackage.expects(:from_registry).with('Google', {}).returns(package) +# NameError: +# uninitialized constant Puppet::Util::Windows::MsiPackage +# # ./spec/unit/provider/package/windows/package_spec.rb:24:in `block (3 levels) in <top (required)>' +# +# --- +# +# Needs more investigation to pinpoint what's going on. +# +require 'puppet/type/package' require 'puppet/util/windows' class Puppet::Provider::Package::Windows diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb index 5da8c7eee..f2f2c1013 100644 --- a/lib/puppet/provider/package/yum.rb +++ b/lib/puppet/provider/package/yum.rb @@ -2,9 +2,9 @@ require 'puppet/util/package' Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do desc "Support via `yum`. - - Using this provider's `uninstallable` feature will not remove dependent packages. To - remove dependent packages with this provider use the `purgeable` feature, but note this + + Using this provider's `uninstallable` feature will not remove dependent packages. To + remove dependent packages with this provider use the `purgeable` feature, but note this feature is destructive and should be used with the utmost care." has_feature :versionable diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb index 51d913a38..b9e8ccfce 100755 --- a/lib/puppet/provider/parsedfile.rb +++ b/lib/puppet/provider/parsedfile.rb @@ -103,6 +103,37 @@ class Puppet::Provider::ParsedFile < Puppet::Provider # HEADER: is definitely not recommended.\n} end + # An optional regular expression matched by third party headers. + # + # For example, this can be used to filter the vixie cron headers as + # erronously exported by older cron versions. + # + # @api private + # @abstract Providers based on ParsedFile may implement this to make it + # possible to identify a header maintained by a third party tool. + # The provider can then allow that header to remain near the top of the + # written file, or remove it after composing the file content. + # If implemented, the function must return a Regexp object. + # The expression must be tailored to match exactly one third party header. + # @see drop_native_header + # @note When specifying regular expressions in multiline mode, avoid + # greedy repititions such as '.*' (use .*? instead). Otherwise, the + # provider may drop file content between sparse headers. + def self.native_header_regex + nil + end + + # How to handle third party headers. + # @api private + # @abstract Providers based on ParsedFile that make use of the support for + # third party headers may override this method to return +true+. + # When this is done, headers that are matched by the native_header_regex + # are not written back to disk. + # @see native_header_regex + def self.drop_native_header + false + end + # Add another type var. def self.initvars @records = [] @@ -186,6 +217,11 @@ class Puppet::Provider::ParsedFile < Puppet::Provider match_providers_with_resources(resources) end + # Match a list of catalog resources with provider instances + # + # @api private + # + # @param [Array<Puppet::Resource>] resources A list of resources using this class as a provider def self.match_providers_with_resources(resources) return unless resources matchers = resources.dup @@ -193,11 +229,10 @@ class Puppet::Provider::ParsedFile < Puppet::Provider # Skip things like comments and blank lines next if skip_record?(record) - if name = record[:name] and resource = resources[name] + if (resource = resource_for_record(record, resources)) resource.provider = new(record) elsif respond_to?(:match) if resource = match(record, matchers) - # Remove this resource from circulation so we don't unnecessarily try to match matchers.delete(resource.title) record[:name] = resource[:name] resource.provider = new(record) @@ -206,6 +241,21 @@ class Puppet::Provider::ParsedFile < Puppet::Provider end end + # Look up a resource based on a parsed file record + # + # @api private + # + # @param [Hash<Symbol, Object>] record + # @param [Array<Puppet::Resource>] resources + # + # @return [Puppet::Resource, nil] The resource if found, else nil + def self.resource_for_record(record, resources) + name = record[:name] + if name + resources[name] + end + end + def self.prefetch_all_targets(resources) records = [] targets(resources).each do |target| @@ -216,7 +266,16 @@ class Puppet::Provider::ParsedFile < Puppet::Provider # Prefetch an individual target. def self.prefetch_target(target) - target_records = retrieve(target).each do |r| + + begin + target_records = retrieve(target) + rescue Puppet::Util::FileType::FileReadError => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not prefetch #{self.resource_type.name} provider '#{self.name}' target '#{target}': #{detail}. Treating as empty" + target_records = [] + end + + target_records.each do |r| r[:on_disk] = true r[:target] = target r[:ensure] = :present @@ -299,8 +358,26 @@ class Puppet::Provider::ParsedFile < Puppet::Provider targets.uniq.compact end + # Compose file contents from the set of records. + # + # If self.native_header_regex is not nil, possible vendor headers are + # identified by matching the return value against the expression. + # If one (or several consecutive) such headers, are found, they are + # either moved in front of the self.header if self.drop_native_header + # is false (this is the default), or removed from the return value otherwise. + # + # @api private def self.to_file(records) text = super + if native_header_regex and (match = text.match(native_header_regex)) + if drop_native_header + # concatenate the text in front of and after the native header + text = match.pre_match + match.post_match + else + native_header = match[0] + return native_header + header + match.pre_match + match.post_match + end + end header + text end diff --git a/lib/puppet/provider/selmodule/semodule.rb b/lib/puppet/provider/selmodule/semodule.rb index d7ce77fd6..ba864b783 100644 --- a/lib/puppet/provider/selmodule/semodule.rb +++ b/lib/puppet/provider/selmodule/semodule.rb @@ -73,7 +73,7 @@ Puppet::Type.type(:selmodule).provide(:semodule) do end def selmod_readnext (handle) - len = handle.read(4).unpack('L')[0] + len = handle.read(4).unpack('V')[0] handle.read(len) end @@ -83,7 +83,7 @@ Puppet::Type.type(:selmodule).provide(:semodule) do filename = selmod_name_to_filename mod = File.new(filename, "r") - (hdr, ver, numsec) = mod.read(12).unpack('LLL') + (hdr, ver, numsec) = mod.read(12).unpack('VVV') raise Puppet::Error, "Found #{hdr} instead of magic #{magic} in #{filename}" if hdr != magic diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb index 5903bd09f..a0707725b 100755 --- a/lib/puppet/provider/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb @@ -18,10 +18,6 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do defaultfor :operatingsystem => [:debian, :ubuntu] - def self.defpath - superclass.defpath - end - # Remove the symlinks def disable if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0 diff --git a/lib/puppet/provider/service/freebsd.rb b/lib/puppet/provider/service/freebsd.rb index 43e0af646..8cf07bab5 100644 --- a/lib/puppet/provider/service/freebsd.rb +++ b/lib/puppet/provider/service/freebsd.rb @@ -41,7 +41,7 @@ Puppet::Type.type(:service).provide :freebsd, :parent => :init do def rcvar_name name = self.rcvar[1] self.error("No rcvar name found in rcvar") if name.nil? - name = name.gsub!(/(.*)(_enable)?=(.*)/, '\1') + name = name.gsub!(/(.*?)(_enable)?=(.*)/, '\1') self.error("rcvar name is empty") if name.nil? self.debug("rcvar name is #{name}") name @@ -73,7 +73,7 @@ Puppet::Type.type(:service).provide :freebsd, :parent => :init do [rcconf, rcconf_local, rcconf_dir + "/#{service}"].each do |filename| if File.exists?(filename) s = File.read(filename) - if s.gsub!(/(#{rcvar}(_enable)?)=\"?(YES|NO)\"?/, "\\1=\"#{yesno}\"") + if s.gsub!(/^(#{rcvar}(_enable)?)=\"?(YES|NO)\"?/, "\\1=\"#{yesno}\"") File.open(filename, File::WRONLY) { |f| f << s } self.debug("Replaced in #{filename}") success = true diff --git a/lib/puppet/provider/service/gentoo.rb b/lib/puppet/provider/service/gentoo.rb index 46e41e23c..f4750eb71 100644 --- a/lib/puppet/provider/service/gentoo.rb +++ b/lib/puppet/provider/service/gentoo.rb @@ -12,15 +12,6 @@ Puppet::Type.type(:service).provide :gentoo, :parent => :init do confine :operatingsystem => :gentoo - def self.defpath - superclass.defpath - end - - def self.instances - # this exclude list was found with grep -L '\/sbin\/runscript' /etc/init.d/* - self.get_services(self.defpath, ['functions.sh', 'reboot.sh', 'shutdown.sh']) - end - def disable output = update :del, @resource[:name], :default rescue Puppet::ExecutionFailure diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index 372ab20cd..19bc31568 100755 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -19,12 +19,38 @@ Puppet::Type.type(:service).provide :init, :parent => :base do # We can't confine this here, because the init path can be overridden. #confine :exists => defpath + # some init scripts are not safe to execute, e.g. we do not want + # to suddently run /etc/init.d/reboot.sh status and reboot our system. The + # exclude list could be platform agnostic but I assume an invalid init script + # on system A will never be a valid init script on system B + def self.excludes + excludes = [] + # these exclude list was found with grep -L '\/sbin\/runscript' /etc/init.d/* on gentoo + excludes += %w{functions.sh reboot.sh shutdown.sh} + # this exclude list is all from /sbin/service (5.x), but I did not exclude kudzu + excludes += %w{functions halt killall single linuxconf reboot boot} + # 'wait-for-state' and 'portmap-wait' are excluded from instances here + # because they take parameters that have unclear meaning. It looks like + # 'wait-for-state' is a generic waiter mainly used internally for other + # upstart services as a 'sleep until something happens' + # (http://lists.debian.org/debian-devel/2012/02/msg01139.html), while + # 'portmap-wait' is a specific instance of a waiter. There is an open + # launchpad bug + # (https://bugs.launchpad.net/ubuntu/+source/upstart/+bug/962047) that may + # eventually explain how to use the wait-for-state service or perhaps why + # it should remain excluded. When that bug is adddressed this should be + # reexamined. + excludes += %w{wait-for-state portmap-wait} + # these excludes were found with grep -r -L start /etc/init.d + excludes += %w{rcS module-init-tools} + end + # List all services of this type. def self.instances get_services(self.defpath) end - def self.get_services(defpath, exclude=[]) + def self.get_services(defpath, exclude = self.excludes) defpath = [defpath] unless defpath.is_a? Array instances = [] defpath.each do |path| @@ -69,7 +95,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do if File.directory?(path) true else - if File.exist?(path) and ! File.directory?(path) + if File.exist?(path) self.debug "Search path #{path} is not a directory" else self.debug "Search path #{path} does not exist" diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb index 5f3726a9c..206676aec 100644 --- a/lib/puppet/provider/service/launchd.rb +++ b/lib/puppet/provider/service/launchd.rb @@ -166,7 +166,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do end # Launchd implemented plist overrides in version 10.6. - # This method checks the major_version of OS X and returns true if + # This method checks the major_version of OS X and returns true if # it is 10.6 or greater. This allows us to implement different plist # behavior for versions >= 10.6 def has_macosx_plist_overrides? diff --git a/lib/puppet/provider/service/openwrt.rb b/lib/puppet/provider/service/openwrt.rb new file mode 100644 index 000000000..f11712756 --- /dev/null +++ b/lib/puppet/provider/service/openwrt.rb @@ -0,0 +1,36 @@ +Puppet::Type.type(:service).provide :openwrt, :parent => :init, :source => :init do + desc <<-EOT + Support for OpenWrt flavored init scripts. + + Uses /etc/init.d/service_name enable, disable, and enabled. + + EOT + + defaultfor :operatingsystem => :openwrt + confine :operatingsystem => :openwrt + + has_feature :enableable + + def self.defpath + ["/etc/init.d"] + end + + def enable + system(self.initscript, 'enable') + end + + def disable + system(self.initscript, 'disable') + end + + def enabled? + # We can't define the "command" for the init script, so we call system? + if system(self.initscript, 'enabled') then return :true else return :false end + end + + # Purposely leave blank so we fail back to ps based status detection + # As OpenWrt init script do not have status commands + def statuscmd + end + +end diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index 63c699854..0dd5f41a8 100755 --- a/lib/puppet/provider/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb @@ -10,15 +10,6 @@ Puppet::Type.type(:service).provide :redhat, :parent => :init, :source => :init defaultfor :osfamily => [:redhat, :suse] - def self.instances - # this exclude list is all from /sbin/service (5.x), but I did not exclude kudzu - self.get_services(['/etc/init.d'], ['functions', 'halt', 'killall', 'single', 'linuxconf', 'reboot', 'boot']) - end - - def self.defpath - superclass.defpath - end - # Remove the symlinks def disable # The off method operates on run levels 2,3,4 and 5 by default We ensure diff --git a/lib/puppet/provider/service/src.rb b/lib/puppet/provider/service/src.rb index 4243d38d2..8028751ff 100755 --- a/lib/puppet/provider/service/src.rb +++ b/lib/puppet/provider/service/src.rb @@ -14,13 +14,26 @@ Puppet::Type.type(:service).provide :src, :parent => :base do defaultfor :operatingsystem => :aix confine :operatingsystem => :aix - commands :stopsrc => "/usr/bin/stopsrc" - commands :startsrc => "/usr/bin/startsrc" - commands :refresh => "/usr/bin/refresh" - commands :lssrc => "/usr/bin/lssrc" + optional_commands :stopsrc => "/usr/bin/stopsrc", + :startsrc => "/usr/bin/startsrc", + :refresh => "/usr/bin/refresh", + :lssrc => "/usr/bin/lssrc", + :lsitab => "/usr/sbin/lsitab", + :mkitab => "/usr/sbin/mkitab", + :rmitab => "/usr/sbin/rmitab", + :chitab => "/usr/sbin/chitab" has_feature :refreshable + def self.instances + services = lssrc('-S') + services.split("\n").reject { |x| x.strip.start_with? '#' }.collect do |line| + data = line.split(':') + service_name = data[0] + new(:name => service_name) + end + end + def startcmd [command(:startsrc), "-s", @resource[:name]] end @@ -29,6 +42,27 @@ Puppet::Type.type(:service).provide :src, :parent => :base do [command(:stopsrc), "-s", @resource[:name]] end + def default_runlevel + "2" + end + + def default_action + "once" + end + + def enabled? + execute([command(:lsitab), @resource[:name]], {:failonfail => false, :combine => true}) + $CHILD_STATUS.exitstatus == 0 ? :true : :false + end + + def enable + mkitab("%s:%s:%s:%s" % [@resource[:name], default_runlevel, default_action, startcmd.join(" ")]) + end + + def disable + rmitab(@resource[:name]) + end + def restart execute([command(:lssrc), "-Ss", @resource[:name]]).each_line do |line| args = line.split(":") diff --git a/lib/puppet/provider/service/systemd.rb b/lib/puppet/provider/service/systemd.rb index e206b1154..3996cfe70 100755 --- a/lib/puppet/provider/service/systemd.rb +++ b/lib/puppet/provider/service/systemd.rb @@ -1,11 +1,12 @@ # Manage systemd services using /bin/systemctl Puppet::Type.type(:service).provide :systemd, :parent => :base do - desc "Manages `systemd` services using `/bin/systemctl`." + desc "Manages `systemd` services using `systemctl`." - commands :systemctl => "/bin/systemctl" + commands :systemctl => "systemctl" #defaultfor :osfamily => [:redhat, :suse] + defaultfor :osfamily => [:archlinux] def self.instances i = [] diff --git a/lib/puppet/provider/service/upstart.rb b/lib/puppet/provider/service/upstart.rb index 221d970d6..04665c971 100755 --- a/lib/puppet/provider/service/upstart.rb +++ b/lib/puppet/provider/service/upstart.rb @@ -25,15 +25,8 @@ Puppet::Type.type(:service).provide :upstart, :parent => :debian do # http://www.linuxplanet.com/linuxplanet/tutorials/7033/2/ has_feature :enableable - # 'wait-for-state' is excluded from instances here because it takes - # parameters that have unclear meaning. It looks like 'wait-for-state' is - # mainly used internally for other upstart services as a 'sleep until something happens' - # (http://lists.debian.org/debian-devel/2012/02/msg01139.html). There is an open launchpad bug - # (https://bugs.launchpad.net/ubuntu/+source/upstart/+bug/962047) that may - # eventually explain how to use this service or perhaps why it should remain - # excluded. When that bug is adddressed this should be reexamined. def self.instances - self.get_services(['wait-for-state']) + self.get_services(self.excludes) # Take exclude list from init provider end def self.get_services(exclude=[]) diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index 6c9679d8e..d46b24778 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -72,6 +72,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do {:aix_attr => :maxage, :puppet_prop => :password_max_age}, {:aix_attr => :minage, :puppet_prop => :password_min_age}, {:aix_attr => :attributes, :puppet_prop => :attributes}, + {:aix_attr => :gecos, :puppet_prop => :comment}, ] #-------------- @@ -94,7 +95,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do end def lscmd(value=@resource[:name]) - [self.class.command(:list)] + self.get_ia_module_args + [ value] + [self.class.command(:list), "-c"] + self.get_ia_module_args + [ value] end def lsallcmd() @@ -146,7 +147,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do end # Get the groupname from its id - def self.groupname_by_id(gid) + def groupname_by_id(gid) groupname=nil execute(lsgroupscmd("ALL")).each_line { |entry| attrs = self.parse_attr_list(entry, nil) @@ -166,7 +167,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # Check that a group exists and is valid def verify_group(value) if value.is_a? Integer or value.is_a? Fixnum - groupname = self.groupname_by_id(value) + groupname = groupname_by_id(value) raise ArgumentError, "AIX group must be a valid existing group" unless groupname else raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) @@ -297,13 +298,6 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do end end - #- **comment** - # A description of the user. Generally is a user's full name. - #def comment=(value) - #end - # - #def comment - #end # UNSUPPORTED #- **profile_membership** # Whether specified roles should be treated as the only roles diff --git a/lib/puppet/provider/user/pw.rb b/lib/puppet/provider/user/pw.rb index 87ad00adc..343fd8a7d 100644 --- a/lib/puppet/provider/user/pw.rb +++ b/lib/puppet/provider/user/pw.rb @@ -12,7 +12,11 @@ Puppet::Type.type(:user).provide :pw, :parent => Puppet::Provider::NameService:: options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" - options :expiry, :method => :expire + options :expiry, :method => :expire, :munge => proc { |value| + value = '0000-00-00' if value == :absent + value.split("-").reverse.join("-") + } + verify :gid, "GID must be an integer" do |value| value.is_a? Integer @@ -26,21 +30,13 @@ Puppet::Type.type(:user).provide :pw, :parent => Puppet::Provider::NameService:: cmd = [command(:pw), "useradd", @resource[:name]] @resource.class.validproperties.each do |property| next if property == :ensure or property == :password - # the value needs to be quoted, mostly because -c might - # have spaces in it if value = @resource.should(property) and value != "" - if property == :expiry - # FreeBSD uses DD-MM-YYYY rather than YYYY-MM-DD - value = value.split("-").reverse.join("-") - end - cmd << flag(property) << value + cmd << flag(property) << munge(property,value) end end cmd << "-o" if @resource.allowdupe? - cmd << "-m" if @resource.managehome? - cmd end diff --git a/lib/puppet/provider/user/useradd.rb b/lib/puppet/provider/user/useradd.rb index 351e749e8..400788e13 100644 --- a/lib/puppet/provider/user/useradd.rb +++ b/lib/puppet/provider/user/useradd.rb @@ -1,4 +1,8 @@ require 'puppet/provider/nameservice/objectadd' +require 'date' +require 'puppet/util/libuser' +require 'time' +require 'puppet/error' Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "User management via `useradd` and its ilk. Note that you will need to @@ -10,8 +14,71 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" - options :password_min_age, :flag => "-m" - options :password_max_age, :flag => "-M" + options :password_min_age, :flag => "-m", :method => :sp_min + options :password_max_age, :flag => "-M", :method => :sp_max + options :password, :method => :sp_pwdp + options :expiry, :method => :sp_expire, + :munge => proc { |value| + if value == :absent + '' + else + case Facter.value(:operatingsystem) + when 'Solaris' + # Solaris uses %m/%d/%Y for useradd/usermod + expiry_year, expiry_month, expiry_day = value.split('-') + [expiry_month, expiry_day, expiry_year].join('/') + else + value + end + end + }, + :unmunge => proc { |value| + if value == -1 + :absent + else + # Expiry is days after 1970-01-01 + (Date.new(1970,1,1) + value).strftime('%Y-%m-%d') + end + } + + optional_commands :localadd => "luseradd" + has_feature :libuser if Puppet.features.libuser? + + def exists? + return !!localuid if @resource.forcelocal? + super + end + + def uid + return localuid if @resource.forcelocal? + get(:uid) + end + + def finduser(key, value) + passwd_file = "/etc/passwd" + passwd_keys = ['account', 'password', 'uid', 'gid', 'gecos', 'directory', 'shell'] + index = passwd_keys.index(key) + File.open(passwd_file) do |f| + f.each_line do |line| + user = line.split(":") + if user[index] == value + f.close + return user + end + end + end + false + end + + def local_username + user = finduser('uid', @resource.uid) + end + + def localuid + user = finduser('account', resource[:name]) + return user[2] if user + false + end verify :gid, "GID must be an integer" do |value| value.is_a? Integer @@ -27,14 +94,25 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ has_features :manages_passwords, :manages_password_age if Puppet.features.libshadow? def check_allow_dup - @resource.allowdupe? ? ["-o"] : [] + # We have to manually check for duplicates when using libuser + # because by default duplicates are allowed. This check is + # to ensure consistent behaviour of the useradd provider when + # using both useradd and luseradd + if not @resource.allowdupe? and @resource.forcelocal? + if @resource.should(:uid) and finduser('uid', @resource.should(:uid).to_s) + raise(Puppet::Error, "UID #{@resource.should(:uid).to_s} already exists, use allowdupe to force user creation") + end + elsif @resource.allowdupe? and not @resource.forcelocal? + return ["-o"] + end + [] end def check_manage_home cmd = [] - if @resource.managehome? + if @resource.managehome? and not @resource.forcelocal? cmd << "-m" - elsif Facter.value(:osfamily) == 'RedHat' + elsif not @resource.managehome? and Facter.value(:osfamily) == 'RedHat' cmd << "-M" end cmd @@ -42,7 +120,7 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ def check_manage_expiry cmd = [] - if @resource[:expiry] + if @resource[:expiry] and not @resource.forcelocal? cmd << "-e #{@resource[:expiry]}" end @@ -59,30 +137,45 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ def add_properties cmd = [] - Puppet::Type.type(:user).validproperties.each do |property| + # validproperties is a list of properties in undefined order + # sort them to have a predictable command line in tests + Puppet::Type.type(:user).validproperties.sort.each do |property| next if property == :ensure next if property.to_s =~ /password_.+_age/ + next if property == :groups and @resource.forcelocal? + next if property == :expiry and @resource.forcelocal? # the value needs to be quoted, mostly because -c might # have spaces in it if value = @resource.should(property) and value != "" - cmd << flag(property) << value + cmd << flag(property) << munge(property, value) end end cmd end def addcmd - cmd = [command(:add)] + if @resource.forcelocal? + cmd = [command(:localadd)] + @custom_environment = Puppet::Util::Libuser.getenv + else + cmd = [command(:add)] + end + if not @resource.should(gid) and Puppet::Util.gid(@resource[:name]) + cmd += ["-g", @resource[:name]] + end cmd += add_properties cmd += check_allow_dup cmd += check_manage_home - cmd += check_manage_expiry cmd += check_system_users cmd << @resource[:name] end def deletecmd - cmd = [command(:delete)] + if @resource.forcelocal? + cmd = [command(:localdelete)] + else + cmd = [command(:delete)] + end cmd += @resource.managehome? ? ['-r'] : [] cmd << @resource[:name] end @@ -96,31 +189,29 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ end end - def password_min_age - if Puppet.features.libshadow? - if ent = Shadow::Passwd.getspnam(@resource.name) - return ent.sp_min + [:expiry, :password_min_age, :password_max_age, :password].each do |shadow_property| + define_method(shadow_property) do + if Puppet.features.libshadow? + if ent = Shadow::Passwd.getspnam(@resource.name) + method = self.class.option(shadow_property, :method) + return unmunge(shadow_property, ent.send(method)) + end end + :absent end - :absent end - def password_max_age - if Puppet.features.libshadow? - if ent = Shadow::Passwd.getspnam(@resource.name) - return ent.sp_max - end - end - :absent + def create + super + if @resource.forcelocal? and self.groups? + set(:groups, @resource[:groups]) + end + if @resource.forcelocal? and @resource[:expiry] + set(:expiry, @resource[:expiry]) + end end - # Retrieve the password using the Shadow Password library - def password - if Puppet.features.libshadow? - if ent = Shadow::Passwd.getspnam(@resource.name) - return ent.sp_pwdp - end - end - :absent + def groups? + !!@resource[:groups] end end diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb index 3421d35b0..6d03764d2 100644 --- a/lib/puppet/provider/vlan/cisco.rb +++ b/lib/puppet/provider/vlan/cisco.rb @@ -8,8 +8,8 @@ Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::Cisco do def self.lookup(device, id) vlans = {} - device.command do |d| - vlans = d.parse_vlans || {} + device.command do |dev| + vlans = dev.parse_vlans || {} end vlans[id] end @@ -20,8 +20,8 @@ Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::Cisco do # Clear out the cached values. def flush - device.command do |device| - device.update_vlan(resource[:name], former_properties, properties) + device.command do |dev| + dev.update_vlan(resource[:name], former_properties, properties) end super end diff --git a/lib/puppet/rails/fact_name.rb b/lib/puppet/rails/fact_name.rb index 08301d9f0..073bbcb0d 100644 --- a/lib/puppet/rails/fact_name.rb +++ b/lib/puppet/rails/fact_name.rb @@ -1,3 +1,4 @@ +require 'active_record' require 'puppet/rails' require 'puppet/rails/fact_value' diff --git a/lib/puppet/rails/fact_value.rb b/lib/puppet/rails/fact_value.rb index 9fd81ae1c..918c0aca7 100644 --- a/lib/puppet/rails/fact_value.rb +++ b/lib/puppet/rails/fact_value.rb @@ -1,3 +1,5 @@ +require 'active_record' + class Puppet::Rails::FactValue < ActiveRecord::Base belongs_to :fact_name belongs_to :host diff --git a/lib/puppet/reference/function.rb b/lib/puppet/reference/function.rb index 7d39bebd5..78f6aa9c0 100644 --- a/lib/puppet/reference/function.rb +++ b/lib/puppet/reference/function.rb @@ -8,8 +8,8 @@ performing stand-alone work like importing. Rvalues return values and can only be used in a statement requiring a value, such as an assignment or a case statement. -Functions execute on the Puppet master. They do not execute on the Puppet agent. -Hence they only have access to the commands and data available on the Puppet master +Functions execute on the Puppet master. They do not execute on the Puppet agent. +Hence they only have access to the commands and data available on the Puppet master host. Here are the functions available in Puppet: diff --git a/lib/puppet/reference/indirection.rb b/lib/puppet/reference/indirection.rb index af1440d1e..e997c88e9 100644 --- a/lib/puppet/reference/indirection.rb +++ b/lib/puppet/reference/indirection.rb @@ -12,21 +12,62 @@ reference = Puppet::Util::Reference.newreference :indirection, :doc => "Indirect text << ind.doc + "\n\n" + text << "### Termini\n\n" + Puppet::Indirector::Terminus.terminus_classes(ind.name).sort { |a,b| a.to_s <=> b.to_s }.each do |terminus| terminus_name = terminus.to_s term_class = Puppet::Indirector::Terminus.terminus_class(ind.name, terminus) - terminus_doc = Puppet::Util::Docs.scrub(term_class.doc) - text << markdown_definitionlist(terminus_name, terminus_doc) + if term_class + terminus_doc = Puppet::Util::Docs.scrub(term_class.doc) + text << markdown_definitionlist(terminus_name, terminus_doc) + else + Puppet.warning "Could not build docs for indirector #{name.inspect}, terminus #{terminus_name.inspect}: could not locate terminus." + end end end text end -reference.header = "This is the list of all indirections, their associated terminus classes, and how you select between them. +reference.header = <<HEADER + +# About Indirection + +Puppet's indirector support pluggable backends (termini) for a variety of key-value stores (indirections). +Each indirection type corresponds to a particular Ruby class (the "Indirected Class" below) and values are instances of that class. +Each instance's key is available from its `name` method. +The termini can be local (e.g., on-disk files) or remote (e.g., using a REST interface to talk to a puppet master). + +An indirector has five methods, which are mapped into HTTP verbs for the REST interface: + + * `find(key)` - get a single value (mapped to GET or POST with a singular endpoint) + * `search(key)` - get a list of matching values (mapped to GET with a plural endpoint) + * `head(key)` - return true if the key exists (mapped to HEAD) + * `destroy(key)` - remove the key van value (mapped to DELETE) + * `save(instance)` - write the instance to the store, using the instance's name as the key (mapped to PUT) + +These methods are available via the `indirection` class method on the indirected classes. For example:: + + foo_cert = Puppet::SSL::Certificate.indirection.find('foo.example.com') + +At startup, each indirection is configured with a terminus. +In most cases, this is the default terminus defined by the indirected class, but it can be overridden by the application or face, or overridden with the `route_file` configuration. +The available termini differ for each indirection, and are listed below. + +Indirections can also have a cache, represented by a second terminus. +This is a write-through cache: modifications are written both to the cache and to the primary terminus. +Values fetched from the terminus are written to the cache. + +## Interaction with REST + +REST endpoints have the form `/{environment}/{indirection}/{key}`, where the indirection can be singular or plural, following normal English spelling rules. +On the server side, REST responses are generated from the locally-configured endpoints. + +## Indirections and Termini + +Below is the list of all indirections, their associated terminus classes, and how you select between them. In general, the appropriate terminus class is selected by the application for you (e.g., `puppet agent` would always use the `rest` terminus for most of its indirected classes), but some classes are tunable via normal settings. These will have `terminus setting` documentation listed with them. - -" +HEADER diff --git a/lib/puppet/reference/metaparameter.rb b/lib/puppet/reference/metaparameter.rb index 7b04840f6..f65dc2ba9 100644 --- a/lib/puppet/reference/metaparameter.rb +++ b/lib/puppet/reference/metaparameter.rb @@ -11,8 +11,8 @@ metaparameter = Puppet::Util::Reference.newreference :metaparameter, :doc => "Al str = %{ # Metaparameters - -Metaparameters are parameters that work with any resource type; they are part of the + +Metaparameters are parameters that work with any resource type; they are part of the Puppet framework itself rather than being part of the implementation of any given instance. Thus, any defined metaparameter can be used with any instance in your manifest, including defined components. diff --git a/lib/puppet/reports.rb b/lib/puppet/reports.rb index bf89ad510..be6e8203f 100755 --- a/lib/puppet/reports.rb +++ b/lib/puppet/reports.rb @@ -12,18 +12,18 @@ require 'puppet/util/instance_loader' # def process # if self.status == 'failed' # msg = "failed puppet run for #{self.host} #{self.status}" -# . . . +# . . . # else # . . . # end -# end +# end # end # # Required configuration: # -- # * A .rb file that defines a new report should be placed in the master's directory `lib/puppet/reports` # * The `puppet.conf` file must have `report = true` in the `[agent]` section -# +# # @see Puppet::Transaction::Report # @api public # @@ -43,14 +43,14 @@ class Puppet::Reports # The block should contain setup, and define a method with the name `process`. The `process` method is # called when the report is executed; the `process` method has access to report data via methods available # in its context where `self` is an instance of {Puppet::Transaction::Report}. - # + # # For an example, see the overview of this class. # # @param name [Symbol] the name of the report (do not use whitespace in the name). # @param options [Hash] a hash of options # @option options [Boolean] :useyaml whether yaml should be used or not # @todo Uncertain what the options :useyaml really does; "whether yaml should be used or not", used where/how? - # + # def self.register_report(name, options = {}, &block) name = name.intern diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index 760d04437..3ac9edb57 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -1,10 +1,10 @@ Puppet::Reports.register_report(:rrdgraph) do desc "Graph all available data about hosts using the RRD library. You must have the Ruby RRDtool library installed to use this report, which - you can get from - [the RubyRRDTool RubyForge page](http://rubyforge.org/projects/rubyrrdtool/). - This package may also be available as `ruby-rrd` or `rrdtool-ruby` in your - distribution's package management system. The library and/or package will both + you can get from + [the RubyRRDTool RubyForge page](http://rubyforge.org/projects/rubyrrdtool/). + This package may also be available as `ruby-rrd` or `rrdtool-ruby` in your + distribution's package management system. The library and/or package will both require the binary `rrdtool` package from your distribution to be installed. This report will create, manage, and graph RRD database files for each diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index c55089015..f3485432c 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -6,7 +6,7 @@ require 'time' Puppet::Reports.register_report(:tagmail) do desc "This report sends specific log messages to specific email addresses - based on the tags in the log messages. + based on the tags in the log messages. See the [documentation on tags](http://projects.puppetlabs.com/projects/puppet/wiki/Using_Tags) for more information. diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb index babb13bcd..317e7f1e2 100644 --- a/lib/puppet/resource.rb +++ b/lib/puppet/resource.rb @@ -313,7 +313,7 @@ class Puppet::Resource # # Example: # - # class foo($port){ ... } + # class foo($port=0){ ... } # # We make a request to the backend for the key 'foo::port' not 'foo' # diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb index 3f08a152e..117513296 100644 --- a/lib/puppet/resource/type.rb +++ b/lib/puppet/resource/type.rb @@ -1,8 +1,9 @@ -require 'puppet/parser/parser' +require 'puppet/parser' require 'puppet/util/warnings' require 'puppet/util/errors' require 'puppet/util/inline_docs' require 'puppet/parser/ast/leaf' +require 'puppet/parser/ast/block_expression' require 'puppet/dsl' class Puppet::Resource::Type @@ -190,14 +191,15 @@ class Puppet::Resource::Type return end - array_class = Puppet::Parser::AST::ASTArray - self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class) - - if other.code.is_a?(array_class) - code.children += other.code.children - else - code.children << other.code - end + self.code = Puppet::Parser::AST::BlockExpression.new(:children => [self.code, other.code]) +# array_class = Puppet::Parser::AST::ASTArray +# self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class) +# +# if other.code.is_a?(array_class) +# code.children += other.code.children +# else +# code.children << other.code +# end end # Make an instance of the resource type, and place it in the catalog @@ -260,10 +262,10 @@ class Puppet::Resource::Type # MQR TODO: # - # The change(s) introduced by the fix for #4270 are mostly silly & should be + # The change(s) introduced by the fix for #4270 are mostly silly & should be # removed, though we didn't realize it at the time. If it can be established/ # ensured that nodes never call parent_type and that resource_types are always - # (as they should be) members of exactly one resource_type_collection the + # (as they should be) members of exactly one resource_type_collection the # following method could / should be replaced with: # # def parent_type diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb index 2b832201d..39a054700 100644 --- a/lib/puppet/resource/type_collection.rb +++ b/lib/puppet/resource/type_collection.rb @@ -199,9 +199,11 @@ class Puppet::Resource::TypeCollection searchspace.each do |fqname| result = send(type, fqname) unless result - # do not try to autoload if we already tried and it wasn't conclusive - # as this is a time consuming operation. - unless @notfound[fqname] + if @notfound[fqname] and Puppet[:ignoremissingtypes] + # do not try to autoload if we already tried and it wasn't conclusive + # as this is a time consuming operation. Warn the user. + Puppet.warning "Not attempting to load #{type} #{fqname} as this object was missing during a prior compilation" + else result = loader.try_load_fqname(type, fqname) @notfound[fqname] = result.nil? end diff --git a/lib/puppet/scheduler.rb b/lib/puppet/scheduler.rb new file mode 100644 index 000000000..e9697a199 --- /dev/null +++ b/lib/puppet/scheduler.rb @@ -0,0 +1,16 @@ +module Puppet::Scheduler + require 'puppet/scheduler/job' + require 'puppet/scheduler/splay_job' + require 'puppet/scheduler/scheduler' + require 'puppet/scheduler/timer' + + module_function + + def create_job(interval, splay=false, splay_limit=0, &block) + if splay + Puppet::Scheduler::SplayJob.new(interval, splay_limit, &block) + else + Puppet::Scheduler::Job.new(interval, &block) + end + end +end diff --git a/lib/puppet/scheduler/job.rb b/lib/puppet/scheduler/job.rb new file mode 100644 index 000000000..a29a1c86f --- /dev/null +++ b/lib/puppet/scheduler/job.rb @@ -0,0 +1,53 @@ +module Puppet::Scheduler + class Job + attr_reader :run_interval + attr_accessor :last_run + attr_accessor :start_time + + def initialize(run_interval, &block) + self.run_interval = run_interval + @last_run = nil + @run_proc = block + @enabled = true + end + + def run_interval=(interval) + @run_interval = [interval, 0].max + end + + def ready?(time) + if @last_run + @last_run + @run_interval <= time + else + true + end + end + + def enabled? + @enabled + end + + def enable + @enabled = true + end + + def disable + @enabled = false + end + + def interval_to_next_from(time) + if ready?(time) + 0 + else + @run_interval - (time - @last_run) + end + end + + def run(now) + @last_run = now + if @run_proc + @run_proc.call(self) + end + end + end +end diff --git a/lib/puppet/scheduler/scheduler.rb b/lib/puppet/scheduler/scheduler.rb new file mode 100644 index 000000000..908fbdef1 --- /dev/null +++ b/lib/puppet/scheduler/scheduler.rb @@ -0,0 +1,45 @@ +module Puppet::Scheduler + class Scheduler + def initialize(jobs, timer=Puppet::Scheduler::Timer.new) + @timer = timer + @jobs = jobs + end + + def run_loop + mark_start_times(@timer.now) + while not enabled_jobs.empty? + @timer.wait_for(min_interval_to_next_run_from(@timer.now)) + run_ready(@timer.now) + end + end + + private + + def enabled_jobs + @jobs.select(&:enabled?) + end + + def mark_start_times(start_time) + @jobs.each do |job| + job.start_time = start_time + end + end + + def min_interval_to_next_run_from(from_time) + enabled_jobs.map do |j| + j.interval_to_next_from(from_time) + end.min + end + + def run_ready(at_time) + enabled_jobs.each do |j| + # This check intentionally happens right before each run, + # instead of filtering on ready schedulers, since one may adjust + # the readiness of a later one + if j.ready?(at_time) + j.run(at_time) + end + end + end + end +end diff --git a/lib/puppet/scheduler/splay_job.rb b/lib/puppet/scheduler/splay_job.rb new file mode 100644 index 000000000..1e0c116d0 --- /dev/null +++ b/lib/puppet/scheduler/splay_job.rb @@ -0,0 +1,32 @@ +module Puppet::Scheduler + class SplayJob < Job + attr_reader :splay + + def initialize(run_interval, splay_limit, &block) + @splay = calculate_splay(splay_limit) + super(run_interval, &block) + end + + def interval_to_next_from(time) + if last_run + super + else + (start_time + splay) - time + end + end + + def ready?(time) + if last_run + super + else + start_time + splay <= time + end + end + + private + + def calculate_splay(limit) + rand(limit + 1) + end + end +end diff --git a/lib/puppet/scheduler/timer.rb b/lib/puppet/scheduler/timer.rb new file mode 100644 index 000000000..98de899fe --- /dev/null +++ b/lib/puppet/scheduler/timer.rb @@ -0,0 +1,13 @@ +module Puppet::Scheduler + class Timer + def wait_for(seconds) + if seconds > 0 + select([], [], [], seconds) + end + end + + def now + Time.now + end + end +end diff --git a/lib/puppet/settings/base_setting.rb b/lib/puppet/settings/base_setting.rb index 0bbb293a5..92c904e90 100644 --- a/lib/puppet/settings/base_setting.rb +++ b/lib/puppet/settings/base_setting.rb @@ -23,7 +23,7 @@ class Puppet::Settings::BaseSetting Puppet.deprecation_warning ":call_on_define has been changed to :call_hook => :on_define_and_write. Please change #{name}." @call_hook = :on_define_and_write else - Puppet.deprecation_warning ":call_on_define => :false has been changed to :call_hook => :on_write_only. Please change #{name}." + Puppet.deprecation_warning ":call_on_define => :false has been changed to :call_hook => :on_write_only. Please change #{name}." @call_hook = :on_write_only end end diff --git a/lib/puppet/settings/file_setting.rb b/lib/puppet/settings/file_setting.rb index 36275852c..8227c5a4b 100644 --- a/lib/puppet/settings/file_setting.rb +++ b/lib/puppet/settings/file_setting.rb @@ -109,7 +109,7 @@ class Puppet::Settings::FileSetting < Puppet::Settings::StringSetting end def munge(value) - if value.is_a?(String) + if value.is_a?(String) and value != ':memory:' # for sqlite3 in-memory tests value = File.expand_path(value) end value diff --git a/lib/puppet/simple_graph.rb b/lib/puppet/simple_graph.rb index 74001d567..2556cbfce 100644 --- a/lib/puppet/simple_graph.rb +++ b/lib/puppet/simple_graph.rb @@ -19,7 +19,7 @@ class Puppet::SimpleGraph # # This class is intended to be used with DAGs. However, if the # graph has a cycle, it will not cause non-termination of any of the - # algorithms. + # algorithms. # def initialize @in_to = {} @@ -376,7 +376,7 @@ class Puppet::SimpleGraph end def direct_dependents_of(v) - (@out_from[v] || {}).keys + (@out_from[v] || {}).keys end def upstream_from_vertex(v) @@ -390,7 +390,7 @@ class Puppet::SimpleGraph end def direct_dependencies_of(v) - (@in_to[v] || {}).keys + (@in_to[v] || {}).keys end # Return an array of the edge-sets between a series of n+1 vertices (f=v0,v1,v2...t=vn) @@ -404,7 +404,7 @@ class Puppet::SimpleGraph # * if there is no path from f to t, the result is nil # # This implementation is not particularly efficient; it's used in testing where clarity - # is more important than last-mile efficiency. + # is more important than last-mile efficiency. # def path_between(f,t) if f==t diff --git a/lib/puppet/ssl/base.rb b/lib/puppet/ssl/base.rb index d1e480903..5ceda6245 100644 --- a/lib/puppet/ssl/base.rb +++ b/lib/puppet/ssl/base.rb @@ -1,6 +1,7 @@ require 'openssl' require 'puppet/ssl' require 'puppet/ssl/digest' +require 'puppet/util/ssl' # The base class for wrapping SSL instances. class Puppet::SSL::Base @@ -47,9 +48,18 @@ class Puppet::SSL::Base self.class.validate_certname(@name) end - # Method to extract a 'name' from the subject of a certificate + ## + # name_from_subject extracts the common name attribute from the subject of an + # x.509 certificate certificate + # + # @api private + # + # @param [OpenSSL::X509::Name] subject The full subject (distinguished name) of the x.509 + # certificate. + # + # @return [String] the name (CN) extracted from the subject. def self.name_from_subject(subject) - subject.to_s.sub(/\/CN=/i, '') + Puppet::Util::SSL.cn_from_subject(subject) end # Create an instance of our Puppet::SSL::* class using a given instance of the wrapped class diff --git a/lib/puppet/ssl/certificate.rb b/lib/puppet/ssl/certificate.rb index fde5c048c..a49267ac7 100644 --- a/lib/puppet/ssl/certificate.rb +++ b/lib/puppet/ssl/certificate.rb @@ -10,7 +10,10 @@ class Puppet::SSL::Certificate < Puppet::SSL::Base wraps OpenSSL::X509::Certificate extend Puppet::Indirector - indirects :certificate, :terminus_class => :file + indirects :certificate, :terminus_class => :file, :doc => <<DOC + This indirection wraps an `OpenSSL::X509::Certificate` object, representing a certificate (signed public key). + The indirection key is the certificate CN (generally a hostname). +DOC # Because of how the format handler class is included, this # can't be in the base class. diff --git a/lib/puppet/ssl/certificate_request.rb b/lib/puppet/ssl/certificate_request.rb index 0d90e5a4f..cc27eebbe 100644 --- a/lib/puppet/ssl/certificate_request.rb +++ b/lib/puppet/ssl/certificate_request.rb @@ -19,7 +19,10 @@ class Puppet::SSL::CertificateRequest < Puppet::SSL::Base end end - indirects :certificate_request, :terminus_class => :file, :extend => AutoSigner + indirects :certificate_request, :terminus_class => :file, :extend => AutoSigner, :doc => <<DOC + This indirection wraps an `OpenSSL::X509::Request` object, representing a certificate signing request (CSR). + The indirection key is the certificate CN (generally a hostname). +DOC # Because of how the format handler class is included, this # can't be in the base class. diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb index 3786be02a..bb6595477 100644 --- a/lib/puppet/ssl/certificate_revocation_list.rb +++ b/lib/puppet/ssl/certificate_revocation_list.rb @@ -8,7 +8,10 @@ class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base wraps OpenSSL::X509::CRL extend Puppet::Indirector - indirects :certificate_revocation_list, :terminus_class => :file + indirects :certificate_revocation_list, :terminus_class => :file, :doc => <<DOC + This indirection wraps an `OpenSSL::X509::CRL` object, representing a certificate revocation list (CRL). + The indirection key is the CA name (usually literally `ca`). +DOC # Convert a string into an instance. def self.from_s(string) diff --git a/lib/puppet/ssl/configuration.rb b/lib/puppet/ssl/configuration.rb index c712ba337..eee52c268 100644 --- a/lib/puppet/ssl/configuration.rb +++ b/lib/puppet/ssl/configuration.rb @@ -1,4 +1,5 @@ require 'puppet/ssl' +require 'openssl' module Puppet module SSL # Puppet::SSL::Configuration is intended to separate out the following concerns: @@ -27,6 +28,37 @@ class Configuration def ca_auth_file @ca_auth_file || @localcacert end + + ## + # ca_auth_certificates returns an Array of OpenSSL::X509::Certificate + # instances intended to be used in the connection verify_callback. This + # method loads and parses the {#ca_auth_file} from the filesystem. + # + # @api private + # + # @return [Array<OpenSSL::X509::Certificate>] + def ca_auth_certificates + @ca_auth_certificates ||= decode_cert_bundle(read_file(ca_auth_file)) + end + + ## + # Decode a string of concatenated certificates + # + # @return [Array<OpenSSL::X509::Certificate>] + def decode_cert_bundle(bundle_str) + re = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m + pem_ary = bundle_str.scan(re) + pem_ary.map do |pem_str| + OpenSSL::X509::Certificate.new(pem_str) + end + end + private :decode_cert_bundle + + # read_file makes testing easier. + def read_file(path) + File.read(path) + end + private :read_file end end end diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index 22428736e..e97ffe6c0 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -16,7 +16,10 @@ class Puppet::SSL::Host CertificateRevocationList = Puppet::SSL::CertificateRevocationList extend Puppet::Indirector - indirects :certificate_status, :terminus_class => :file + indirects :certificate_status, :terminus_class => :file, :doc => <<DOC + This indirection represents the host that ties a key, certificate, and certificate request together. + The indirection key is the certificate CN (generally a hostname). +DOC attr_reader :name attr_accessor :ca @@ -156,20 +159,12 @@ class Puppet::SSL::Host @certificate_request ||= CertificateRequest.indirection.find(name) end - def this_csr_is_for_the_current_host - name == Puppet[:certname].downcase - end - - def this_csr_is_for_the_current_host - name == Puppet[:certname].downcase - end - # Our certificate request requires the key but that's all. def generate_certificate_request(options = {}) generate_key unless key - # If this is for the current machine... - if this_csr_is_for_the_current_host + # If this CSR is for the current machine... + if name == Puppet[:certname].downcase # ...add our configured dns_alt_names if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' options[:dns_alt_names] ||= Puppet[:dns_alt_names] @@ -257,10 +252,12 @@ ERROR_STRING # a lookup in the middle of setting our ssl connection. @ssl_store.add_file(Puppet[:localcacert]) - # If there's a CRL, add it to our store. - if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME) - @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] - @ssl_store.add_crl(crl.content) + # If we're doing revocation and there's a CRL, add it to our store. + if Puppet.settings[:certificate_revocation] + if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME) + @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + @ssl_store.add_crl(crl.content) + end end return @ssl_store end @@ -284,7 +281,7 @@ ERROR_STRING # It appears that we have no internal consumers of this api # --jeffweiss 30 aug 2012 pson_hash[:fingerprint] = thing_to_use.fingerprint - + # The above fingerprint doesn't tell us what message digest algorithm was used # No problem, except that the default is changing between 2.7 and 3.0. Also, as # we move to FIPS 140-2 compliance, MD5 is no longer allowed (and, gasp, will @@ -294,15 +291,15 @@ ERROR_STRING # --jeffweiss 31 july 2012 pson_hash[:fingerprints] = {} pson_hash[:fingerprints][:default] = thing_to_use.fingerprint - - suitable_message_digest_algorithms.each do |md| + + suitable_message_digest_algorithms.each do |md| pson_hash[:fingerprints][md] = thing_to_use.fingerprint md end pson_hash[:dns_alt_names] = thing_to_use.subject_alt_names pson_hash.to_pson(*args) end - + # eventually we'll probably want to move this somewhere else or make it # configurable # --jeffweiss 29 aug 2012 @@ -319,7 +316,7 @@ ERROR_STRING rescue SystemExit,NoMemoryError raise rescue Exception => detail - Puppet.log_exception(detail, "Could not request certificate: #{detail}") + Puppet.log_exception(detail, "Could not request certificate: #{detail.message}") if time < 1 puts "Exiting; failed to retrieve certificate and waitforcert is disabled" exit(1) @@ -340,7 +337,7 @@ ERROR_STRING break if certificate Puppet.notice "Did not receive certificate" rescue StandardError => detail - Puppet.log_exception(detail, "Could not request certificate: #{detail}") + Puppet.log_exception(detail, "Could not request certificate: #{detail.message}") end end end diff --git a/lib/puppet/ssl/key.rb b/lib/puppet/ssl/key.rb index 0ddc9623c..569fb706d 100644 --- a/lib/puppet/ssl/key.rb +++ b/lib/puppet/ssl/key.rb @@ -6,7 +6,10 @@ class Puppet::SSL::Key < Puppet::SSL::Base wraps OpenSSL::PKey::RSA extend Puppet::Indirector - indirects :key, :terminus_class => :file + indirects :key, :terminus_class => :file, :doc => <<DOC + This indirection wraps an `OpenSSL::PKey::RSA object, representing a private key. + The indirection key is the certificate CN (generally a hostname). +DOC # Because of how the format handler class is included, this # can't be in the base class. diff --git a/lib/puppet/ssl/validator.rb b/lib/puppet/ssl/validator.rb new file mode 100644 index 000000000..18255fb3c --- /dev/null +++ b/lib/puppet/ssl/validator.rb @@ -0,0 +1,116 @@ +require 'puppet/ssl' +require 'openssl' +module Puppet +module SSL +class Validator + attr_reader :peer_certs + attr_reader :verify_errors + attr_reader :ssl_configuration + + ## + # @param [Hash] opts the options to initialze the instance with. + # + # @option opts [Puppet::SSL::Configuration] :ssl_configuration to use for + # authorizing the peer certificate chain. + def initialize(opts = {}) + reset! + @ssl_configuration = opts[:ssl_configuration] or raise ArgumentError, ":ssl_configuration is required" + end + + ## + # reset to the initial state. + def reset! + @peer_certs = [] + @verify_errors = [] + end + + ## + # call performs verification of the SSL connection and collection of the + # certificates for use in constructing the error message if the verification + # failed. This callback will be executed once for each certificate in a + # chain being verified. + # + # From the [OpenSSL + # documentation](http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html): + # The `verify_callback` function is used to control the behaviour when the + # SSL_VERIFY_PEER flag is set. It must be supplied by the application and + # receives two arguments: preverify_ok indicates, whether the verification of + # the certificate in question was passed (preverify_ok=1) or not + # (preverify_ok=0). x509_ctx is a pointer to the complete context used for + # the certificate chain verification. + # + # See {Puppet::Network::HTTP::Connection} for more information and where this + # class is intended to be used. + # + # @param [Boolean] preverify_ok indicates whether the verification of the + # certificate in question was passed (preverify_ok=true) + # @param [OpenSSL::SSL::SSLContext] ssl_context holds the SSLContext for the + # chain being verified. + # + # @return [Boolean] false if the peer is invalid, true otherwise. + def call(preverify_ok, ssl_context) + # We must make a copy since the scope of the ssl_context will be lost + # across invocations of this method. + current_cert = ssl_context.current_cert + @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert) + + if preverify_ok + # If we've copied all of the certs in the chain out of the SSL library + if @peer_certs.length == ssl_context.chain.length + # (#20027) The peer cert must be issued by a specific authority + preverify_ok = valid_peer? + end + else + if ssl_context.error_string + @verify_errors << "#{ssl_context.error_string} for #{current_cert.subject}" + end + end + preverify_ok + rescue => ex + @verify_errors << ex.message + false + end + + ## + # Register the instance's call method with the connection. + # + # @param [Net::HTTP] connection The connection to velidate + # + # @return [void] + def register_verify_callback(connection) + connection.verify_callback = self + end + + ## + # Validate the peer certificates against the authorized certificates. + def valid_peer? + descending_cert_chain = @peer_certs.reverse.map {|c| c.content } + authz_ca_certs = ssl_configuration.ca_auth_certificates + + if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs) + msg = "The server presented a SSL certificate chain which does not include a " << + "CA listed in the ssl_client_ca_auth file. " + msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " << + "Peer Chain: #{descending_cert_chain.collect {|c| c.subject}.join(' => ')}" + @verify_errors << msg + false + else + true + end + end + + ## + # checks if the set of peer_certs contains at least one certificate issued + # by a certificate listed in authz_certs + # + # @return [Boolean] + def has_authz_peer_cert(peer_certs, authz_certs) + peer_certs.any? do |peer_cert| + authz_certs.any? do |authz_cert| + peer_cert.verify(authz_cert.public_key) + end + end + end +end +end +end diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 60d880b98..68671aee5 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -224,7 +224,7 @@ class Puppet::Transaction # Should we ignore tags? def ignore_tags? - ! @catalog.host_config? + ! @catalog.host_config? end # this should only be called by a Puppet::Type::Component resource now diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb index 52bc6010f..066ef2067 100644 --- a/lib/puppet/transaction/event.rb +++ b/lib/puppet/transaction/event.rb @@ -9,7 +9,7 @@ class Puppet::Transaction::Event include Puppet::Util::Tagging include Puppet::Util::Logging - ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited] + ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited, :invalidate_refreshes] YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}.map(&:to_sym) attr_accessor *ATTRIBUTES attr_writer :tags @@ -29,15 +29,8 @@ class Puppet::Transaction::Event end def resource=(res) - begin - # In Ruby 1.8 looking up a symbol on a string gives nil; in 1.9 it will - # raise a TypeError, which we then catch. This should work on both - # versions, for all that it is a bit naff. --daniel 2012-03-11 - if res.respond_to?(:[]) and level = res[:loglevel] - @default_log_level = level - end - rescue TypeError => e - raise unless e.to_s == "can't convert Symbol into Integer" + if res.respond_to?(:[]) and level = res[:loglevel] + @default_log_level = level end @resource = res.to_s end diff --git a/lib/puppet/transaction/event_manager.rb b/lib/puppet/transaction/event_manager.rb index b5106f24c..187f27a56 100644 --- a/lib/puppet/transaction/event_manager.rb +++ b/lib/puppet/transaction/event_manager.rb @@ -59,6 +59,13 @@ class Puppet::Transaction::EventManager queue_events_for_resource(resource, resource, :refresh, [event]) if resource.self_refresh? and ! resource.deleting? end + + dequeue_events_for_resource(resource, :refresh) if events.detect { |e| e.invalidate_refreshes } + end + + def dequeue_events_for_resource(target, callback) + target.info "Unscheduling #{callback} on #{target}" + @event_queues[target][callback] = {} if @event_queues[target] end def queue_events_for_resource(source, target, callback, events) @@ -83,7 +90,7 @@ class Puppet::Transaction::EventManager def queued_events(resource) return unless callbacks = @event_queues[resource] callbacks.each do |callback, events| - yield callback, events + yield callback, events unless events.empty? end end diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index 260a068b2..ce630c65b 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -3,16 +3,16 @@ require 'puppet/indirector' # This class is used to report what happens on a client. # There are two types of data in a report; _Logs_ and _Metrics_. -# +# # * **Logs** - are the output that each change produces. # * **Metrics** - are all of the numerical data involved in the transaction. -# +# # Use {Puppet::Reports} class to create a new custom report type. This class is indirectly used # as a source of data to report in such a registered report. -# +# # ##Metrics # There are three types of metrics in each report, and each type of metric has one or more values. -# +# # * Time: Keeps track of how long things took. # * Total: Total time for the configuration run # * File: @@ -42,36 +42,36 @@ class Puppet::Transaction::Report # The version of the configuration # @todo Uncertain what this is? # @return [???] the configuration version - attr_accessor :configuration_version - + attr_accessor :configuration_version + # The host name for which the report is generated # @return [String] the host name attr_accessor :host - + # The name of the environment the host is in # @return [String] the environment name attr_accessor :environment - + # A hash with a map from resource to status # @return [Hash<{String => String}>] Resource name to status string. # @todo Uncertain if the types in the hash are correct... attr_reader :resource_statuses - + # A list of log messages. # @return [Array<String>] logged messages attr_reader :logs - + # A hash of metric name to metric value. # @return [Hash<{String => Object}>] A map of metric name to value. # @todo Uncertain if all values are numbers - now marked as Object. # attr_reader :metrics - + # The time when the report data was generated. # @return [Time] A time object indicating when the report data was generated # attr_reader :time - + # The 'kind' of report is the name of operation that triggered the report to be produced. # Typically "apply". # @return [String] the kind of operation that triggered the generation of the report. @@ -80,14 +80,14 @@ class Puppet::Transaction::Report # The status of the client run is an enumeration: 'failed', 'changed' or 'unchanged' # @return [String] the status of the run - one of the values 'failed', 'changed', or 'unchanged' - # + # attr_reader :status - + # @return [String] The Puppet version in String form. # @see Puppet::version() # attr_reader :puppet_version - + # @return [Integer] (3) a report format version number # @todo Unclear what this is - a version? # @@ -237,7 +237,7 @@ class Puppet::Transaction::Report # individual bits represent the presence of different metrics. # # * 0x2 set if there are changes - # * 0x4 set if there are failures + # * 0x4 set if there are resource failures or resources that failed to restart # @return [Integer] A bitmask where 0x2 is set if there are changes, and 0x4 is set of there are failures. # @api public # @@ -245,6 +245,7 @@ class Puppet::Transaction::Report status = 0 status |= 2 if @metrics["changes"]["total"] > 0 status |= 4 if @metrics["resources"]["failed"] > 0 + status |= 4 if @metrics["resources"]["failed_to_restart"] > 0 status end diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb index 1ae305661..fedccfe99 100644 --- a/lib/puppet/transaction/resource_harness.rb +++ b/lib/puppet/transaction/resource_harness.rb @@ -117,11 +117,17 @@ class Puppet::Transaction::ResourceHarness end event rescue => detail + # Execution will continue on StandardErrors, just store the event Puppet.log_exception(detail) event.status = "failure" event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}" event + rescue Exception => detail + # Execution will halt on Exceptions, they get raised to the application + event.status = "failure" + event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}" + raise ensure event.send_log end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index e5328d8db..eefd52140 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -16,7 +16,7 @@ require 'puppet/util/tagging' module Puppet # The base class for all Puppet types. -# +# # A type describes: #-- # * **Attributes** - properties, parameters, and meta-parameters are different types of attributes of a type. @@ -30,7 +30,7 @@ module Puppet # * **Parameters** - additional attributes of the type (that does not directly related to an instance of the managed # resource; if an operation is recursive or not, where to look for things, etc.). A Parameter (in contrast to Property) # has one current value where a Property has two (current-state and wanted-state). -# * **Meta-Parameters** - parameters that are available across all types. A meta-parameter typically has +# * **Meta-Parameters** - parameters that are available across all types. A meta-parameter typically has # additional semantics; like the `require` meta-parameter. A new type typically does not add new meta-parameters, # but you need to be aware of their existence so you do not inadvertently shadow an existing meta-parameters. # * **Parent** - a type can have a super type (that it inherits from). @@ -40,7 +40,7 @@ module Puppet # by a provider) into an internal representation and vice versa. A Type supports adding custom logic for these. # * **Auto Requirements** - a type can specify automatic relationships to resources to ensure that if they are being # managed, they will be processed before this type. -# * **Providers** - a provider is an implementation of a type's behavior - the management of a resource in the +# * **Providers** - a provider is an implementation of a type's behavior - the management of a resource in the # system being managed. A provider is often platform specific and is selected at runtime based on # criteria/predicates specified in the configured providers. See {Puppet::Provider} for details. # * **Device Support** - A type has some support for being applied to a device; i.e. something that is managed @@ -55,12 +55,12 @@ module Puppet # its interface (what can be said/what is known about a resource of this type), # * **Managed Entity** - This is not a term in general use, but is used here when there is a need to make # a distinction between a resource (a description of what/how something should be managed), and what it is -# managing (a file in the file system). The term _managed entity_ is a reference to the "file in the file system" +# managing (a file in the file system). The term _managed entity_ is a reference to the "file in the file system" # * **Isomorphism** - the quality of being _isomorphic_ means that two resource instances with the same name # refers to the same managed entity. Or put differently; _an isomorphic name is the identity of a resource_. # As an example, `exec` resources (that executes some command) have the command (i.e. the command line string) as # their name, and these resources are said to be non-isomorphic. -# +# # @note The Type class deals with multiple concerns; some methods provide an internal DSL for convenient definition # of types, other methods deal with various aspects while running; wiring up a resource (expressed in Puppet DSL # or Ruby DSL) with its _resource type_ (i.e. an instance of Type) to enable validation, transformation of values @@ -70,7 +70,7 @@ module Puppet # documentation of this class, you will be switching between these concepts, as well as switching between # the conceptual level "a resource is an instance of a resource-type" and the actual implementation classes # (Type, Resource, Provider, and various utility and helper classes). -# +# # @api public # # @@ -83,7 +83,7 @@ class Type # Comparing type instances. include Comparable - + # Compares this type against the given _other_ (type) and returns -1, 0, or +1 depending on the order. # @param other [Object] the object to compare against (produces nil, if not kind of Type} # @return [-1, 0, +1, nil] produces -1 if this type is before the given _other_ type, 0 if equals, and 1 if after. @@ -102,7 +102,7 @@ class Type class << self include Puppet::Util::ClassGen include Puppet::Util::Warnings - + # @return [Array<Puppet::Property>] The list of declared properties for the resource type. # The returned lists contains instances if Puppet::Property or its subclasses. attr_reader :properties @@ -154,7 +154,7 @@ class Type @attrtypes[attr] end - + # Provides iteration over meta-parameters. # @yieldparam p [Puppet::Parameter] each meta parameter # @return [void] @@ -179,10 +179,10 @@ class Type # @overload ensurable({|| ... }) # @yield [ ] A block evaluated in scope of the new Parameter # @yieldreturn [void] - # @return [void] + # @return [void] # @dsl type # @api public - # + # def self.ensurable(&block) if block_given? self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block) @@ -193,7 +193,7 @@ class Type end end - # Returns true if the type implements the default behavior expected by being _ensurable_ "by default". + # Returns true if the type implements the default behavior expected by being _ensurable_ "by default". # A type is _ensurable_ by default if it responds to `:exists`, `:create`, and `:destroy`. # If a type implements these methods and have not already specified that it is _ensurable_, it will be # made so with the defaults specified in {ensurable}. @@ -248,7 +248,7 @@ class Type # Returns true if this type is applicable to the given target. # @param target [Symbol] should be :device, :host or :target, if anything else, :host is enforced - # @return [Boolean] true + # @return [Boolean] true # @api private # def self.can_apply_to(target) @@ -313,7 +313,7 @@ class Type # This creates a new meta-parameter that is added to all types. # @param name [Symbol] the name of the parameter # @param options [Hash] a hash with options. - # @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter + # @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. @@ -352,7 +352,7 @@ class Type end # Returns parameters that act as a key. - # All parameters that return true from #isnamevar? or is named `:name` are included in the returned result. + # All parameters that return true from #isnamevar? or is named `:name` are included in the returned result. # @todo would like a better explanation # @return Array<??? Puppet::Parameter> # @@ -383,10 +383,10 @@ class Type # These advanced options are rarely used (only one of the built in puppet types use this, and then only # a small part of the available functionality), and the support for these advanced mappings is not # implemented in a straight forward way. For these reasons, this method has been marked as private). - # + # # @raise [Puppet::DevError] if there is no title pattern and there are two or more key attributes # @return [Array<Array<Regexp, Array<Array <Symbol, Proc>>>>, nil] a structure with a regexp and the first key_attribute ??? - # @comment This wonderful piece of logic creates a structure used by Resource.parse_title which + # @comment This wonderful piece of logic creates a structure used by Resource.parse_title which # has the capability to assign parts of the title to one or more attributes; It looks like an implementation # of a composite identity key (all parts of the key_attributes array are in the key). This can also # be seen in the method uniqueness_key. @@ -396,7 +396,7 @@ class Type # as it raises an exception if there is more than 1. Note that in puppet, it is only File that uses this # to create a different pattern for assigning to the :path attribute # This requires further digging. - # The entire construct is somewhat strange, since resource checks if the method "title_patterns" is + # The entire construct is somewhat strange, since resource checks if the method "title_patterns" is # implemented (it seems it always is) - why take this more expensive regexp mathching route for all # other types? # @api private @@ -422,7 +422,7 @@ class Type # Creates a new parameter. # @param name [Symbol] the name of the parameter # @param options [Hash] a hash with options. - # @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter + # @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. @@ -462,7 +462,7 @@ class Type # @param options [Hash] a hash with options. # @option options [Symbol] :array_matching (:first) specifies how the current state is matched against # the wanted state. Use `:first` if the property is single valued, and (`:all`) otherwise. - # @option options [Class<inherits Puppet::Property>] :parent (Puppet::Property) the super class of this property + # @option options [Class<inherits Puppet::Property>] :parent (Puppet::Property) the super class of this property # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. @@ -474,7 +474,7 @@ class Type # @yield [ ] a required block that is evaluated in the scope of the new property # @api public # @dsl type - # + # def self.newproperty(name, options = {}, &block) name = name.intern @@ -604,7 +604,7 @@ class Type # The logic caches the name of the namevar if it is a single name, but otherwise always # calls key_attributes, and then caches the first if there was only one, otherwise it returns # false and caches this (which is then subsequently returned as a cache hit). - # + # def name_var return @name_var_cache unless @name_var_cache.nil? key_attributes = self.class.key_attributes @@ -634,8 +634,10 @@ class Type end # Sets the 'should' (wanted state) value of a property, or the value of a parameter. - # @return + # @return # @raise [Puppet::Error] if the setting of the value fails, or if the given name is nil. + # @raise [Puppet::ResourceError] when the parameter validation raises Puppet::Error or + # ArgumentError def []=(name,value) name = name.intern @@ -652,9 +654,9 @@ class Type begin # make sure the parameter doesn't have any errors property.value = value - rescue => detail - error = Puppet::Error.new("Parameter #{name} failed on #{ref}: #{detail}") - error.set_backtrace(detail.backtrace) + rescue Puppet::Error, ArgumentError => detail + error = Puppet::ResourceError.new("Parameter #{name} failed on #{ref}: #{detail}") + adderrorcontext(error, detail) raise error end end @@ -667,8 +669,8 @@ class Type # @todo Incomprehensible - the comment says "Remove a property", the code refers to @parameters, and # the method parameter is called "attr" - What is it, property, parameter, both (i.e an attribute) or what? # @todo Don't know what the attr is (name or Property/Parameter?). Guessing it is a String name... - # @todo Is it possible to delete a meta-parameter? - # @todo What does delete mean? Is it deleted from the type or is its value state 'is'/'should' deleted? + # @todo Is it possible to delete a meta-parameter? + # @todo What does delete mean? Is it deleted from the type or is its value state 'is'/'should' deleted? # @param attr [String] the attribute to delete from this object. WHAT IS THE TYPE? # @raise [Puppet::DecError] when an attempt is made to delete an attribute that does not exists. # @@ -700,13 +702,13 @@ class Type # @todo Needs a better explanation "Why should I care who is calling this method?", What do I need to know # about events and how they work? Where can I read about them? # @param options [Hash] options merged with a fixed set of options defined by this method, passed on to {Puppet::Transaction::Event}. - # @return [Puppet::Transaction::Event] the created event + # @return [Puppet::Transaction::Event] the created event def event(options = {}) Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options)) end # @return [Object, nil] Returns the 'should' (wanted state) value for a specified property, or nil if the - # given attribute name is not a property (i.e. if it is a parameter, meta-parameter, or does not exist). + # given attribute name is not a property (i.e. if it is a parameter, meta-parameter, or does not exist). def should(name) name = name.intern (prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil @@ -785,7 +787,7 @@ class Type # to self.class.attrclass to produce a class on which a check is made if it has a method class :default (does # not seem to support an array... # @return [void] - # + # def set_default(attr) return unless klass = self.class.attrclass(attr) return unless klass.method_defined?(:default) @@ -802,7 +804,7 @@ class Type # @todo the comment says: "Convert our object to a hash. This just includes properties." # @todo this is confused, again it is the @parameters instance variable that is consulted, and - # each value is copied - does it contain "properties" and "parameters" or both? Does it contain + # each value is copied - does it contain "properties" and "parameters" or both? Does it contain # meta-parameters? # # @return [Hash{ ??? => ??? }] a hash of WHAT?. The hash is a shallow copy, any changes to the @@ -901,7 +903,6 @@ class Type ############################### # Code related to the container behaviour. - # Returns true if the search should be done in depth-first order. # This implementation always returns false. # @todo What is this used for? @@ -940,7 +941,6 @@ class Type ############################### # Code related to evaluating the resources. - # Returns the ancestors - WHAT? # This implementation always returns an empty list. # @todo WHAT IS THIS ? @@ -1068,7 +1068,7 @@ class Type end end - # Returns the `noop` run mode status of this. + # Returns the `noop` run mode status of this. # @return [Boolean] true if running in noop mode. def noop? # If we're not a host_config, we're almost certainly part of @@ -1088,7 +1088,7 @@ class Type end # Retrieves all known instances. - # @todo Retrieves them from where? Known to whom? + # @todo Retrieves them from where? Known to whom? # Either requires providers or must be overridden. # @raise [Puppet::DevError] when there are no providers and the implementation has not overridded this method. def self.instances @@ -1375,7 +1375,7 @@ class Type # RelationshipMetaparam is an implementation supporting the meta-parameters `:require`, `:subscribe`, # `:notify`, and `:before`. - # + # # class RelationshipMetaparam < Puppet::Parameter class << self @@ -1417,13 +1417,13 @@ class Type # The `:in` relationships are specified by the event-receivers, and `:out` # relationships are specified by the event generator. # @todo references to "event-receivers" and "event generator" means in this context - are those just - # the resources at the two ends of the relationship? + # the resources at the two ends of the relationship? # This way 'source' and 'target' are consistent terms in both edges # and events, i.e. an event targets edges whose source matches # the event's source. The direction of the relationship determines # which resource is applied first and which resource is considered # to be the event generator. - # @return [Array<Puppet::Relationship>] + # @return [Array<Puppet::Relationship>] # @raise [???fail] when a reference can not be resolved # def to_edges @@ -1465,7 +1465,7 @@ class Type end # @todo document this, have no clue what this does... it retuns "RelationshipMetaparam.subclasses" - # + # def self.relationship_params RelationshipMetaparam.subclasses end @@ -1630,7 +1630,7 @@ class Type # @api private # attr_accessor :providerloader - + # @todo Don't know if this is a name, or a reference to a Provider instance (now marked up as an instance # of Provider. # @return [Puppet::Provider, nil] The default provider for this type, or nil if non is defines @@ -1705,7 +1705,7 @@ class Type # this type. # @todo How does the provider know if it is suitable for the type? Is it just suitable for the platform/ # environment where this method is executing? - # @param name [String] the name of the provider for which validity is checked + # @param name [String] the name of the provider for which validity is checked # @return [Boolean] true if the given name references a provider that is suitable # def self.validprovider?(name) @@ -1720,12 +1720,12 @@ class Type # Is this a new provider of a Type (metatype), or a provider of an instance of Type (a resource), or # a Provider (the implementation of a Type's behavior). CONFUSED. It calls magically named methods like # "providify" ... - # @param name [String, Symbol] the name of the WHAT? provider? type? + # @param name [String, Symbol] the name of the WHAT? provider? type? # @param options [Hash{Symbol => Object}] a hash of options, used by this method, and passed on to {#genclass}, (see # it for additional options to pass). # @option options [Puppet::Provider] :parent the parent provider (what is this?) # @option options [Puppet::Type] :resource_type the resource type, defaults to this type if unspecified - # @return [Puppet::Provider] a provider ??? + # @return [Puppet::Provider] a provider ??? # @raise [Puppet::DevError] when the parent provider could not be found. # def self.provide(name, options = {}, &block) @@ -1849,7 +1849,7 @@ class Type rmclass(name, :hash => provider_hash, :prefix => "Provider") end - # Returns a list of suitable providers for the given type. + # Returns a list of suitable providers for the given type. # A call to this method will load all providers if not already loaded and ask each if it is # suitable - those that are are included in the result. # @note This method also does some special processing which rejects a provider named `:fake` (for testing purposes). @@ -1866,7 +1866,7 @@ class Type # @return [Boolean] Returns true if this is something else than a `:provider`, or if it # is a provider and it is suitable, or if there is a default provider. Otherwise, false is returned. - # + # def suitable? # If we don't use providers, then we consider it suitable. return true unless self.class.paramclass(:provider) @@ -1909,11 +1909,10 @@ class Type ############################### # All of the relationship code. - # Adds a block producing a single name (or list of names) of the given resource type name to autorequire. # @example Autorequire the files File['foo', 'bar'] # autorequire( 'file', {|| ['foo', 'bar'] }) - # + # # @todo original = _"Specify a block for generating a list of objects to autorequire. # This makes it so that you don't have to manually specify things that you clearly require."_ # @param name [String] the name of a type of which one or several resources should be autorequired e.g. "file" @@ -1944,9 +1943,9 @@ class Type # See {autorequire} for how to add an auto-requirement. # @todo needs details - see the param rel_catalog, and type of this param # @param rel_catalog [Puppet::Catalog, nil] the catalog to add dependencies to. Defaults to the - # catalog (TODO: what is the type of the catalog). + # catalog (TODO: what is the type of the catalog). # @raise [Puppet::DevError] if there is no catalog - # + # def autorequire(rel_catalog = nil) rel_catalog ||= catalog raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog @@ -2005,7 +2004,7 @@ class Type # are orphaned ... I think they can just be removed as what they say should be covered # by the now added yardoc. <irony>(Yo! to quote some of the other actual awsome specific comments applicable # to objects called from elsewhere, or not. ;-)</irony> - # + # # @comment Types (which map to resources in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an # 'attribute', but these attributes always take one of three specific @@ -2014,13 +2013,12 @@ class Type # @comment In naming methods, I have tried to consistently name the method so # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. - - + # The title attribute of WHAT ??? # @todo Figure out what this is the title attribute of (it appears on line 1926 currently). # @return [String] the title attr_writer :title - + # The noop attribute of WHAT ??? does WHAT??? # @todo Figure out what this is the noop attribute of (it appears on line 1931 currently). # @return [???] the noop WHAT ??? (mode? if so of what, or noop for an instance of the type, or for all @@ -2039,7 +2037,7 @@ class Type # @return [String] the name of the resource type; e.g., "File" # attr_reader :name - + # @return [Boolean] true if the type should send itself a refresh event on change. # attr_accessor :self_refresh @@ -2051,7 +2049,7 @@ class Type end # Initializes all of the variables that must be initialized for each subclass. - # @todo Does the explanation make sense? + # @todo Does the explanation make sense? # @return [void] def self.initvars # all of the instances of this class @@ -2084,7 +2082,7 @@ class Type # The returned name is on the form "Puppet::Type::<name>", where the first letter of name is # capitalized. # @return [String] the fully qualified name Puppet::Type::<name> where the first letter of name is captialized - # + # def self.to_s if defined?(@name) "Puppet::Type::#{@name.to_s.capitalize}" @@ -2111,7 +2109,7 @@ class Type # @return [String] The file from which this type originates from attr_accessor :file - + # @return [Integer] The line in {#file} from which this type originates from attr_accessor :line @@ -2160,9 +2158,13 @@ class Type # causes the title to be resource.ref ("for components") - what is a component? # # @overaload initialize(hsh) - # @param hsh [Hash] + # @param hsh [Hash] + # @raise [Puppet::ResourceError] when the type validation raises + # Puppet::Error or ArgumentError # @overload initialize(resource) # @param resource [Puppet:Resource] + # @raise [Puppet::ResourceError] when the type validation raises + # Puppet::Error or ArgumentError # def initialize(resource) resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource) @@ -2195,7 +2197,13 @@ class Type set_parameters(@original_parameters) - self.validate if self.respond_to?(:validate) + begin + self.validate if self.respond_to?(:validate) + rescue Puppet::Error, ArgumentError => detail + error = Puppet::ResourceError.new("Validation of #{ref} failed: #{detail}") + adderrorcontext(error, detail) + raise error + end end private @@ -2205,9 +2213,9 @@ class Type # given by the corresponding entry in the given hash - e.g. if name_var appoints the name `:path` the value # of `:path` is set to the value at the key `:path` in the given hash. As a side effect this key/value is then # removed from the given hash. - # + # # @note This method mutates the given hash by removing the entry with a key equal to the value - # returned from name_var! + # returned from name_var! # @param hash [Hash] a hash of what # @return [void] def set_name(hash) @@ -2215,7 +2223,7 @@ class Type end # Sets parameters from the given hash. - # Values are set in _attribute order_ i.e. higher priority attributes before others, otherwise in + # Values are set in _attribute order_ i.e. higher priority attributes before others, otherwise in # the order they were specified (as opposed to just setting them in the order they happen to appear in # when iterating over the given hash). # @@ -2257,12 +2265,12 @@ class Type public # Finishes any outstanding processing. - # This method should be called as a final step in setup, + # This method should be called as a final step in setup, # to allow the parameters that have associated auto-require needs to be processed. - # + # # @todo what is the expected sequence here - who is responsible for calling this? When? # Is the returned type correct? - # @return [Array<Puppet::Parameter>] the validated list/set of attributes + # @return [Array<Puppet::Parameter>] the validated list/set of attributes # def finish # Make sure all of our relationships are valid. Again, must be done @@ -2349,9 +2357,9 @@ class Type # @todo it is somewhat confusing that if the name_var is a valid parameter, it is assumed to # be the name_var called :name, but if it is a property, it uses the name_var. # It is further confusing as Type in some respects supports multiple namevars. - # + # # @return [String] Returns the title of this object, or it's name if title was not explicetly set. - # @raise [??? devfail] if title is not set, and name_var can not be found. + # @raise [??? devfail] if title is not set, and name_var can not be found. def title unless @title if self.class.validparameter?(name_var) @@ -2375,7 +2383,7 @@ class Type # @todo What to resource? Which one of the resource forms is prroduced? returned here? # @return [??? Resource] a resource that WHAT??? - # + # def to_resource resource = self.retrieve_resource resource.tag(*self.tags) diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index 01f9bd9c7..ed1b9d53d 100755 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb @@ -10,11 +10,12 @@ Puppet::Type.newtype(:cron) do job is not part of the actual job, it is used by Puppet to store and retrieve it. - If you specify a cron job that matches an existing job in every way - except name, then the jobs will be considered equivalent and the - new name will be permanently associated with that job. Once this - association is made and synced to disk, you can then manage the job - normally (e.g., change the schedule of the job). + If you specify a cron resource that duplicates the scheduling and command + used by an existing crontab entry, then Puppet will take no action and + defers to the existing crontab entry. If the duplicate cron resource + specifies `ensure => absent`, all existing duplicated crontab entries will + be removed. Specifying multiple duplicate cron resources with different + `ensure` states will result in undefined behavior. Example: @@ -42,6 +43,13 @@ Puppet::Type.newtype(:cron) do hour => ['2-4'], minute => '*/10' } + + An important note: _the Cron type will not reset parameters that are + removed from a manifest_. For example, removing a `minute => 10` parameter + will not reset the minute component of the associated cronjob to `*`. + These changes must be expressed by setting the parameter to + `minute => absent` because Puppet only manages parameters that are out of + sync with manifest entries. EOT ensurable @@ -224,6 +232,12 @@ Puppet::Type.newtype(:cron) do nil end end + + def munge(value) + value.sub!(/^\s+/, '') + value.sub!(/\s+$/, '') + value + end end newproperty(:special) do @@ -364,7 +378,7 @@ Puppet::Type.newtype(:cron) do end newproperty(:target) do - desc "The username that will own the cron entry. Defaults to + desc "The username that will own the cron entry. Defaults to the value of $USER for the shell that invoked Puppet, or root if $USER is empty." @@ -417,5 +431,3 @@ Puppet::Type.newtype(:cron) do end end - - diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index e469269f1..35e2c062c 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -137,6 +137,10 @@ module Puppet normal log level (usually `notice`), but if the command fails (meaning its return code does not match the specified code) then any output is logged at the `err` log level." + + validate do |command| + raise ArgumentError, "Command must be a String, got value of class #{command.class}" unless command.is_a? String + end end newparam(:path) do @@ -180,9 +184,11 @@ module Puppet end newparam(:logoutput) do - desc "Whether to log output. Defaults to `on_failure`, which only logs - the output when the command has a non-zero exit code. In addition to - the values below, you may set this attribute to any legal log level." + desc "Whether to log command output in addition to logging the + exit code. Defaults to `on_failure`, which only logs the output + when the command has an exit code that does not match any value + specified by the `returns` attribute. In addition to the values + below, you may set this attribute to any legal log level." defaultto :on_failure diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index cb726cf3a..9a40b0c4d 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -58,47 +58,44 @@ Puppet::Type.newtype(:file) do end newparam(:backup) do - desc "Whether files should be backed up before - being replaced. The preferred method of backing files up is via - a `filebucket`, which stores files by their MD5 sums and allows - easy retrieval without littering directories with backups. You - can specify a local filebucket or a network-accessible - server-based filebucket by setting `backup => bucket-name`. - Alternatively, if you specify any value that begins with a `.` - (e.g., `.puppet-bak`), then Puppet will use copy the file in - the same directory with that value as the extension of the - backup. Setting `backup => false` disables all backups of the - file in question. - - Puppet automatically creates a local filebucket named `puppet` and - defaults to backing up there. To use a server-based filebucket, - you must specify one in your configuration. - - filebucket { main: - server => puppet, - path => false, - # The path => false line works around a known issue with the filebucket type. - } - - The `puppet master` daemon creates a filebucket by default, - so you can usually back up to your main server with this - configuration. Once you've described the bucket in your - configuration, you can use it in any file's backup attribute: - - file { \"/my/file\": - source => \"/path/in/nfs/or/something\", - backup => main - } - - This will back the file up to the central server. - - At this point, the benefits of using a central filebucket are that you - do not have backup files lying around on each of your machines, a given - version of a file is only backed up once, you can restore any given file - manually (no matter how old), and you can use Puppet Dashboard to view - file contents. Eventually, transactional support will be able to - automatically restore filebucketed files. - " + desc <<-EOT + Whether (and how) file content should be backed up before being replaced. + This attribute works best as a resource default in the site manifest + (`File { backup => main }`), so it can affect all file resources. + + * If set to `false`, file content won't be backed up. + * If set to a string beginning with `.` (e.g., `.puppet-bak`), Puppet will + use copy the file in the same directory with that value as the extension + of the backup. (A value of `true` is a synonym for `.puppet-bak`.) + * If set to any other string, Puppet will try to back up to a filebucket + with that title. See the `filebucket` resource type for more details. + (This is the preferred method for backup, since it can be centralized + and queried.) + + Default value: `puppet`, which backs up to a filebucket of the same name. + (Puppet automatically creates a **local** filebucket named `puppet` if one + doesn't already exist.) + + Backing up to a local filebucket isn't particularly useful. If you want + to make organized use of backups, you will generally want to use the + puppet master server's filebucket service. This requires declaring a + filebucket resource and a resource default for the `backup` attribute + in site.pp: + + # /etc/puppet/manifests/site.pp + filebucket { 'main': + path => false, # This is required for remote filebuckets. + server => 'puppet.example.com', # Optional; defaults to the configured puppet master. + } + + File { backup => main, } + + If you are using multiple puppet master servers, you will want to + centralize the contents of the filebucket. Either configure your load + balancer to direct all filebucket traffic to a single master, or use + something like an out-of-band rsync task to synchronize the content on all + masters. + EOT defaultto "puppet" @@ -245,6 +242,18 @@ Puppet::Type.newtype(:file) do newvalues(:first, :all) end + newparam(:show_diff, :boolean => true) do + desc "Whether to display differences when the file changes, defaulting to + true. This parameter is useful for files that may contain passwords or + other secret data, which might otherwise be included in Puppet reports or + other insecure outputs. If the global ``show_diff` configuration parameter + is false, then no diffs will be shown even if this parameter is true." + + defaultto :true + + newvalues(:true, :false) + end + # Autorequire the nearest ancestor directory found in the catalog. autorequire(:file) do req = [] @@ -471,6 +480,11 @@ Puppet::Type.newtype(:file) do @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") end + # Should we be showing diffs? + def show_diff? + @parameters.include?(:show_diff) and (self[:show_diff] == :true or self[:show_diff] == "true") + end + # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. @@ -597,34 +611,35 @@ Puppet::Type.newtype(:file) do ) end - # Remove any existing data. This is only used when dealing with - # links or directories. + # Back up and remove the file or directory at `self[:path]`. + # + # @param [Symbol] should The file type replacing the current content. + # @return [Boolean] True if the file was removed, else False + # @raises [fail???] If the current file isn't one of %w{file link directory} and can't be removed. def remove_existing(should) - return unless s = stat + wanted_type = should.to_s + current_type = read_current_type - self.fail "Could not back up; will not replace" unless perform_backup + if current_type.nil? + return false + end - unless should.to_s == "link" - return if s.ftype.to_s == should.to_s + if can_backup?(current_type) + backup_existing end - case s.ftype + if wanted_type != "link" and current_type == wanted_type + return false + end + + case current_type when "directory" - if self[:force] == :true - debug "Removing existing directory for replacement with #{should}" - FileUtils.rmtree(self[:path]) - else - notice "Not removing directory; use 'force' to override" - return - end + return remove_directory(wanted_type) when "link", "file" - debug "Removing existing #{s.ftype} for replacement with #{should}" - ::File.unlink(self[:path]) + return remove_file(current_type, wanted_type) else - self.fail "Could not back up files of type #{s.ftype}" + self.fail "Could not back up files of type #{current_type}" end - @stat = :needs_stat - true end def retrieve @@ -746,6 +761,64 @@ Puppet::Type.newtype(:file) do private + # @return [String] The type of the current file, cast to a string. + def read_current_type + stat_info = stat + if stat_info + stat_info.ftype.to_s + else + nil + end + end + + # @return [Boolean] If the current file can be backed up and needs to be backed up. + def can_backup?(type) + if type == "directory" and self[:force] == :false + # (#18110) Directories cannot be removed without :force, so it doesn't + # make sense to back them up. + false + else + true + end + end + + # @return [Boolean] True if the directory was removed + # @api private + def remove_directory(wanted_type) + if self[:force] == :true + debug "Removing existing directory for replacement with #{wanted_type}" + FileUtils.rmtree(self[:path]) + stat_needed + true + else + notice "Not removing directory; use 'force' to override" + false + end + end + + # @return [Boolean] if the file was removed (which is always true currently) + # @api private + def remove_file(current_type, wanted_type) + debug "Removing existing #{current_type} for replacement with #{wanted_type}" + ::File.unlink(self[:path]) + stat_needed + true + end + + def stat_needed + @stat = :needs_stat + end + + # Back up the existing file at a given prior to it being removed + # @api private + # @raise [Puppet::Error] if the file backup failed + # @return [void] + def backup_existing + unless perform_backup + raise Puppet::Error, "Could not back up; will not replace" + end + end + # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ @@ -766,8 +839,6 @@ Puppet::Type.newtype(:file) do (content = property(:content)) && content.write(file) end - private - def write_temporary_file? # unfortunately we don't know the source file size before fetching it # so let's assume the file won't be empty diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb index 92f0c93d4..a590e03a6 100755 --- a/lib/puppet/type/file/content.rb +++ b/lib/puppet/type/file/content.rb @@ -107,7 +107,7 @@ module Puppet result = super - if ! result and Puppet[:show_diff] + if ! result and Puppet[:show_diff] and resource.show_diff? write_temporarily do |path| notice "\n" + diff(@resource[:path], path) end diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb index 7c9f23150..1b3a1e8ab 100755 --- a/lib/puppet/type/file/mode.rb +++ b/lib/puppet/type/file/mode.rb @@ -144,7 +144,13 @@ module Puppet end def is_to_s(currentvalue) - currentvalue.rjust(4, "0") + if currentvalue == :absent + # This can occur during audits---if a file is transitioning from + # present to absent the mode will have a value of `:absent`. + super + else + currentvalue.rjust(4, "0") + end end end end diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb index a040acd67..000636b6b 100755 --- a/lib/puppet/type/file/source.rb +++ b/lib/puppet/type/file/source.rb @@ -101,7 +101,7 @@ module Puppet return @content if @content raise Puppet::DevError, "No source for content was stored with the metadata" unless metadata.source - unless tmp = Puppet::FileServing::Content.indirection.find(metadata.source, :environment => resource.catalog.environment) + unless tmp = Puppet::FileServing::Content.indirection.find(metadata.source, :environment => resource.catalog.environment, :links => resource[:links]) fail "Could not find any content at %s" % metadata.source end @content = tmp.content @@ -154,7 +154,7 @@ module Puppet return nil unless value value.each do |source| begin - if data = Puppet::FileServing::Metadata.indirection.find(source, :environment => resource.catalog.environment) + if data = Puppet::FileServing::Metadata.indirection.find(source, :environment => resource.catalog.environment, :links => resource[:links]) @metadata = data @metadata.source = source break diff --git a/lib/puppet/type/filebucket.rb b/lib/puppet/type/filebucket.rb index af7fb2216..cbb551464 100755 --- a/lib/puppet/type/filebucket.rb +++ b/lib/puppet/type/filebucket.rb @@ -2,35 +2,44 @@ module Puppet require 'puppet/file_bucket/dipper' newtype(:filebucket) do - @doc = "A repository for backing up files. If no filebucket is - defined, then files will be backed up in their current directory, - but the filebucket can be either a host- or site-global repository - for backing up. It stores files and returns the MD5 sum, which - can later be used to retrieve the file if restoration becomes - necessary. A filebucket does not do any work itself; instead, - it can be specified as the value of *backup* in a **file** object. - - Currently, filebuckets are only useful for manual retrieval of - accidentally removed files (e.g., you look in the log for the md5 sum - and retrieve the file with that sum from the filebucket), but when - transactions are fully supported filebuckets will be used to undo - transactions. - - You will normally want to define a single filebucket for your - whole network and then use that as the default backup location: - - # Define the bucket + @doc = <<-EOT + A repository for storing and retrieving file content by MD5 checksum. Can + be local to each agent node, or centralized on a puppet master server. All + puppet masters provide a filebucket service that agent nodes can access + via HTTP, but you must declare a filebucket resource before any agents + will do so. + + Filebuckets are used for the following features: + + - **Content backups.** If the `file` type's `backup` attribute is set to + the name of a filebucket, Puppet will back up the _old_ content whenever + it rewrites a file; see the documentation for the `file` type for more + details. These backups can be used for manual recovery of content, but + are more commonly used to display changes and differences in a tool like + Puppet Dashboard. + - **Content distribution.** The optional static compiler populates the + puppet master's filebucket with the _desired_ content for each file, + then instructs the agent to retrieve the content for a specific + checksum. For more details, + [see the `static_compiler` section in the catalog indirection docs](http://docs.puppetlabs.com/references/latest/indirection.html#catalog). + + To use a central filebucket for backups, you will usually want to declare + a filebucket resource and a resource default for the `backup` attribute + in site.pp: + + # /etc/puppet/manifests/site.pp filebucket { 'main': - server => puppet, - path => false, - # Due to a known issue, path must be set to false for remote filebuckets. + path => false, # This is required for remote filebuckets. + server => 'puppet.example.com', # Optional; defaults to the configured puppet master. } - # Specify it as the default target - File { backup => main } + File { backup => main, } - Puppetmaster servers create a filebucket by default, so this will - work in a default configuration." + Puppet master servers automatically provide the filebucket service, so + this will work in a default configuration. If you have a heavily + restricted `auth.conf` file, you may need to allow access to the + `file_bucket_file` endpoint. + EOT newparam(:name) do desc "The name of the filebucket." @@ -38,27 +47,25 @@ module Puppet end newparam(:server) do - desc "The server providing the remote filebucket. If this is not - specified then *path* is checked. If it is set, then the - bucket is local. Otherwise the puppetmaster server specified - in the config or at the commandline is used. + desc "The server providing the remote filebucket service. Defaults to the + value of the `server` setting (that is, the currently configured + puppet master server). - Due to a known issue, you currently must set the `path` attribute to - false if you wish to specify a `server` attribute." + This setting is _only_ consulted if the `path` attribute is set to `false`." defaultto { Puppet[:server] } end newparam(:port) do - desc "The port on which the remote server is listening. - Defaults to the normal Puppet port, %s." % Puppet[:masterport] + desc "The port on which the remote server is listening. Defaults to the + value of the `masterport` setting, which is usually %s." % Puppet[:masterport] defaultto { Puppet[:masterport] } end newparam(:path) do - desc "The path to the local filebucket. If this is - unset, then the bucket is remote. The parameter *server* must - can be specified to set the remote server." + desc "The path to the _local_ filebucket; defaults to the value of the + `clientbucketdir` setting. To use a remote filebucket, you _must_ set + this attribute to `false`." defaultto { Puppet[:clientbucketdir] } diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index 19a0b2daf..e7d77b8d7 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -21,6 +21,10 @@ module Puppet 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." @@ -146,6 +150,13 @@ module Puppet defaultto false end + newparam(:forcelocal, :boolean => true, :required_features => :libuser ) do + desc "Forces the mangement of local accounts when accounts are also + being managed by some other NSS" + newvalues(:true, :false) + 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. diff --git a/lib/puppet/type/scheduled_task.rb b/lib/puppet/type/scheduled_task.rb index a235e7516..290b2717e 100644 --- a/lib/puppet/type/scheduled_task.rb +++ b/lib/puppet/type/scheduled_task.rb @@ -62,7 +62,11 @@ Puppet::Type.newtype(:scheduled_task) do completed successfully'. It is recommended that you either choose another user to run the scheduled task, or alter the security policy to allow v1 scheduled tasks to run as the - 'SYSTEM' account. Defaults to 'SYSTEM'." + 'SYSTEM' account. Defaults to 'SYSTEM'. + + Please also note that Puppet must be running as a privileged user + in order to manage `scheduled_task` resources. Running as an + unprivileged user will result in 'access denied' errors." defaultto :system diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index f15cf14cf..a6a95892f 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -71,7 +71,7 @@ module Puppet provider.stop end - newvalue(:running, :event => :service_started) do + newvalue(:running, :event => :service_started, :invalidate_refreshes => true) do provider.start end diff --git a/lib/puppet/type/ssh_authorized_key.rb b/lib/puppet/type/ssh_authorized_key.rb index a761bc03b..1bdb5863b 100644 --- a/lib/puppet/type/ssh_authorized_key.rb +++ b/lib/puppet/type/ssh_authorized_key.rb @@ -27,8 +27,8 @@ module Puppet end newproperty(:key) do - desc "The public key itself; generally a long string of hex characters. The key attribute - may not contain whitespace: Omit key headers (e.g. 'ssh-rsa') and key identifiers + desc "The public key itself; generally a long string of hex characters. The key attribute + may not contain whitespace: Omit key headers (e.g. 'ssh-rsa') and key identifiers (e.g. 'joe@joescomputer.local') found in the public key file." validate do |value| diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 509b0deb4..d118ee07f 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -49,6 +49,10 @@ module Puppet feature :manages_aix_lam, "The provider can manage AIX Loadable Authentication Module (LAM) system." + feature :libuser, + "Allows local users to be managed on systems that also use some other + remote NSS method of managing accounts." + newproperty(:ensure, :parent => Puppet::Property::Ensure) do newvalue(:present, :event => :user_created) do provider.create @@ -315,11 +319,16 @@ module Puppet newproperty(:expiry, :required_features => :manages_expiry) do desc "The expiry date for this user. Must be provided in - a zero-padded YYYY-MM-DD format --- e.g. 2010-02-19." + a zero-padded YYYY-MM-DD format --- e.g. 2010-02-19. + If you want to make sure the user account does never + expire, you can pass the special value `absent`." + + newvalues :absent + newvalues /^\d{4}-\d{2}-\d{2}$/ validate do |value| - if value !~ /^\d{4}-\d{2}-\d{2}$/ - raise ArgumentError, "Expiry dates must be YYYY-MM-DD" + if value.intern != :absent and value !~ /^\d{4}-\d{2}-\d{2}$/ + raise ArgumentError, "Expiry dates must be YYYY-MM-DD or the string \"absent\"" end end end @@ -549,5 +558,12 @@ module Puppet end end end + + newparam(:forcelocal, :boolean => true, :required_features => :libuser ) do + desc "Forces the mangement of local accounts when accounts are also + being managed by some other NSS" + newvalues(:true, :false) + defaultto false + end end end diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index ef32e60d4..2e2251fff 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -481,7 +481,7 @@ module Util retry end else - File.rename(tempfile.path, file) + File.rename(tempfile.path, file.to_s) end # Ideally, we would now fsync the directory as well, but Ruby doesn't @@ -519,7 +519,17 @@ module Util end module_function :exit_on_fail - + def deterministic_rand(seed,max) + if defined?(Random) == 'constant' && Random.class == Class + Random.new(seed).rand(max).to_s + else + srand(seed) + result = rand(max).to_s + srand() + result + end + end + module_function :deterministic_rand ####################################################################################################### diff --git a/lib/puppet/util/adsi.rb b/lib/puppet/util/adsi.rb index 7568912d1..52eb8baf5 100644 --- a/lib/puppet/util/adsi.rb +++ b/lib/puppet/util/adsi.rb @@ -28,7 +28,7 @@ module Puppet::Util::ADSI unless @computer_name buf = " " * 128 Win32API.new('kernel32', 'GetComputerName', ['P','P'], 'I').call(buf, buf.length.to_s) - @computer_name = buf.unpack("A*") + @computer_name = buf.unpack("A*")[0] end @computer_name end diff --git a/lib/puppet/util/classgen.rb b/lib/puppet/util/classgen.rb index e03bf2ab7..e25599b53 100644 --- a/lib/puppet/util/classgen.rb +++ b/lib/puppet/util/classgen.rb @@ -34,7 +34,7 @@ module Puppet::Util::ClassGen genthing(name, Class, options, block) end - # Creates a new module. + # Creates a new module. # @param name [String] the name of the generated module # @param optinos [Hash] hash with options # @option options [Array<Class>] :array if specified, the generated class is appended to this array @@ -148,10 +148,10 @@ module Puppet::Util::ClassGen # @api private # def is_constant_defined?(const) - if ::RUBY_VERSION =~ /1.9/ - const_defined?(const, false) - else + if ::RUBY_VERSION =~ /^1.8/ const_defined?(const) + else + const_defined?(const, false) end end diff --git a/lib/puppet/util/colors.rb b/lib/puppet/util/colors.rb index 5c74cc996..37bcddf4a 100644 --- a/lib/puppet/util/colors.rb +++ b/lib/puppet/util/colors.rb @@ -83,6 +83,61 @@ module Puppet::Util::Colors # We're on windows, need win32console for color to work begin require 'win32console' + require 'windows/wide_string' + + # The win32console gem uses ANSI functions for writing to the console + # which doesn't work for unicode strings, e.g. module tool. Ruby 1.9 + # does the same thing, but doesn't account for ANSI escape sequences + class WideConsole < Win32::Console + WriteConsole = Win32API.new( "kernel32", "WriteConsoleW", ['l', 'p', 'l', 'p', 'p'], 'l' ) + WriteConsoleOutputCharacter = Win32API.new( "kernel32", "WriteConsoleOutputCharacterW", ['l', 'p', 'l', 'l', 'p'], 'l' ) + + def initialize(t = nil) + super(t) + end + + def WriteChar(str, col, row) + dwWriteCoord = (row << 16) + col + lpNumberOfCharsWritten = ' ' * 4 + utf16, nChars = string_encode(str) + WriteConsoleOutputCharacter.call(@handle, utf16, nChars, dwWriteCoord, lpNumberOfCharsWritten) + lpNumberOfCharsWritten.unpack('L') + end + + def Write(str) + written = 0.chr * 4 + reserved = 0.chr * 4 + utf16, nChars = string_encode(str) + WriteConsole.call(@handle, utf16, nChars, written, reserved) + end + + if String.method_defined?("encode") + def string_encode(str) + wstr = str.encode('UTF-16LE') + [wstr, wstr.length] + end + else + require 'iconv' + def string_encode(str) + wstr = Iconv.conv('UTF-16LE', 'UTF-8', str) + [wstr, wstr.length/2] + end + end + end + + # Override the win32console's IO class so we can supply + # our own Console class + class WideIO < Win32::Console::ANSI::IO + def initialize(fd_std = :stdout) + super(fd_std) + + handle = FD_STD_MAP[fd_std][1] + @Out = WideConsole.new(handle) + end + end + + $stdout = WideIO.new(:stdout) + $stderr = WideIO.new(:stderr) rescue LoadError def console_has_color? false diff --git a/lib/puppet/util/command_line/trollop.rb b/lib/puppet/util/command_line/trollop.rb index dd192fc54..57ff89320 100644 --- a/lib/puppet/util/command_line/trollop.rb +++ b/lib/puppet/util/command_line/trollop.rb @@ -22,7 +22,7 @@ VERSION = "1.16.2" ## Thrown by Parser in the event of a commandline error. Not needed if ## you're using the Trollop::options entry. class CommandlineError < StandardError; end - + ## Thrown by Parser if the user passes in '-h' or '--help'. Handled ## automatically by Trollop#options. class HelpNeeded < StandardError; end @@ -273,7 +273,7 @@ class Parser syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] } @constraints << [:depends, syms] end - + ## Marks two (or more!) options as conflicting. def conflicts *syms syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] } @@ -465,7 +465,7 @@ class Parser # call this unless the cursor's at the beginning of a line. left = {} - @specs.each do |name, spec| + @specs.each do |name, spec| left[name] = "--#{spec[:long]}" + (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") + case spec[:type] @@ -686,7 +686,7 @@ private start = 0 ret = [] until start > str.length - nextt = + nextt = if start + width >= str.length str.length else diff --git a/lib/puppet/util/errors.rb b/lib/puppet/util/errors.rb index fd9b98864..3a0500dd5 100644 --- a/lib/puppet/util/errors.rb +++ b/lib/puppet/util/errors.rb @@ -1,11 +1,24 @@ -# Some helper methods for throwing errors. +# Some helper methods for throwing and populating errors. +# +# @api public module Puppet::Util::Errors - # Throw a dev error. + # Throw a Puppet::DevError with the specified message. Used for unknown or + # internal application failures. + # + # @param msg [String] message used in raised error + # @raise [Puppet::DevError] always raised with the supplied message def devfail(msg) self.fail(Puppet::DevError, msg) end - # Add line and file info if available and appropriate. + # Add line and file info to the supplied exception if info is available from + # this object, is appropriately populated and the supplied exception supports + # it. When other is supplied, the backtrace will be copied to the error + # object. + # + # @param error [Exception] exception that is populated with info + # @param other [Exception] original exception, source of backtrace info + # @return [Exception] error parameter def adderrorcontext(error, other = nil) error.line ||= self.line if error.respond_to?(:line=) and self.respond_to?(:line) and self.line error.file ||= self.file if error.respond_to?(:file=) and self.respond_to?(:file) and self.file @@ -15,6 +28,10 @@ module Puppet::Util::Errors error end + # Return a human-readable string of this object's file and line attributes, + # if set. + # + # @return [String] description of file and line def error_context if file and line " at #{file}:#{line}" @@ -29,6 +46,15 @@ module Puppet::Util::Errors # Wrap a call in such a way that we always throw the right exception and keep # as much context as possible. + # + # @param options [Hash<Symbol,Object>] options used to create error + # @option options [Class] :type error type to raise, defaults to + # Puppet::DevError + # @option options [String] :message message to use in error, default mentions + # the name of this class + # @raise [Puppet::Error] re-raised with extra context if the block raises it + # @raise [Error] of type options[:type], when the block raises other + # exceptions def exceptwrap(options = {}) options[:type] ||= Puppet::DevError begin @@ -48,6 +74,16 @@ module Puppet::Util::Errors end # Throw an error, defaulting to a Puppet::Error. + # + # @overload fail(message, ..) + # Throw a Puppet::Error with a message concatenated from the given + # arguments. + # @param [String] message error message(s) + # @overload fail(error_klass, message, ..) + # Throw an exception of type error_klass with a message concatenated from + # the given arguments. + # @param [Class] type of error + # @param [String] message error message(s) def fail(*args) if args[0].is_a?(Class) type = args.shift diff --git a/lib/puppet/util/fileparsing.rb b/lib/puppet/util/fileparsing.rb index 5ac1c843b..84344c96b 100644 --- a/lib/puppet/util/fileparsing.rb +++ b/lib/puppet/util/fileparsing.rb @@ -141,6 +141,11 @@ module Puppet::Util::FileParsing end # Try to match a record. + # + # @param [String] line The line to be parsed + # @param [Puppet::Util::FileType] record The filetype to use for parsing + # + # @return [Hash<Symbol, Object>] The parsed elements of the line def handle_record_line(line, record) ret = nil if record.respond_to?(:process) diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index ac91d4c42..40278f1df 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -8,6 +8,8 @@ require 'fileutils' class Puppet::Util::FileType attr_accessor :loaded, :path, :synced + class FileReadError < Puppet::Error; end + include Puppet::Util::SELinux class << self @@ -166,7 +168,7 @@ class Puppet::Util::FileType begin @uid = Puppet::Util.uid(user) rescue Puppet::Error => detail - raise Puppet::Error, "Could not retrieve user #{user}: #{detail}", detail.backtrace + raise FileReadError, "Could not retrieve user #{user}: #{detail}", detail.backtrace end # XXX We have to have the user name, not the uid, because some @@ -221,9 +223,9 @@ class Puppet::Util::FileType when /can't open your crontab/ return "" when /you are not authorized to use cron/ - raise Puppet::Error, "User #{@path} not authorized to use cron", detail.backtrace + raise FileReadError, "User #{@path} not authorized to use cron", detail.backtrace else - raise Puppet::Error, "Could not read crontab for #{@path}: #{detail}", detail.backtrace + raise FileReadError, "Could not read crontab for #{@path}: #{detail}", detail.backtrace end end @@ -231,7 +233,7 @@ class Puppet::Util::FileType def remove Puppet::Util::Execution.execute(%w{crontab -r}, cronargs) rescue => detail - raise Puppet::Error, "Could not remove crontab for #{@path}: #{detail}", detail.backtrace + raise FileReadError, "Could not remove crontab for #{@path}: #{detail}", detail.backtrace end # Overwrite a specific @path's cron tab; must be passed the @path name @@ -245,7 +247,7 @@ class Puppet::Util::FileType File.chown(Puppet::Util.uid(@path), nil, output_file.path) Puppet::Util::Execution.execute(["crontab", output_file.path], cronargs) rescue => detail - raise Puppet::Error, "Could not write crontab for #{@path}: #{detail}", detail.backtrace + raise FileReadError, "Could not write crontab for #{@path}: #{detail}", detail.backtrace ensure output_file.close output_file.unlink @@ -263,9 +265,9 @@ class Puppet::Util::FileType when /Cannot open a file in the .* directory/ return "" when /You are not authorized to use the cron command/ - raise Puppet::Error, "User #{@path} not authorized to use cron", detail.backtrace + raise FileReadError, "User #{@path} not authorized to use cron", detail.backtrace else - raise Puppet::Error, "Could not read crontab for #{@path}: #{detail}", detail.backtrace + raise FileReadError, "Could not read crontab for #{@path}: #{detail}", detail.backtrace end end @@ -273,7 +275,7 @@ class Puppet::Util::FileType def remove Puppet::Util::Execution.execute(%w{crontab -r}, cronargs) rescue => detail - raise Puppet::Error, "Could not remove crontab for #{@path}: #{detail}", detail.backtrace + raise FileReadError, "Could not remove crontab for #{@path}: #{detail}", detail.backtrace end # Overwrite a specific @path's cron tab; must be passed the @path name @@ -288,7 +290,7 @@ class Puppet::Util::FileType File.chown(Puppet::Util.uid(@path), nil, output_file.path) Puppet::Util::Execution.execute(["crontab", output_file.path], cronargs) rescue => detail - raise Puppet::Error, "Could not write crontab for #{@path}: #{detail}", detail.backtrace + raise FileReadError, "Could not write crontab for #{@path}: #{detail}", detail.backtrace ensure output_file.close output_file.unlink diff --git a/lib/puppet/util/instrumentation/instrumentable.rb b/lib/puppet/util/instrumentation/instrumentable.rb index 1ce18a777..4c6d2f97e 100644 --- a/lib/puppet/util/instrumentation/instrumentable.rb +++ b/lib/puppet/util/instrumentation/instrumentable.rb @@ -83,11 +83,11 @@ module Puppet::Util::Instrumentation::Instrumentable # this can either be a static symbol/string or a block. If it's a block # this one will be evaluated on every call of the instrumented method and # should return a string or a symbol - # + # # data:: # this can be a hash or a block. If it's a block this one will be evaluated # on every call of the instrumented method and should return a hash. - # + # #Example: # # class MyClass diff --git a/lib/puppet/util/libuser.conf b/lib/puppet/util/libuser.conf new file mode 100644 index 000000000..4b1cb85e1 --- /dev/null +++ b/lib/puppet/util/libuser.conf @@ -0,0 +1,15 @@ +[import] +login_defs = /etc/login.defs +default_useradd = /etc/default/useradd + +[defaults] +crypt_style = md5 +modules = files shadow +create_modules = files shadow + +[userdefaults] +LU_USERNAME = %n +LU_GIDNUMBER = %u + +[groupdefaults] +LU_GROUPNAME = %n diff --git a/lib/puppet/util/libuser.rb b/lib/puppet/util/libuser.rb new file mode 100644 index 000000000..e3940ca00 --- /dev/null +++ b/lib/puppet/util/libuser.rb @@ -0,0 +1,12 @@ +module Puppet::Util::Libuser + def self.getconf + File.expand_path("../libuser.conf", __FILE__) + end + + def self.getenv + newenv = {} + newenv['LIBUSER_CONF'] = getconf + newenv + end + +end diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb index ca19fa484..e9943ee07 100644 --- a/lib/puppet/util/monkey_patches.rb +++ b/lib/puppet/util/monkey_patches.rb @@ -394,3 +394,32 @@ class OpenSSL::SSL::SSLContext set_params(params) end end + +require 'puppet/util/platform' +if Puppet::Util::Platform.windows? + require 'puppet/util/windows' + require 'openssl' + + class OpenSSL::X509::Store + alias __original_set_default_paths set_default_paths + def set_default_paths + # This can be removed once openssl integrates with windows + # cert store, see http://rt.openssl.org/Ticket/Display.html?id=2158 + Puppet::Util::Windows::RootCerts.instance.each do |x509| + add_cert(x509) + end + + __original_set_default_paths + end + end +end + +# Old puppet clients may make large GET requests, lets be reasonably tolerant +# in our default WEBrick server. +require 'webrick' +if defined?(WEBrick::HTTPRequest::MAX_URI_LENGTH) and WEBrick::HTTPRequest::MAX_URI_LENGTH < 8192 + # Silence ruby warning: already initialized constant MAX_URI_LENGTH + v, $VERBOSE = $VERBOSE, nil + WEBrick::HTTPRequest.const_set("MAX_URI_LENGTH", 8192) + $VERBOSE = v +end diff --git a/lib/puppet/util/network_device.rb b/lib/puppet/util/network_device.rb index 7fb8e2ff3..2d0f94f4f 100644 --- a/lib/puppet/util/network_device.rb +++ b/lib/puppet/util/network_device.rb @@ -5,7 +5,7 @@ class Puppet::Util::NetworkDevice def self.init(device) require "puppet/util/network_device/#{device.provider}/device" - @current = Puppet::Util::NetworkDevice.const_get(device.provider.capitalize).const_get(:Device).new(device.url) + @current = Puppet::Util::NetworkDevice.const_get(device.provider.capitalize).const_get(:Device).new(device.url, device.options) rescue => detail raise "Can't load #{device.provider} for #{device.name}: #{detail}" end diff --git a/lib/puppet/util/network_device/base.rb b/lib/puppet/util/network_device/base.rb index d2fbfccb2..8f971efdf 100644 --- a/lib/puppet/util/network_device/base.rb +++ b/lib/puppet/util/network_device/base.rb @@ -7,7 +7,7 @@ class Puppet::Util::NetworkDevice::Base attr_accessor :url, :transport - def initialize(url) + def initialize(url, options = {}) @url = URI.parse(url) @autoloader = Puppet::Util::Autoload.new( @@ -17,7 +17,7 @@ class Puppet::Util::NetworkDevice::Base ) if @autoloader.load(@url.scheme) - @transport = Puppet::Util::NetworkDevice::Transport.const_get(@url.scheme.capitalize).new + @transport = Puppet::Util::NetworkDevice::Transport.const_get(@url.scheme.capitalize).new(options[:debug]) @transport.host = @url.host @transport.port = @url.port || case @url.scheme ; when "ssh" ; 22 ; when "telnet" ; 23 ; end @transport.user = @url.user diff --git a/lib/puppet/util/network_device/cisco/device.rb b/lib/puppet/util/network_device/cisco/device.rb index 588c8504d..1d41d6a34 100644 --- a/lib/puppet/util/network_device/cisco/device.rb +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -13,26 +13,36 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: attr_accessor :enable_password def initialize(url, options = {}) - super(url) + super(url, options) @enable_password = options[:enable_password] || parse_enable(@url.query) transport.default_prompt = /[#>]\s?\z/n end def parse_enable(query) - return $1 if query =~ /enable=(.*)/ + if query + params = CGI.parse(query) + params['enable'].first unless params['enable'].empty? + end end - def command(cmd=nil) - Puppet.debug("command #{cmd}") + def connect transport.connect login transport.command("terminal length 0") do |out| enable if out =~ />\s?\z/n end find_capabilities + end + + def disconnect + transport.close + end + + def command(cmd = nil) + connect out = execute(cmd) if cmd yield self if block_given? - transport.close + disconnect out end @@ -61,14 +71,14 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: end def find_capabilities - out = transport.command("sh vlan brief") + out = execute("sh vlan brief") lines = out.split("\n") lines.shift; lines.pop @support_vlan_brief = ! (lines.first =~ /^%/) end - IF={ + IF = { :FastEthernet => %w{FastEthernet FastEth Fast FE Fa F}, :GigabitEthernet => %w{GigabitEthernet GigEthernet GigEth GE Gi G}, :TenGigabitEthernet => %w{TenGigabitEthernet TE Te}, @@ -116,7 +126,7 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: def parse_interface(name) resource = {} - out = transport.command("sh interface #{name}") + out = execute("sh interface #{name}") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| @@ -144,7 +154,7 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: def parse_interface_config(name) resource = Hash.new { |hash, key| hash[key] = Array.new ; } - out = transport.command("sh running-config interface #{name} | begin interface") + out = execute("sh running-config interface #{name} | begin interface") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| @@ -166,7 +176,7 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: def parse_vlans vlans = {} - out = transport.command(support_vlan_brief? ? "sh vlan brief" : "sh vlan-switch brief") + out = execute(support_vlan_brief? ? "sh vlan brief" : "sh vlan-switch brief") lines = out.split("\n") lines.shift; lines.shift; lines.shift; lines.pop vlan = nil @@ -193,27 +203,27 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: def update_vlan(id, is = {}, should = {}) if should[:ensure] == :absent Puppet.info "Removing #{id} from device vlan" - transport.command("conf t") - transport.command("no vlan #{id}") - transport.command("exit") + execute("conf t") + execute("no vlan #{id}") + execute("exit") return end # We're creating or updating an entry - transport.command("conf t") - transport.command("vlan #{id}") + execute("conf t") + execute("vlan #{id}") [is.keys, should.keys].flatten.uniq.each do |property| Puppet.debug("trying property: #{property}: #{should[property]}") next if property != :description - transport.command("name #{should[property]}") + execute("name #{should[property]}") end - transport.command("exit") - transport.command("exit") + execute("exit") + execute("exit") end def parse_trunking(interface) trunking = {} - out = transport.command("sh interface #{interface} switchport") + out = execute("sh interface #{interface} switchport") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| diff --git a/lib/puppet/util/network_device/config.rb b/lib/puppet/util/network_device/config.rb index 6480d5970..cf28761ba 100644 --- a/lib/puppet/util/network_device/config.rb +++ b/lib/puppet/util/network_device/config.rb @@ -59,10 +59,11 @@ class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile device = OpenStruct.new device.name = name device.line = count + device.options = { :debug => false } Puppet.debug "found device: #{device.name} at #{device.line}" devices[name] = device - when /^\s*(type|url)\s+(.+)$/ - parse_directive(device, $1, $2, count) + when /^\s*(type|url|debug)(\s+(.+))*$/ + parse_directive(device, $1, $3, count) else raise Puppet::Error, "Invalid line #{count}: #{line}" end @@ -85,6 +86,8 @@ class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile device.provider = value when "url" device.url = value + when "debug" + device.options[:debug] = true else raise Puppet::Error, "Invalid argument '#{var}' at line #{count}" end diff --git a/lib/puppet/util/network_device/ipcalc.rb b/lib/puppet/util/network_device/ipcalc.rb index b2e3aa673..8ca5295a7 100644 --- a/lib/puppet/util/network_device/ipcalc.rb +++ b/lib/puppet/util/network_device/ipcalc.rb @@ -1,5 +1,5 @@ - require 'puppet/util/network_device' + module Puppet::Util::NetworkDevice::IPCalc # This is a rip-off of authstore diff --git a/lib/puppet/util/network_device/transport/ssh.rb b/lib/puppet/util/network_device/transport/ssh.rb index dca5600bd..daee1d3a3 100644 --- a/lib/puppet/util/network_device/transport/ssh.rb +++ b/lib/puppet/util/network_device/transport/ssh.rb @@ -7,10 +7,11 @@ require 'puppet/util/network_device/transport/base' # a sane interface to Net::SSH. Credits goes to net-ssh-telnet authors class Puppet::Util::NetworkDevice::Transport::Ssh < Puppet::Util::NetworkDevice::Transport::Base - attr_accessor :buf, :ssh, :channel, :verbose + attr_accessor :buf, :ssh, :channel - def initialize - super + def initialize(verbose = false) + super() + @verbose = verbose unless Puppet.features.ssh? raise 'Connecting with ssh to a network device requires the \'net/ssh\' ruby library' end diff --git a/lib/puppet/util/network_device/transport/telnet.rb b/lib/puppet/util/network_device/transport/telnet.rb index e9322f81b..b25ce688e 100644 --- a/lib/puppet/util/network_device/transport/telnet.rb +++ b/lib/puppet/util/network_device/transport/telnet.rb @@ -4,8 +4,9 @@ require 'puppet/util/network_device/transport/base' require 'net/telnet' class Puppet::Util::NetworkDevice::Transport::Telnet < Puppet::Util::NetworkDevice::Transport::Base - def initialize - super + def initialize(verbose = false) + super() + @verbose = verbose end def handles_login? @@ -37,6 +38,7 @@ class Puppet::Util::NetworkDevice::Transport::Telnet < Puppet::Util::NetworkDevi end def send(line) + Puppet.debug("telnet: send #{line}") if @verbose @telnet.puts(line) end end diff --git a/lib/puppet/util/plugins.rb b/lib/puppet/util/plugins.rb index 5a559cb97..0bea67d05 100644 --- a/lib/puppet/util/plugins.rb +++ b/lib/puppet/util/plugins.rb @@ -44,7 +44,7 @@ module Puppet # # Add more places to look for plugins without adding duplicates or changing the # order of ones we've already found. - # + # def self.look_in(*paths) Paths.replace Paths | paths.flatten.collect { |path| File.expand_path(path) } end @@ -53,18 +53,18 @@ module Puppet # look_in $LOAD_PATH # - # Calling methods (hooks) on the class calls the method of the same name on + # Calling methods (hooks) on the class calls the method of the same name on # all plugins that use that hook, passing in the same arguments to each # and returning an array containing the results returned by each plugin as # an array of [plugin_name,result] pairs. - # + # def self.method_missing(hook,*args,&block) known. select { |p| p.respond_to? hook }. collect { |p| [p.name,p.send(hook,*args,&block)] } end # - # + # # attr_reader :path,:name def initialize(path) diff --git a/lib/puppet/util/posix.rb b/lib/puppet/util/posix.rb index 2ef4f2f2e..cf4c24e48 100755 --- a/lib/puppet/util/posix.rb +++ b/lib/puppet/util/posix.rb @@ -31,7 +31,7 @@ module Puppet::Util::POSIX begin return Etc.send(method, id).send(field) - rescue ArgumentError => detail + rescue NoMethodError, ArgumentError => detail # ignore it; we couldn't find the object return nil end diff --git a/lib/puppet/util/profiler.rb b/lib/puppet/util/profiler.rb new file mode 100644 index 000000000..52f8745b7 --- /dev/null +++ b/lib/puppet/util/profiler.rb @@ -0,0 +1,28 @@ +require 'benchmark' + +# A simple profiling callback system. +# +# @api private +module Puppet::Util::Profiler + require 'puppet/util/profiler/wall_clock' + require 'puppet/util/profiler/object_counts' + require 'puppet/util/profiler/none' + + NONE = Puppet::Util::Profiler::None.new + + # @returns This thread's configured profiler + def self.current + Thread.current[:profiler] || NONE + end + + # @param profiler [#profile] A profiler for the current thread + def self.current=(profiler) + Thread.current[:profiler] = profiler + end + + # @param message [String] A description of the profiled event + # @param block [Block] The segment of code to profile + def self.profile(message, &block) + current.profile(message, &block) + end +end diff --git a/lib/puppet/util/profiler/logging.rb b/lib/puppet/util/profiler/logging.rb new file mode 100644 index 000000000..c0de09e25 --- /dev/null +++ b/lib/puppet/util/profiler/logging.rb @@ -0,0 +1,47 @@ +class Puppet::Util::Profiler::Logging + def initialize(logger, identifier) + @logger = logger + @identifier = identifier + @sequence = Sequence.new + end + + def profile(description, &block) + retval = nil + @sequence.next + @sequence.down + context = start + begin + retval = yield + ensure + profile_explanation = finish(context) + @sequence.up + @logger.call("PROFILE [#{@identifier}] #{@sequence} #{description}: #{profile_explanation}") + end + retval + end + + class Sequence + INITIAL = 0 + SEPARATOR = '.' + + def initialize + @elements = [INITIAL] + end + + def next + @elements[-1] += 1 + end + + def down + @elements << INITIAL + end + + def up + @elements.pop + end + + def to_s + @elements.join(SEPARATOR) + end + end +end diff --git a/lib/puppet/util/profiler/none.rb b/lib/puppet/util/profiler/none.rb new file mode 100644 index 000000000..7d4ad716d --- /dev/null +++ b/lib/puppet/util/profiler/none.rb @@ -0,0 +1,8 @@ +# A no-op profiler. Used when there is no profiling wanted. +# +# @api private +class Puppet::Util::Profiler::None + def profile(description, &block) + yield + end +end diff --git a/lib/puppet/util/profiler/object_counts.rb b/lib/puppet/util/profiler/object_counts.rb new file mode 100644 index 000000000..3f9e5deb6 --- /dev/null +++ b/lib/puppet/util/profiler/object_counts.rb @@ -0,0 +1,17 @@ +require 'puppet/util/profiler/logging' + +class Puppet::Util::Profiler::ObjectCounts < Puppet::Util::Profiler::Logging + def start + ObjectSpace.count_objects + end + + def finish(before) + after = ObjectSpace.count_objects + + diff = before.collect do |type, count| + [type, after[type] - count] + end + + diff.sort.collect { |pair| pair.join(': ') }.join(', ') + end +end diff --git a/lib/puppet/util/profiler/wall_clock.rb b/lib/puppet/util/profiler/wall_clock.rb new file mode 100644 index 000000000..2ba47ca3e --- /dev/null +++ b/lib/puppet/util/profiler/wall_clock.rb @@ -0,0 +1,34 @@ +require 'puppet/util/profiler/logging' + +# A profiler implementation that measures the number of seconds a segment of +# code takes to execute and provides a callback with a string representation of +# the profiling information. +# +# @api private +class Puppet::Util::Profiler::WallClock < Puppet::Util::Profiler::Logging + def start + Timer.new + end + + def finish(context) + context.stop + "took #{context} seconds" + end + + class Timer + FOUR_DECIMAL_DIGITS = '%0.4f' + + def initialize + @start = Time.now + end + + def stop + @finish = Time.now + end + + def to_s + format(FOUR_DECIMAL_DIGITS, @finish - @start) + end + end +end + diff --git a/lib/puppet/util/provider_features.rb b/lib/puppet/util/provider_features.rb index f2f7a1028..d557c0380 100644 --- a/lib/puppet/util/provider_features.rb +++ b/lib/puppet/util/provider_features.rb @@ -113,7 +113,7 @@ module Puppet::Util::ProviderFeatures end # Generates a module that sets up the boolean predicate methods to test for given features. - # + # def feature_module unless defined?(@feature_module) @features ||= {} diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb index 145706f2d..6b5ad740b 100644 --- a/lib/puppet/util/rdoc/parser.rb +++ b/lib/puppet/util/rdoc/parser.rb @@ -8,18 +8,18 @@ require "rdoc/code_objects" require "puppet/util/rdoc/code_objects" require "rdoc/tokenstream" -if ::RUBY_VERSION =~ /1.9/ - require "rdoc/markup/preprocess" - require "rdoc/parser" -else +if ::RUBY_VERSION =~ /^1.8/ require "rdoc/markup/simple_markup/preprocess" require "rdoc/parsers/parserfactory" +else + require "rdoc/markup/preprocess" + require "rdoc/parser" end module RDoc class Parser - extend ParserFactory unless ::RUBY_VERSION =~ /1.9/ + extend ParserFactory if ::RUBY_VERSION =~ /^1.8/ SITE = "__site__" diff --git a/lib/puppet/util/ssl.rb b/lib/puppet/util/ssl.rb new file mode 100644 index 000000000..48faf52bb --- /dev/null +++ b/lib/puppet/util/ssl.rb @@ -0,0 +1,53 @@ +## +# SSL is a private module with class methods that help work with x.509 +# subjects. +# +# @api private +module Puppet::Util::SSL + NO_NAME = OpenSSL::X509::Name.new + + DN_PARSERS = [ + OpenSSL::X509::Name.method(:parse_rfc2253), + OpenSSL::X509::Name.method(:parse_openssl), + lambda { |dn| NO_NAME } + ] + + # Given a DN string, parse it into an OpenSSL certificate subject. This + # method will flexibly handle both OpenSSl and RFC2253 formats, as given by + # nginx and Apache, respectively. + # + # @param [String] dn the x.509 Distinguished Name (DN) string. + # + # @return [OpenSSL::X509::Name] the certificate subject + def self.subject_from_dn(dn) + if is_possibly_valid_dn?(dn) + DN_PARSERS.each do |parser| + begin + return parser.call(dn) + rescue OpenSSL::X509::NameError + end + end + else + NO_NAME + end + end + + ## + # cn_from_subject extracts the CN from the given OpenSSL certtificate + # subject. + # + # @api private + # + # @param [OpenSSL::X509::Name] subject the subject to extract the CN field from + # + # @return [String, nil] the CN, or nil if not found + def self.cn_from_subject(subject) + if subject.respond_to? :to_a + (subject.to_a.assoc('CN') || [])[1] + end + end + + def self.is_possibly_valid_dn?(dn) + dn =~ /=/ + end +end diff --git a/lib/puppet/util/subclass_loader.rb b/lib/puppet/util/subclass_loader.rb index 3fb048835..7d943745e 100644 --- a/lib/puppet/util/subclass_loader.rb +++ b/lib/puppet/util/subclass_loader.rb @@ -18,11 +18,7 @@ module Puppet::Util::SubclassLoader raise ArgumentError, "Must be a class to use SubclassLoader" unless self.is_a?(Class) @subclasses = [] - @loader = Puppet::Util::Autoload.new( - self, - - path, :wrap => false - ) + @loader = Puppet::Util::Autoload.new(self, path, :wrap => false) @subclassname = name diff --git a/lib/puppet/util/windows.rb b/lib/puppet/util/windows.rb index d3edef514..612f72b78 100644 --- a/lib/puppet/util/windows.rb +++ b/lib/puppet/util/windows.rb @@ -7,6 +7,7 @@ module Puppet::Util::Windows require 'puppet/util/windows/user' require 'puppet/util/windows/process' require 'puppet/util/windows/file' + require 'puppet/util/windows/root_certs' end require 'puppet/util/windows/registry' end diff --git a/lib/puppet/util/windows/process.rb b/lib/puppet/util/windows/process.rb index f2220ebc9..fc85119c3 100644 --- a/lib/puppet/util/windows/process.rb +++ b/lib/puppet/util/windows/process.rb @@ -1,4 +1,7 @@ require 'puppet/util/windows' +require 'windows/process' +require 'windows/handle' +require 'windows/synchronize' module Puppet::Util::Windows::Process extend ::Windows::Process diff --git a/lib/puppet/util/windows/root_certs.rb b/lib/puppet/util/windows/root_certs.rb new file mode 100644 index 000000000..4eae0a540 --- /dev/null +++ b/lib/puppet/util/windows/root_certs.rb @@ -0,0 +1,86 @@ +require 'puppet/util/windows' +require 'openssl' +require 'Win32API' +require 'windows/msvcrt/buffer' + +# Represents a collection of trusted root certificates. +# +# @api public +class Puppet::Util::Windows::RootCerts + include Enumerable + + CertOpenSystemStore = Win32API.new('crypt32', 'CertOpenSystemStore', ['L','P'], 'L') + CertEnumCertificatesInStore = Win32API.new('crypt32', 'CertEnumCertificatesInStore', ['L', 'L'], 'L') + CertCloseStore = Win32API.new('crypt32', 'CertCloseStore', ['L', 'L'], 'B') + + def initialize(roots) + @roots = roots + end + + # Enumerates each root certificate. + # @yieldparam cert [OpenSSL::X509::Certificate] each root certificate + # @api public + def each + @roots.each {|cert| yield cert} + end + + class << self + include Windows::MSVCRT::Buffer + end + + # Returns a new instance. + # @return [Puppet::Util::Windows::RootCerts] object constructed from current root certificates + def self.instance + new(self.load_certs) + end + + # Returns an array of root certificates. + # + # @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates + # @api private + def self.load_certs + certs = [] + + # This is based on a patch submitted to openssl: + # http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html + context = 0 + store = CertOpenSystemStore.call(0, "ROOT") + begin + while (context = CertEnumCertificatesInStore.call(store, context) and context != 0) + # 466 typedef struct _CERT_CONTEXT { + # 467 DWORD dwCertEncodingType; + # 468 BYTE *pbCertEncoded; + # 469 DWORD cbCertEncoded; + # 470 PCERT_INFO pCertInfo; + # 471 HCERTSTORE hCertStore; + # 472 } CERT_CONTEXT, *PCERT_CONTEXT; + + # buffer to hold struct above + ctx_buf = 0.chr * 5 * 8 + + # copy from win to ruby + memcpy(ctx_buf, context, ctx_buf.size) + + # unpack structure + arr = ctx_buf.unpack('LLLLL') + + # create buf of length cbCertEncoded + cert_buf = 0.chr * arr[2] + + # copy pbCertEncoded from win to ruby + memcpy(cert_buf, arr[1], cert_buf.length) + + # create a cert + begin + certs << OpenSSL::X509::Certificate.new(cert_buf) + rescue => detail + Puppet.warning("Failed to import root certificate: #{detail.inspect}") + end + end + ensure + CertCloseStore.call(store, 0) + end + + certs + end +end diff --git a/lib/puppet/util/windows/security.rb b/lib/puppet/util/windows/security.rb index 1f12f233f..92df1b746 100644 --- a/lib/puppet/util/windows/security.rb +++ b/lib/puppet/util/windows/security.rb @@ -69,6 +69,7 @@ require 'windows/handle' require 'windows/security' require 'windows/process' require 'windows/memory' +require 'windows/msvcrt/buffer' require 'windows/volume' module Puppet::Util::Windows::Security diff --git a/lib/puppet/version.rb b/lib/puppet/version.rb index f76c47e1c..a986277ca 100644 --- a/lib/puppet/version.rb +++ b/lib/puppet/version.rb @@ -7,7 +7,7 @@ module Puppet - PUPPETVERSION = '3.1.1' + PUPPETVERSION = '3.2.1' ## # version is a public API method intended to always provide a fast and diff --git a/spec/fixtures/integration/provider/cron/crontab/create_normal_entry b/spec/fixtures/integration/provider/cron/crontab/create_normal_entry new file mode 100644 index 000000000..e3e2c0437 --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/create_normal_entry @@ -0,0 +1,19 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + +17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command + +# Puppet Name: My daily failure +MAILTO="" +@daily /bin/false +# Puppet Name: Monthly job +SHELL=/bin/sh +MAILTO=mail@company.com +15 14 1 * * $HOME/bin/monthly +# Puppet Name: new entry +MAILTO="" +SHELL=/bin/bash +12 * * * 2 /bin/new diff --git a/spec/fixtures/integration/provider/cron/crontab/create_special_entry b/spec/fixtures/integration/provider/cron/crontab/create_special_entry new file mode 100644 index 000000000..ee25954c3 --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/create_special_entry @@ -0,0 +1,18 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + +17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command + +# Puppet Name: My daily failure +MAILTO="" +@daily /bin/false +# Puppet Name: Monthly job +SHELL=/bin/sh +MAILTO=mail@company.com +15 14 1 * * $HOME/bin/monthly +# Puppet Name: new special entry +MAILTO=bob@company.com +@reboot echo "Booted" 1>&2 diff --git a/spec/fixtures/integration/provider/cron/crontab/crontab_user1 b/spec/fixtures/integration/provider/cron/crontab/crontab_user1 new file mode 100644 index 000000000..2c7d54273 --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/crontab_user1 @@ -0,0 +1,15 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + +17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command + +# Puppet Name: My daily failure +MAILTO="" +@daily /bin/false +# Puppet Name: Monthly job +SHELL=/bin/sh +MAILTO=mail@company.com +15 14 1 * * $HOME/bin/monthly diff --git a/spec/fixtures/integration/provider/cron/crontab/crontab_user2 b/spec/fixtures/integration/provider/cron/crontab/crontab_user2 new file mode 100644 index 000000000..267e64341 --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/crontab_user2 @@ -0,0 +1,4 @@ +# HEADER: some simple +# HEADER: header +# Puppet Name: some_unrelevant job +* * * * * /bin/true diff --git a/spec/fixtures/integration/provider/cron/crontab/modify_entry b/spec/fixtures/integration/provider/cron/crontab/modify_entry new file mode 100644 index 000000000..ed06fd47b --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/modify_entry @@ -0,0 +1,13 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + +17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command + +# Puppet Name: My daily failure +MAILTO="" +@daily /bin/false +# Puppet Name: Monthly job +@monthly /usr/bin/monthly diff --git a/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input1 b/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input1 new file mode 100644 index 000000000..2c7d54273 --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input1 @@ -0,0 +1,15 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + +17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command + +# Puppet Name: My daily failure +MAILTO="" +@daily /bin/false +# Puppet Name: Monthly job +SHELL=/bin/sh +MAILTO=mail@company.com +15 14 1 * * $HOME/bin/monthly diff --git a/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input2 b/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input2 new file mode 100644 index 000000000..0b682875e --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input2 @@ -0,0 +1,6 @@ +# HEADER: some simple +# HEADER: header +# Puppet Name: some_unrelevant job +* * * * * /bin/true +# Puppet Name: My daily failure +@daily /bin/false diff --git a/spec/fixtures/integration/provider/cron/crontab/remove_named_resource b/spec/fixtures/integration/provider/cron/crontab/remove_named_resource new file mode 100644 index 000000000..e1c17169f --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/remove_named_resource @@ -0,0 +1,12 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + +17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command + +# Puppet Name: Monthly job +SHELL=/bin/sh +MAILTO=mail@company.com +15 14 1 * * $HOME/bin/monthly diff --git a/spec/fixtures/integration/provider/cron/crontab/remove_unnamed_resource b/spec/fixtures/integration/provider/cron/crontab/remove_unnamed_resource new file mode 100644 index 000000000..2dcbfe2c8 --- /dev/null +++ b/spec/fixtures/integration/provider/cron/crontab/remove_unnamed_resource @@ -0,0 +1,14 @@ +# HEADER: some simple +# HEADER: header +@daily /bin/unnamed_special_command >> /dev/null 2>&1 + +# commend with blankline above and below + + +# Puppet Name: My daily failure +MAILTO="" +@daily /bin/false +# Puppet Name: Monthly job +SHELL=/bin/sh +MAILTO=mail@company.com +15 14 1 * * $HOME/bin/monthly diff --git a/spec/fixtures/unit/pops/parser/lexer/aliastest.pp b/spec/fixtures/unit/pops/parser/lexer/aliastest.pp new file mode 100644 index 000000000..f2b61592e --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/aliastest.pp @@ -0,0 +1,16 @@ +file { "a file": + path => "/tmp/aliastest", + ensure => file +} + +file { "another": + path => "/tmp/aliastest2", + ensure => file, + require => File["a file"] +} + +file { "a third": + path => "/tmp/aliastest3", + ensure => file, + require => File["/tmp/aliastest"] +} diff --git a/spec/fixtures/unit/pops/parser/lexer/append.pp b/spec/fixtures/unit/pops/parser/lexer/append.pp new file mode 100644 index 000000000..20cbda662 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/append.pp @@ -0,0 +1,11 @@ +$var=['/tmp/file1','/tmp/file2'] + +class arraytest { + $var += ['/tmp/file3', '/tmp/file4'] + file { + $var: + content => "test" + } +} + +include arraytest diff --git a/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp b/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp new file mode 100644 index 000000000..eac9dd757 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp @@ -0,0 +1,14 @@ +# $Id$ + +define testargs($file, $mode = 755) { + file { $file: ensure => file, mode => $mode } +} + +testargs { "testingname": + file => "/tmp/argumenttest1" +} + +testargs { "testingother": + file => "/tmp/argumenttest2", + mode => 644 +} diff --git a/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp b/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp new file mode 100644 index 000000000..2d27d7d5f --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp @@ -0,0 +1,8 @@ + +$one = 1.30 +$two = 2.034e-2 + +$result = ((( $two + 2) / $one) + 4 * 5.45) - (6 << 7) + (0x800 + -9) + + +notice("result is $result == 1295.87692307692") diff --git a/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp b/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp new file mode 100644 index 000000000..a410f9553 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp @@ -0,0 +1,3 @@ +file { + ["/tmp/arraytrailingcomma1","/tmp/arraytrailingcomma2", ]: content => "tmp" +} diff --git a/spec/fixtures/unit/pops/parser/lexer/casestatement.pp b/spec/fixtures/unit/pops/parser/lexer/casestatement.pp new file mode 100644 index 000000000..66ecd72b9 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/casestatement.pp @@ -0,0 +1,65 @@ +# $Id$ + +$var = "value" + +case $var { + "nope": { + file { "/tmp/fakefile": mode => 644, ensure => file } + } + "value": { + file { "/tmp/existsfile": mode => 755, ensure => file } + } +} + +$ovar = "yayness" + +case $ovar { + "fooness": { + file { "/tmp/nostillexistsfile": mode => 644, ensure => file } + } + "booness", "yayness": { + case $var { + "nep": { + file { "/tmp/noexistsfile": mode => 644, ensure => file } + } + "value": { + file { "/tmp/existsfile2": mode => 755, ensure => file } + } + } + } +} + +case $ovar { + "fooness": { + file { "/tmp/nostillexistsfile": mode => 644, ensure => file } + } + default: { + file { "/tmp/existsfile3": mode => 755, ensure => file } + } +} + +$bool = true + +case $bool { + true: { + file { "/tmp/existsfile4": mode => 755, ensure => file } + } +} + +$yay = yay +$a = yay +$b = boo + +case $yay { + $a: { file { "/tmp/existsfile5": mode => 755, ensure => file } } + $b: { file { "/tmp/existsfile5": mode => 644, ensure => file } } + default: { file { "/tmp/existsfile5": mode => 711, ensure => file } } + +} + +$regexvar = "exists regex" +case $regexvar { + "no match": { file { "/tmp/existsfile6": mode => 644, ensure => file } } + /(.*) regex$/: { file { "/tmp/${1}file6": mode => 755, ensure => file } } + default: { file { "/tmp/existsfile6": mode => 711, ensure => file } } +} diff --git a/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp b/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp new file mode 100644 index 000000000..36619d8b9 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp @@ -0,0 +1,15 @@ +# $Id$ + +class base { + file { "/tmp/classheir1": ensure => file, mode => 755 } +} + +class sub1 inherits base { + file { "/tmp/classheir2": ensure => file, mode => 755 } +} + +class sub2 inherits base { + file { "/tmp/classheir3": ensure => file, mode => 755 } +} + +include sub1, sub2 diff --git a/spec/fixtures/unit/pops/parser/lexer/classincludes.pp b/spec/fixtures/unit/pops/parser/lexer/classincludes.pp new file mode 100644 index 000000000..bd5b44ed7 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/classincludes.pp @@ -0,0 +1,17 @@ +# $Id$ + +class base { + file { "/tmp/classincludes1": ensure => file, mode => 755 } +} + +class sub1 inherits base { + file { "/tmp/classincludes2": ensure => file, mode => 755 } +} + +class sub2 inherits base { + file { "/tmp/classincludes3": ensure => file, mode => 755 } +} + +$sub = "sub2" + +include sub1, $sub diff --git a/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp b/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp new file mode 100644 index 000000000..580333369 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp @@ -0,0 +1,11 @@ +# $Id$ + +define mytype { + file { "/tmp/classtest": ensure => file, mode => 755 } +} + +class testing { + mytype { "componentname": } +} + +include testing diff --git a/spec/fixtures/unit/pops/parser/lexer/collection.pp b/spec/fixtures/unit/pops/parser/lexer/collection.pp new file mode 100644 index 000000000..bc29510a9 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/collection.pp @@ -0,0 +1,10 @@ +class one { + @file { "/tmp/colltest1": content => "one" } + @file { "/tmp/colltest2": content => "two" } +} + +class two { + File <| content == "one" |> +} + +include one, two diff --git a/spec/fixtures/unit/pops/parser/lexer/collection_override.pp b/spec/fixtures/unit/pops/parser/lexer/collection_override.pp new file mode 100644 index 000000000..b1b39ab16 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/collection_override.pp @@ -0,0 +1,8 @@ +@file { + "/tmp/collection": + content => "whatever" +} + +File<| |> { + mode => 0600 +} diff --git a/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp b/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp new file mode 100644 index 000000000..3c21468b0 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp @@ -0,0 +1,20 @@ +define test($name) { + file {"/tmp/collection_within_virtual_definitions1_$name.txt": + content => "File name $name\n" + } + Test2 <||> +} + +define test2() { + file {"/tmp/collection_within_virtual_definitions2_$name.txt": + content => "This is a test\n" + } +} + +node default { + @test {"foo": + name => "foo" + } + @test2 {"foo2": } + Test <||> +} diff --git a/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp b/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp new file mode 100644 index 000000000..7d9f0c2c1 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp @@ -0,0 +1,11 @@ +file { "/tmp/component1": + ensure => file +} + +define thing { + file { $name: ensure => file } +} + +thing { "/tmp/component2": + require => File["/tmp/component1"] +} diff --git a/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp b/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp new file mode 100644 index 000000000..a61d2050c --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp @@ -0,0 +1,8 @@ +define testfile($mode) { + file { $name: mode => $mode, ensure => present } +} + +testfile { "/tmp/testing_component_requires2": mode => 755 } + +file { "/tmp/testing_component_requires1": mode => 755, ensure => present, + require => Testfile["/tmp/testing_component_requires2"] } diff --git a/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp b/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp new file mode 100644 index 000000000..249e6334d --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp @@ -0,0 +1,23 @@ +# $Id$ + +class base { + file { "/tmp/deepclassheir1": ensure => file, mode => 755 } +} + +class sub1 inherits base { + file { "/tmp/deepclassheir2": ensure => file, mode => 755 } +} + +class sub2 inherits sub1 { + file { "/tmp/deepclassheir3": ensure => file, mode => 755 } +} + +class sub3 inherits sub2 { + file { "/tmp/deepclassheir4": ensure => file, mode => 755 } +} + +class sub4 inherits sub3 { + file { "/tmp/deepclassheir5": ensure => file, mode => 755 } +} + +include sub4 diff --git a/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp b/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp new file mode 100644 index 000000000..c68b139e3 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp @@ -0,0 +1,17 @@ +# $Id$ + +$file = "/tmp/defineoverrides1" + +define myfile($mode) { + file { $name: ensure => file, mode => $mode } +} + +class base { + myfile { $file: mode => 644 } +} + +class sub inherits base { + Myfile[$file] { mode => 755, } # test the end-comma +} + +include sub diff --git a/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp b/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp new file mode 100644 index 000000000..48047e748 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp @@ -0,0 +1,9 @@ +# $Id$ + +define component { +} + +class testing { +} + +include testing diff --git a/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp b/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp new file mode 100644 index 000000000..847a30d18 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp @@ -0,0 +1,3 @@ +exec { "touch /tmp/emptyexectest": + path => "/usr/bin:/bin" +} diff --git a/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp b/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp new file mode 100644 index 000000000..598b486ac --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp @@ -0,0 +1,9 @@ + +if false { +} else { + # nothing here +} + +if true { + # still nothing +} diff --git a/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp b/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp new file mode 100644 index 000000000..2143b79a7 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp @@ -0,0 +1,3 @@ +$value = false + +file { "/tmp/falsevalues$value": ensure => file } diff --git a/spec/fixtures/unit/pops/parser/lexer/filecreate.pp b/spec/fixtures/unit/pops/parser/lexer/filecreate.pp new file mode 100644 index 000000000..d7972c234 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/filecreate.pp @@ -0,0 +1,11 @@ +# $Id$ + +file { + "/tmp/createatest": ensure => file, mode => 755; + "/tmp/createbtest": ensure => file, mode => 755 +} + +file { + "/tmp/createctest": ensure => file; + "/tmp/createdtest": ensure => file; +} diff --git a/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp b/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp new file mode 100644 index 000000000..ddb0675a9 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp @@ -0,0 +1,5 @@ +define one::two($ensure) { + file { "/tmp/fqdefinition": ensure => $ensure } +} + +one::two { "/tmp/fqdefinition": ensure => file } diff --git a/spec/fixtures/unit/pops/parser/lexer/fqparents.pp b/spec/fixtures/unit/pops/parser/lexer/fqparents.pp new file mode 100644 index 000000000..ee2f65423 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/fqparents.pp @@ -0,0 +1,11 @@ +class base { + class one { + file { "/tmp/fqparent1": ensure => file } + } +} + +class two::three inherits base::one { + file { "/tmp/fqparent2": ensure => file } +} + +include two::three diff --git a/spec/fixtures/unit/pops/parser/lexer/funccomma.pp b/spec/fixtures/unit/pops/parser/lexer/funccomma.pp new file mode 100644 index 000000000..32e34f92e --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/funccomma.pp @@ -0,0 +1,5 @@ +@file { + ["/tmp/funccomma1","/tmp/funccomma2"]: content => "1" +} + +realize( File["/tmp/funccomma1"], File["/tmp/funccomma2"] , ) diff --git a/spec/fixtures/unit/pops/parser/lexer/hash.pp b/spec/fixtures/unit/pops/parser/lexer/hash.pp new file mode 100644 index 000000000..d33249872 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/hash.pp @@ -0,0 +1,33 @@ + +$hash = { "file" => "/tmp/myhashfile1" } + +file { + $hash["file"]: + ensure => file, content => "content"; +} + +$hash2 = { "a" => { key => "/tmp/myhashfile2" }} + +file { + $hash2["a"][key]: + ensure => file, content => "content"; +} + +define test($a = { "b" => "c" }) { + file { + $a["b"]: + ensure => file, content => "content" + } +} + +test { + "test": + a => { "b" => "/tmp/myhashfile3" } +} + +$hash3 = { mykey => "/tmp/myhashfile4" } +$key = "mykey" + +file { + $hash3[$key]: ensure => file, content => "content" +} diff --git a/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp b/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp new file mode 100644 index 000000000..29a637291 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp @@ -0,0 +1,12 @@ +$one = 1 +$two = 2 + +if ($one < $two) and (($two < 3) or ($two == 2)) { + notice("True!") +} + +if "test regex" =~ /(.*) regex/ { + file { + "/tmp/${1}iftest": ensure => file, mode => 0755 + } +} diff --git a/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp b/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp new file mode 100644 index 000000000..6f34cb29c --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp @@ -0,0 +1,15 @@ +# $Id$ + +$files = ["/tmp/iterationatest", "/tmp/iterationbtest"] + +file { $files: ensure => file, mode => 755 } + +file { ["/tmp/iterationctest", "/tmp/iterationdtest"]: + ensure => file, + mode => 755 +} + +file { + ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => 755; + ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => 755; +} diff --git a/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp b/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp new file mode 100644 index 000000000..f9819c020 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp @@ -0,0 +1,10 @@ + +/* +file { + "/tmp/multilinecomments": content => "pouet" +} +*/ + +/* and another one for #2333, the whitespace after the +end comment is here on purpose */ + diff --git a/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp b/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp new file mode 100644 index 000000000..ae02edc38 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp @@ -0,0 +1,9 @@ +class one { + file { "/tmp/multipleclassone": content => "one" } +} + +class one { + file { "/tmp/multipleclasstwo": content => "two" } +} + +include one diff --git a/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp b/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp new file mode 100644 index 000000000..2f9b3c2e8 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp @@ -0,0 +1,7 @@ +# $Id$ + +file { + "/tmp/multipleinstancesa": ensure => file, mode => 755; + "/tmp/multipleinstancesb": ensure => file, mode => 755; + "/tmp/multipleinstancesc": ensure => file, mode => 755; +} diff --git a/spec/fixtures/unit/pops/parser/lexer/multisubs.pp b/spec/fixtures/unit/pops/parser/lexer/multisubs.pp new file mode 100644 index 000000000..bcec69e2a --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/multisubs.pp @@ -0,0 +1,13 @@ +class base { + file { "/tmp/multisubtest": content => "base", mode => 644 } +} + +class sub1 inherits base { + File["/tmp/multisubtest"] { mode => 755 } +} + +class sub2 inherits base { + File["/tmp/multisubtest"] { content => sub2 } +} + +include sub1, sub2 diff --git a/spec/fixtures/unit/pops/parser/lexer/namevartest.pp b/spec/fixtures/unit/pops/parser/lexer/namevartest.pp new file mode 100644 index 000000000..dbee1c356 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/namevartest.pp @@ -0,0 +1,9 @@ +define filetest($mode, $ensure = file) { + file { $name: + mode => $mode, + ensure => $ensure + } +} + +filetest { "/tmp/testfiletest": mode => 644} +filetest { "/tmp/testdirtest": mode => 755, ensure => directory} diff --git a/spec/fixtures/unit/pops/parser/lexer/scopetest.pp b/spec/fixtures/unit/pops/parser/lexer/scopetest.pp new file mode 100644 index 000000000..331491766 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/scopetest.pp @@ -0,0 +1,13 @@ + +$mode = 640 + +define thing { + file { "/tmp/$name": ensure => file, mode => $mode } +} + +class testing { + $mode = 755 + thing {scopetest: } +} + +include testing diff --git a/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp b/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp new file mode 100644 index 000000000..d80d26c36 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp @@ -0,0 +1,49 @@ +$value1 = "" +$value2 = true +$value3 = false +$value4 = yay + +$test = "yay" + +$mode1 = $value1 ? { + "" => 755, + default => 644 +} + +$mode2 = $value2 ? { + true => 755, + default => 644 +} + +$mode3 = $value3 ? { + false => 755, + default => 644 +} + +$mode4 = $value4 ? { + $test => 755, + default => 644 +} + +$mode5 = yay ? { + $test => 755, + default => 644 +} + +$mode6 = $mode5 ? { + 755 => 755 +} + +$mode7 = "test regex" ? { + /regex$/ => 755, + default => 644 +} + + +file { "/tmp/selectorvalues1": ensure => file, mode => $mode1 } +file { "/tmp/selectorvalues2": ensure => file, mode => $mode2 } +file { "/tmp/selectorvalues3": ensure => file, mode => $mode3 } +file { "/tmp/selectorvalues4": ensure => file, mode => $mode4 } +file { "/tmp/selectorvalues5": ensure => file, mode => $mode5 } +file { "/tmp/selectorvalues6": ensure => file, mode => $mode6 } +file { "/tmp/selectorvalues7": ensure => file, mode => $mode7 } diff --git a/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp b/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp new file mode 100644 index 000000000..63d199a68 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp @@ -0,0 +1,5 @@ +# $Id$ + +File { mode => 755 } + +file { "/tmp/defaulttest": ensure => file } diff --git a/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp b/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp new file mode 100644 index 000000000..8b9bc7292 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp @@ -0,0 +1,38 @@ +# $Id$ + +$var = "value" + +file { "/tmp/snippetselectatest": + ensure => file, + mode => $var ? { + nottrue => 641, + value => 755 + } +} + +file { "/tmp/snippetselectbtest": + ensure => file, + mode => $var ? { + nottrue => 644, + default => 755 + } +} + +$othervar = "complex value" + +file { "/tmp/snippetselectctest": + ensure => file, + mode => $othervar ? { + "complex value" => 755, + default => 644 + } +} +$anothervar = Yayness + +file { "/tmp/snippetselectdtest": + ensure => file, + mode => $anothervar ? { + Yayness => 755, + default => 644 + } +} diff --git a/spec/fixtures/unit/pops/parser/lexer/singleary.pp b/spec/fixtures/unit/pops/parser/lexer/singleary.pp new file mode 100644 index 000000000..9ce56dd89 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/singleary.pp @@ -0,0 +1,19 @@ +# $Id$ + +file { "/tmp/singleary1": + ensure => file +} + +file { "/tmp/singleary2": + ensure => file +} + +file { "/tmp/singleary3": + ensure => file, + require => [File["/tmp/singleary1"], File["/tmp/singleary2"]] +} + +file { "/tmp/singleary4": + ensure => file, + require => [File["/tmp/singleary1"]] +} diff --git a/spec/fixtures/unit/pops/parser/lexer/singlequote.pp b/spec/fixtures/unit/pops/parser/lexer/singlequote.pp new file mode 100644 index 000000000..dc876a2f8 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/singlequote.pp @@ -0,0 +1,11 @@ +# $Id$ + +file { "/tmp/singlequote1": + ensure => file, + content => 'a $quote' +} + +file { "/tmp/singlequote2": + ensure => file, + content => 'some "\yayness\"' +} diff --git a/spec/fixtures/unit/pops/parser/lexer/singleselector.pp b/spec/fixtures/unit/pops/parser/lexer/singleselector.pp new file mode 100644 index 000000000..520a14017 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/singleselector.pp @@ -0,0 +1,22 @@ +$value1 = "" +$value2 = true +$value3 = false +$value4 = yay + +$test = "yay" + +$mode1 = $value1 ? { + "" => 755 +} + +$mode2 = $value2 ? { + true => 755 +} + +$mode3 = $value3 ? { + default => 755 +} + +file { "/tmp/singleselector1": ensure => file, mode => $mode1 } +file { "/tmp/singleselector2": ensure => file, mode => $mode2 } +file { "/tmp/singleselector3": ensure => file, mode => $mode3 } diff --git a/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp b/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp new file mode 100755 index 000000000..10f1d75ed --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp @@ -0,0 +1,11 @@ +#!/usr/bin/env puppet + +class one::fake { + file { "/tmp/subclass_name_duplication1": ensure => present } +} + +class two::fake { + file { "/tmp/subclass_name_duplication2": ensure => present } +} + +include one::fake, two::fake diff --git a/spec/fixtures/unit/pops/parser/lexer/tag.pp b/spec/fixtures/unit/pops/parser/lexer/tag.pp new file mode 100644 index 000000000..e6e770dd9 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/tag.pp @@ -0,0 +1,9 @@ +# $Id$ + +$variable = value + +tag yayness, rahness + +tag booness, $variable + +file { "/tmp/settestingness": ensure => file } diff --git a/spec/fixtures/unit/pops/parser/lexer/tagged.pp b/spec/fixtures/unit/pops/parser/lexer/tagged.pp new file mode 100644 index 000000000..7bf90a645 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/tagged.pp @@ -0,0 +1,35 @@ +# $Id$ + +tag testing +tag(funtest) + +class tagdefine { + $path = tagged(tagdefine) ? { + true => "true", false => "false" + } + + file { "/tmp/taggeddefine$path": ensure => file } +} + +include tagdefine + +$yayness = tagged(yayness) ? { + true => "true", false => "false" +} + +$funtest = tagged(testing) ? { + true => "true", false => "false" +} + +$both = tagged(testing, yayness) ? { + true => "true", false => "false" +} + +$bothtrue = tagged(testing, testing) ? { + true => "true", false => "false" +} + +file { "/tmp/taggedyayness$yayness": ensure => file } +file { "/tmp/taggedtesting$funtest": ensure => file } +file { "/tmp/taggedboth$both": ensure => file } +file { "/tmp/taggedbothtrue$bothtrue": ensure => file } diff --git a/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp b/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp new file mode 100644 index 000000000..a29406b84 --- /dev/null +++ b/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp @@ -0,0 +1,14 @@ +class one { + @file { "/tmp/virtualtest1": content => "one" } + @file { "/tmp/virtualtest2": content => "two" } + @file { "/tmp/virtualtest3": content => "three" } + @file { "/tmp/virtualtest4": content => "four" } +} + +class two { + File <| content == "one" |> + realize File["/tmp/virtualtest2"] + realize(File["/tmp/virtualtest3"], File["/tmp/virtualtest4"]) +} + +include one, two diff --git a/spec/fixtures/unit/provider/cron/crontab/single_line.yaml b/spec/fixtures/unit/provider/cron/crontab/single_line.yaml index 56dd6ad49..da2853eab 100644 --- a/spec/fixtures/unit/provider/cron/crontab/single_line.yaml +++ b/spec/fixtures/unit/provider/cron/crontab/single_line.yaml @@ -9,7 +9,7 @@ :record: :special: hourly :command: /bin/date - :record_type: :freebsd_special + :record_type: :crontab :long_name: :text: "# Puppet Name: long_name" :record: @@ -63,7 +63,7 @@ :record: :special: daily :command: /bin/echo testing - :record_type: :freebsd_special + :record_type: :crontab :tabs: :text: !binary | CQ== @@ -145,7 +145,7 @@ :record: :special: hourly :command: /bin/date - :record_type: :freebsd_special + :record_type: :crontab :long_name: :text: "# Puppet Name: long_name" :record: @@ -199,7 +199,7 @@ :record: :special: daily :command: /bin/echo testing - :record_type: :freebsd_special + :record_type: :crontab :tabs: :text: !binary | CQ== diff --git a/spec/fixtures/unit/provider/cron/crontab/vixie_header.txt b/spec/fixtures/unit/provider/cron/crontab/vixie_header.txt new file mode 100644 index 000000000..7ccfc378e --- /dev/null +++ b/spec/fixtures/unit/provider/cron/crontab/vixie_header.txt @@ -0,0 +1,3 @@ +# DO NOT EDIT THIS FILE - edit the master and reinstall. +# (- installed on Thu Apr 12 12:16:01 2007) +# (Cron version V5.0 -- $Id: crontab.c,v 1.12 2004/01/23 18:56:42 vixie Exp $) diff --git a/spec/fixtures/unit/provider/cron/parsed/managed b/spec/fixtures/unit/provider/cron/parsed/managed new file mode 100644 index 000000000..c48d20a6c --- /dev/null +++ b/spec/fixtures/unit/provider/cron/parsed/managed @@ -0,0 +1,6 @@ +# Puppet Name: real_job +* * * * * /bin/true +# Puppet Name: complex_job +MAILTO=foo@example.com +SHELL=/bin/sh +@reboot /bin/true >> /dev/null 2>&1 diff --git a/spec/fixtures/unit/provider/cron/parsed/simple b/spec/fixtures/unit/provider/cron/parsed/simple new file mode 100644 index 000000000..477553ea2 --- /dev/null +++ b/spec/fixtures/unit/provider/cron/parsed/simple @@ -0,0 +1,9 @@ +# use /bin/sh to run commands, no matter what /etc/passwd says +SHELL=/bin/sh +# mail any output to `paul', no matter whose crontab this is +MAILTO=paul +# +# run five minutes after midnight, every day +5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 +# run at 2:15pm on the first of every month -- output mailed to paul +15 14 1 * * $HOME/bin/monthly diff --git a/spec/fixtures/unit/provider/parsedfile/simple.txt b/spec/fixtures/unit/provider/parsedfile/simple.txt new file mode 100644 index 000000000..a5437a154 --- /dev/null +++ b/spec/fixtures/unit/provider/parsedfile/simple.txt @@ -0,0 +1,4 @@ +# This is a sample fixture for the parsedfile provider. +# HEADER As added by software from a third party. +# Another inconspicuous comment. +A generic content line with: a value. diff --git a/spec/fixtures/unit/provider/service/systemd/list_units b/spec/fixtures/unit/provider/service/systemd/list_units new file mode 100644 index 000000000..4998a08e4 --- /dev/null +++ b/spec/fixtures/unit/provider/service/systemd/list_units @@ -0,0 +1,18 @@ +UNIT LOAD ACTIVE SUB DESCRIPTION +auditd.service loaded active running Security Auditing Service +crond.service loaded active running Command Scheduler +dbus.service loaded active running D-Bus System Message Bus +display-manager.service error inactive dead display-manager.service +ebtables.service loaded inactive dead SYSV: Ethernet Bridge filtering tables +fedora-readonly.service loaded active exited Configure read-only root support +initrd-switch-root.service loaded inactive dead Switch Root +ip6tables.service error inactive dead ip6tables.service +puppet.service loaded inactive dead SYSV: Enables periodic system configuration checks through puppet. +sshd.service loaded failed failed OpenSSH server daemon + +LOAD = Reflects whether the unit definition was properly loaded. +ACTIVE = The high-level unit activation state, i.e. generalization of SUB. +SUB = The low-level unit activation state, values depend on unit type. + +155 loaded units listed. +To show all installed unit files use 'systemctl list-unit-files'. diff --git a/spec/integration/parser/collector_spec.rb b/spec/integration/parser/collector_spec.rb index cb210b372..49ce74583 100755 --- a/spec/integration/parser/collector_spec.rb +++ b/spec/integration/parser/collector_spec.rb @@ -64,7 +64,7 @@ describe Puppet::Parser::Collector do expect_the_message_to_be(["the message", "different message"], <<-MANIFEST) @notify { "testing": message => "different message", withpath => true } @notify { "other": message => "the message" } - @notify { "yet another": message => "the message", withpath => true } + @notify { "yet another": message => "the message", withpath => true } Notify <| (title == "testing" or message == "the message") and withpath == true |> MANIFEST diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb index 9a4deb23e..01e5c085f 100755 --- a/spec/integration/parser/compiler_spec.rb +++ b/spec/integration/parser/compiler_spec.rb @@ -1,7 +1,8 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'puppet/parser/parser_factory' -describe Puppet::Parser::Compiler do +describe "Puppet::Parser::Compiler" do before :each do @node = Puppet::Node.new "testnode" @@ -13,297 +14,321 @@ describe Puppet::Parser::Compiler do Puppet.settings.clear end - it "should be able to determine the configuration version from a local version control repository" do - pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do - # This should always work, because we should always be - # in the puppet repo when we run this. - version = %x{git rev-parse HEAD}.chomp + # shared because tests are invoked both for classic and future parser + # + shared_examples_for "the compiler" do + it "should be able to determine the configuration version from a local version control repository" do + pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do + # This should always work, because we should always be + # in the puppet repo when we run this. + version = %x{git rev-parse HEAD}.chomp - Puppet.settings[:config_version] = 'git rev-parse HEAD' + Puppet.settings[:config_version] = 'git rev-parse HEAD' - @parser = Puppet::Parser::Parser.new "development" - @compiler = Puppet::Parser::Compiler.new(@node) + @parser = Puppet::Parser::ParserFactory.parser "development" + @compiler = Puppet::Parser::Compiler.new(@node) - @compiler.catalog.version.should == version + @compiler.catalog.version.should == version + end end - end - - it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do - Puppet[:code] = <<-PP - class foo - { - notify { foo_notify: } - include bar - } - class bar - { - notify { bar_notify: } - } - PP - - @node.stubs(:classes).returns(['foo', 'bar']) - - catalog = Puppet::Parser::Compiler.compile(@node) - - catalog.resource("Notify[foo_notify]").should_not be_nil - catalog.resource("Notify[bar_notify]").should_not be_nil - end - describe "when resolving class references" do - it "should favor local scope, even if there's an included class in topscope" do + it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do Puppet[:code] = <<-PP - class experiment { - class baz { - } - notify {"x" : require => Class[Baz] } + class foo + { + notify { foo_notify: } + include bar } - class baz { + class bar + { + notify { bar_notify: } } - include baz - include experiment - include experiment::baz PP - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + @node.stubs(:classes).returns(['foo', 'bar']) - notify_resource = catalog.resource( "Notify[x]" ) + catalog = Puppet::Parser::Compiler.compile(@node) - notify_resource[:require].title.should == "Experiment::Baz" + catalog.resource("Notify[foo_notify]").should_not be_nil + catalog.resource("Notify[bar_notify]").should_not be_nil end - it "should favor local scope, even if there's an unincluded class in topscope" do - Puppet[:code] = <<-PP - class experiment { + describe "when resolving class references" do + it "should favor local scope, even if there's an included class in topscope" do + Puppet[:code] = <<-PP + class experiment { + class baz { + } + notify {"x" : require => Class[Baz] } + } class baz { } - notify {"x" : require => Class[Baz] } - } - class baz { - } - include experiment - include experiment::baz - PP + include baz + include experiment + include experiment::baz + PP - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - notify_resource = catalog.resource( "Notify[x]" ) + notify_resource = catalog.resource( "Notify[x]" ) - notify_resource[:require].title.should == "Experiment::Baz" - end - end - describe "(ticket #13349) when explicitly specifying top scope" do - ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| - describe "with #{include}" do - it "should find the top level class" do - Puppet[:code] = <<-MANIFEST - class { 'foo::test': } - class foo::test { - #{include} - } - class bar::baz { - notify { 'good!': } - } - class foo::bar::baz { - notify { 'bad!': } + notify_resource[:require].title.should == "Experiment::Baz" + end + + it "should favor local scope, even if there's an unincluded class in topscope" do + Puppet[:code] = <<-PP + class experiment { + class baz { } - MANIFEST + notify {"x" : require => Class[Baz] } + } + class baz { + } + include experiment + include experiment::baz + PP - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - catalog.resource("Class[Bar::Baz]").should_not be_nil - catalog.resource("Notify[good!]").should_not be_nil - catalog.resource("Class[Foo::Bar::Baz]").should be_nil - catalog.resource("Notify[bad!]").should be_nil + notify_resource = catalog.resource( "Notify[x]" ) + + notify_resource[:require].title.should == "Experiment::Baz" + end + end + describe "(ticket #13349) when explicitly specifying top scope" do + ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| + describe "with #{include}" do + it "should find the top level class" do + Puppet[:code] = <<-MANIFEST + class { 'foo::test': } + class foo::test { + #{include} + } + class bar::baz { + notify { 'good!': } + } + class foo::bar::baz { + notify { 'bad!': } + } + MANIFEST + + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + + catalog.resource("Class[Bar::Baz]").should_not be_nil + catalog.resource("Notify[good!]").should_not be_nil + catalog.resource("Class[Foo::Bar::Baz]").should be_nil + catalog.resource("Notify[bad!]").should be_nil + end end end end - end - it "should recompute the version after input files are re-parsed" do - Puppet[:code] = 'class foo { }' - Time.stubs(:now).returns(1) - node = Puppet::Node.new('mynode') - Puppet::Parser::Compiler.compile(node).version.should == 1 - Time.stubs(:now).returns(2) - Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change - Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change - Puppet::Parser::Compiler.compile(node).version.should == 2 - end + it "should recompute the version after input files are re-parsed" do + Puppet[:code] = 'class foo { }' + Time.stubs(:now).returns(1) + node = Puppet::Node.new('mynode') + Puppet::Parser::Compiler.compile(node).version.should == 1 + Time.stubs(:now).returns(2) + Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change + Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change + Puppet::Parser::Compiler.compile(node).version.should == 2 + end - ['class', 'define', 'node'].each do |thing| - it "should not allow #{thing} inside evaluated conditional constructs" do - Puppet[:code] = <<-PP - if true { - #{thing} foo { + ['class', 'define', 'node'].each do |thing| + it "should not allow #{thing} inside evaluated conditional constructs" do + Puppet[:code] = <<-PP + if true { + #{thing} foo { + } + notify { decoy: } } - notify { decoy: } - } - PP + PP - begin - Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - raise "compilation should have raised Puppet::Error" - rescue Puppet::Error => e - e.message.should =~ /at line 2/ + begin + Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + raise "compilation should have raised Puppet::Error" + rescue Puppet::Error => e + e.message.should =~ /at line 2/ + end end end - end - it "should not allow classes inside unevaluated conditional constructs" do - Puppet[:code] = <<-PP - if false { - class foo { + it "should not allow classes inside unevaluated conditional constructs" do + Puppet[:code] = <<-PP + if false { + class foo { + } } - } - PP - - lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) - end + PP - describe "when defining relationships" do - def extract_name(ref) - ref.sub(/File\[(\w+)\]/, '\1') + lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) end - let(:node) { Puppet::Node.new('mynode') } - let(:code) do - <<-MANIFEST - file { [a,b,c]: - mode => 0644, - } - file { [d,e]: - mode => 0755, - } - MANIFEST - end - let(:expected_relationships) { [] } - let(:expected_subscriptions) { [] } + describe "when defining relationships" do + def extract_name(ref) + ref.sub(/File\[(\w+)\]/, '\1') + end - before :each do - Puppet[:code] = code - end + let(:node) { Puppet::Node.new('mynode') } + let(:code) do + <<-MANIFEST + file { [a,b,c]: + mode => 0644, + } + file { [d,e]: + mode => 0755, + } + MANIFEST + end + let(:expected_relationships) { [] } + let(:expected_subscriptions) { [] } - after :each do - catalog = described_class.compile(node) + before :each do + Puppet[:code] = code + end - resources = catalog.resources.select { |res| res.type == 'File' } + after :each do + catalog = Puppet::Parser::Compiler.compile(node) - actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| - resources.map do |res| - dependents = Array(res[relation]) - dependents.map { |ref| [res.title, extract_name(ref)] } - end.inject(&:concat) - end + resources = catalog.resources.select { |res| res.type == 'File' } - actual_relationships.should =~ expected_relationships - actual_subscriptions.should =~ expected_subscriptions - end + actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| + resources.map do |res| + dependents = Array(res[relation]) + dependents.map { |ref| [res.title, extract_name(ref)] } + end.inject(&:concat) + end - it "should create a relationship" do - code << "File[a] -> File[b]" + actual_relationships.should =~ expected_relationships + actual_subscriptions.should =~ expected_subscriptions + end - expected_relationships << ['a','b'] - end + it "should create a relationship" do + code << "File[a] -> File[b]" - it "should create a subscription" do - code << "File[a] ~> File[b]" + expected_relationships << ['a','b'] + end - expected_subscriptions << ['a', 'b'] - end + it "should create a subscription" do + code << "File[a] ~> File[b]" - it "should create relationships using title arrays" do - code << "File[a,b] -> File[c,d]" + expected_subscriptions << ['a', 'b'] + end - expected_relationships.concat [ - ['a', 'c'], - ['b', 'c'], - ['a', 'd'], - ['b', 'd'], - ] - end + it "should create relationships using title arrays" do + code << "File[a,b] -> File[c,d]" - it "should create relationships using collection expressions" do - code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" - - expected_relationships.concat [ - ['a', 'd'], - ['b', 'd'], - ['c', 'd'], - ['a', 'e'], - ['b', 'e'], - ['c', 'e'], - ] - end + expected_relationships.concat [ + ['a', 'c'], + ['b', 'c'], + ['a', 'd'], + ['b', 'd'], + ] + end - it "should create relationships using resource names" do - code << "'File[a]' -> 'File[b]'" + it "should create relationships using collection expressions" do + code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" + + expected_relationships.concat [ + ['a', 'd'], + ['b', 'd'], + ['c', 'd'], + ['a', 'e'], + ['b', 'e'], + ['c', 'e'], + ] + end - expected_relationships << ['a', 'b'] - end + it "should create relationships using resource names" do + code << "'File[a]' -> 'File[b]'" - it "should create relationships using variables" do - code << <<-MANIFEST - $var = File[a] - $var -> File[b] - MANIFEST + expected_relationships << ['a', 'b'] + end - expected_relationships << ['a', 'b'] - end + it "should create relationships using variables" do + code << <<-MANIFEST + $var = File[a] + $var -> File[b] + MANIFEST - it "should create relationships using case statements" do - code << <<-MANIFEST - $var = 10 - case $var { - 10: { - file { s1: } - } - 12: { - file { s2: } - } - } - -> - case $var + 2 { - 10: { - file { t1: } + expected_relationships << ['a', 'b'] + end + + it "should create relationships using case statements" do + code << <<-MANIFEST + $var = 10 + case $var { + 10: { + file { s1: } + } + 12: { + file { s2: } + } } - 12: { - file { t2: } + -> + case $var + 2 { + 10: { + file { t1: } + } + 12: { + file { t2: } + } } - } - MANIFEST + MANIFEST - expected_relationships << ['s1', 't2'] - end + expected_relationships << ['s1', 't2'] + end - it "should create relationships using array members" do - code << <<-MANIFEST - $var = [ [ [ File[a], File[b] ] ] ] - $var[0][0][0] -> $var[0][0][1] - MANIFEST + it "should create relationships using array members" do + code << <<-MANIFEST + $var = [ [ [ File[a], File[b] ] ] ] + $var[0][0][0] -> $var[0][0][1] + MANIFEST - expected_relationships << ['a', 'b'] - end + expected_relationships << ['a', 'b'] + end - it "should create relationships using hash members" do - code << <<-MANIFEST - $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} - $var[foo][bar][source] -> $var[foo][bar][target] - MANIFEST + it "should create relationships using hash members" do + code << <<-MANIFEST + $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} + $var[foo][bar][source] -> $var[foo][bar][target] + MANIFEST - expected_relationships << ['a', 'b'] - end + expected_relationships << ['a', 'b'] + end - it "should create relationships using resource declarations" do - code << "file { l: } -> file { r: }" + it "should create relationships using resource declarations" do + code << "file { l: } -> file { r: }" - expected_relationships << ['l', 'r'] + expected_relationships << ['l', 'r'] + end + + it "should chain relationships" do + code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" + + expected_relationships << ['a', 'b'] << ['d', 'c'] + expected_subscriptions << ['b', 'c'] << ['e', 'd'] + end end + end - it "should chain relationships" do - code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" + describe 'using classic parser' do + before :each do + Puppet[:parser] = 'current' + end + it_behaves_like 'the compiler' do + end + end - expected_relationships << ['a', 'b'] << ['d', 'c'] - expected_subscriptions << ['b', 'c'] << ['e', 'd'] + describe 'using future parser' do + # have absolutely no clue to why this is needed - if not required here (even if required by used classes) + # the tests will fail with error that rgen/ecore/ruby_to_ecore cannot be found... + # TODO: Solve this mystery ! + require 'rgen/metamodel_builder' + + before :each do + Puppet[:parser] = 'future' end + it_behaves_like 'the compiler' end end diff --git a/spec/integration/parser/parser_spec.rb b/spec/integration/parser/parser_spec.rb index 427430323..759643a0f 100755 --- a/spec/integration/parser/parser_spec.rb +++ b/spec/integration/parser/parser_spec.rb @@ -1,11 +1,12 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'puppet/parser/parser_factory' -describe Puppet::Parser::Parser do +describe "Puppet::Parser::Parser" do module ParseMatcher class ParseAs def initialize(klass) - @parser = Puppet::Parser::Parser.new "development" + @parser = Puppet::Parser::ParserFactory.parser("development") @class = klass end @@ -38,7 +39,7 @@ describe Puppet::Parser::Parser do class ParseWith def initialize(block) - @parser = Puppet::Parser::Parser.new "development" + @parser = Puppet::Parser::ParserFactory.parser("development") @block = block end @@ -74,76 +75,193 @@ describe Puppet::Parser::Parser do before :each do @resource_type_collection = Puppet::Resource::TypeCollection.new("env") - @parser = Puppet::Parser::Parser.new "development" - end - - describe "when parsing comments before statement" do - it "should associate the documentation to the statement AST node" do - ast = @parser.parse(""" - # comment - class test {} - """) + @parser = Puppet::Parser::ParserFactory.parser("development") - ast.code[0].should be_a(Puppet::Parser::AST::Hostclass) - ast.code[0].name.should == 'test' - ast.code[0].instantiate('')[0].doc.should == "comment\n" - end +# @parser = Puppet::Parser::Parser.new "development" end + shared_examples_for 'a puppet parser' do + describe "when parsing comments before statement" do + it "should associate the documentation to the statement AST node" do + if Puppet[:parser] == 'future' + pending "egrammar does not yet process comments" + end + ast = @parser.parse(""" + # comment + class test {} + """) - describe "when parsing" do - it "should be able to parse normal left to right relationships" do - "Notify[foo] -> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) + ast.code[0].should be_a(Puppet::Parser::AST::Hostclass) + ast.code[0].name.should == 'test' + ast.code[0].instantiate('')[0].doc.should == "comment\n" + end end - it "should be able to parse right to left relationships" do - "Notify[foo] <- Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) - end + describe "when parsing" do + it "should be able to parse normal left to right relationships" do + "Notify[foo] -> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) + end - it "should be able to parse normal left to right subscriptions" do - "Notify[foo] ~> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) - end + it "should be able to parse right to left relationships" do + "Notify[foo] <- Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) + end - it "should be able to parse right to left subscriptions" do - "Notify[foo] <~ Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) - end + it "should be able to parse normal left to right subscriptions" do + "Notify[foo] ~> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) + end + + it "should be able to parse right to left subscriptions" do + "Notify[foo] <~ Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) + end - it "should correctly set the arrow type of a relationship" do - "Notify[foo] <~ Notify[bar]".should parse_with { |rel| rel.arrow == "<~" } + it "should correctly set the arrow type of a relationship" do + "Notify[foo] <~ Notify[bar]".should parse_with { |rel| rel.arrow == "<~" } + end + + it "should be able to parse deep hash access" do + %q{ + $hash = { 'a' => { 'b' => { 'c' => 'it works' } } } + $out = $hash['a']['b']['c'] + }.should parse_with { |v| v.value.is_a?(Puppet::Parser::AST::ASTHash) } + end + + it "should fail if asked to parse '$foo::::bar'" do + expect { @parser.parse("$foo::::bar") }.to raise_error(Puppet::ParseError, /Syntax error at ':'/) + end + + describe "function calls" do + it "should be able to pass an array to a function" do + "my_function([1,2,3])".should parse_with { |fun| + fun.is_a?(Puppet::Parser::AST::Function) && + fun.arguments[0].evaluate(stub 'scope') == ['1','2','3'] + } + end + + it "should be able to pass a hash to a function" do + "my_function({foo => bar})".should parse_with { |fun| + fun.is_a?(Puppet::Parser::AST::Function) && + fun.arguments[0].evaluate(stub 'scope') == {'foo' => 'bar'} + } + end + end + + describe "collections" do + it "should find resources according to an expression" do + %q{ File <| mode == 0700 + 0050 + 0050 |> }.should parse_with { |coll| + coll.is_a?(Puppet::Parser::AST::Collection) && + coll.query.evaluate(stub 'scope').first == ["mode", "==", 0700 + 0050 + 0050] + } + end + end end + end - it "should be able to parse deep hash access" do - %q{ - $hash = { 'a' => { 'b' => { 'c' => 'it works' } } } - $out = $hash['a']['b']['c'] - }.should parse_with { |v| v.value.is_a?(Puppet::Parser::AST::ASTHash) } + describe 'using classic parser' do + before :each do + Puppet[:parser] = 'current' end + it_behaves_like 'a puppet parser' + end - it "should fail if asked to parse '$foo::::bar'" do - expect { @parser.parse("$foo::::bar") }.to raise_error(Puppet::ParseError, /Syntax error at ':'/) + describe 'using future parser' do + before :each do + Puppet[:parser] = 'future' end + it_behaves_like 'a puppet parser' - describe "function calls" do - it "should be able to pass an array to a function" do - "my_function([1,2,3])".should parse_with { |fun| - fun.is_a?(Puppet::Parser::AST::Function) && - fun.arguments[0].evaluate(stub 'scope') == ['1','2','3'] - } + context 'more detailed errors should be generated' do + before :each do + Puppet[:parser] = 'future' + @resource_type_collection = Puppet::Resource::TypeCollection.new("env") + @parser = Puppet::Parser::ParserFactory.parser("development") + end + + it 'should flag illegal type references' do + source = <<-SOURCE.gsub(/^ {8}/,'') + 1+1 { "title": } + SOURCE + # This error message is currently produced by the parser, and is not as detailed as desired + # It references position 16 at the closing '}' + expect { @parser.parse(source) }.to raise_error(/Expression is not valid as a resource.*line 1:16/) + end + + it 'should flag illegal type references and get position correct' do + source = <<-SOURCE.gsub(/^ {8}/,'') + 1+1 { "title": + } + SOURCE + # This error message is currently produced by the parser, and is not as detailed as desired + # It references position 16 at the closing '}' + expect { @parser.parse(source) }.to raise_error(/Expression is not valid as a resource.*line 2:3/) end - it "should be able to pass a hash to a function" do - "my_function({foo => bar})".should parse_with { |fun| - fun.is_a?(Puppet::Parser::AST::Function) && - fun.arguments[0].evaluate(stub 'scope') == {'foo' => 'bar'} + it 'should flag illegal use of non r-value producing if' do + source = <<-SOURCE.gsub(/^ {8}/,'') + $a = if true { + false } + SOURCE + expect { @parser.parse(source) }.to raise_error(/An 'if' statement does not produce a value at line 1:6/) end - end - describe "collections" do - it "should find resources according to an expression" do - %q{ File <| mode == 0700 + 0050 + 0050 |> }.should parse_with { |coll| - coll.is_a?(Puppet::Parser::AST::Collection) && - coll.query.evaluate(stub 'scope').first == ["mode", "==", 0700 + 0050 + 0050] + it 'should flag illegal use of non r-value producing case' do + source = <<-SOURCE.gsub(/^ {8}/,'') + $a = case true { + false :{ } } + SOURCE + expect { @parser.parse(source) }.to raise_error(/A 'case' statement does not produce a value at line 1:6/) + end + + it 'should flag illegal use of non r-value producing <| |>' do + expect { @parser.parse("$a = file <| |>") }.to raise_error(/A Virtual Query does not produce a value at line 1:6/) + end + + it 'should flag illegal use of non r-value producing <<| |>>' do + expect { @parser.parse("$a = file <<| |>>") }.to raise_error(/An Exported Query does not produce a value at line 1:6/) + end + + it 'should flag illegal use of 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") + expect { @parser.parse("$a = define foo { }") }.to raise_error(/2 errors/) + end + + it 'should flag illegal use of 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") + expect { @parser.parse("$a = class foo { }") }.to raise_error(/2 errors/) + end + + it 'unclosed quote should be flagged for start position of string' do + source = <<-SOURCE.gsub(/^ {8}/,'') + $a = "xx + yyy + SOURCE + expect { @parser.parse(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' at line 1:6/) + end + + it 'can produce multiple errors and raise a summary exception' do + source = <<-SOURCE.gsub(/^ {8}/,'') + $a = node x { } + SOURCE + 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") + expect { @parser.parse(source) }.to raise_error(/2 errors/) + end + + it 'can produce detailed error for a bad hostname' do + source = <<-SOURCE.gsub(/^ {8}/,'') + node 'macbook+owned+by+name' { } + SOURCE + expect { @parser.parse(source) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.*at line 1:6/) + end + + it 'can produce detailed error for a hostname with interpolation' do + source = <<-SOURCE.gsub(/^ {8}/,'') + $name = 'fred' + node "macbook-owned-by$name" { } + SOURCE + expect { @parser.parse(source) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node at line 2:24/) end end end diff --git a/spec/integration/parser/scope_spec.rb b/spec/integration/parser/scope_spec.rb index 66d29a438..8df54393b 100644 --- a/spec/integration/parser/scope_spec.rb +++ b/spec/integration/parser/scope_spec.rb @@ -182,7 +182,7 @@ describe "Two step scoping for variables" do class c { notify { 'something': message => "$a::b" } } - + class a { } node default { diff --git a/spec/integration/provider/cron/crontab_spec.rb b/spec/integration/provider/cron/crontab_spec.rb new file mode 100644 index 000000000..b061abb0d --- /dev/null +++ b/spec/integration/provider/cron/crontab_spec.rb @@ -0,0 +1,187 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'puppet/file_bucket/dipper' + +describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless => Puppet.features.microsoft_windows? do + include PuppetSpec::Files + + before :each do + Puppet::Type.type(:cron).stubs(:defaultprovider).returns described_class + Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to filebucket + + # I dont want to execute anything + described_class.stubs(:filetype).returns Puppet::Util::FileType::FileTypeFlat + described_class.stubs(:default_target).returns crontab_user1 + + # I dont want to stub Time.now to get a static header because I dont know + # where Time.now is used elsewere so just go with a very simple header + described_class.stubs(:header).returns "# HEADER: some simple\n# HEADER: header\n" + FileUtils.cp(my_fixture('crontab_user1'), crontab_user1) + FileUtils.cp(my_fixture('crontab_user2'), crontab_user2) + end + + after :each do + described_class.clear + end + + let :crontab_user1 do + tmpfile('cron_integration_specs') + end + + let :crontab_user2 do + tmpfile('cron_integration_specs') + end + + def run_in_catalog(*resources) + catalog = Puppet::Resource::Catalog.new + catalog.host_config = false + resources.each do |resource| + resource.expects(:err).never + catalog.add_resource(resource) + end + catalog.apply + end + + def expect_output(fixture_name) + File.read(crontab_user1).should == File.read(my_fixture(fixture_name)) + end + + describe "when managing a cron entry" do + describe "with ensure absent" do + it "should do nothing if entry already absent" do + resource = Puppet::Type.type(:cron).new( + :name => 'no_such_entry', + :ensure => :absent, + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('crontab_user1') + end + + it "should remove the resource from crontab if present" do + resource = Puppet::Type.type(:cron).new( + :name => 'My daily failure', + :ensure => :absent, + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('remove_named_resource') + end + + it "should remove a matching cronentry if present" do + resource = Puppet::Type.type(:cron).new( + :name => 'no_such_named_resource_in_crontab', + :ensure => :absent, + :minute => [ '17-19', '22' ], + :hour => [ '0-23/2' ], + :weekday => 'Tue', + :command => '/bin/unnamed_regular_command', + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('remove_unnamed_resource') + end + end + + describe "with ensure present" do + it "should do nothing if entry already present" do + resource = Puppet::Type.type(:cron).new( + :name => 'My daily failure', + :special => 'daily', + :command => '/bin/false', + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('crontab_user1') + end + + it "should do nothing if a matching entry already present" do + resource = Puppet::Type.type(:cron).new( + :name => 'no_such_named_resource_in_crontab', + :ensure => :present, + :minute => [ '17-19', '22' ], + :hour => [ '0-23/2' ], + :command => '/bin/unnamed_regular_command', + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('crontab_user1') + end + + it "should add a new normal entry if currently absent" do + resource = Puppet::Type.type(:cron).new( + :name => 'new entry', + :ensure => :present, + :minute => '12', + :weekday => 'Tue', + :command => '/bin/new', + :environment => [ + 'MAILTO=""', + 'SHELL=/bin/bash' + ], + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('create_normal_entry') + end + + it "should add a new special entry if currently absent" do + resource = Puppet::Type.type(:cron).new( + :name => 'new special entry', + :ensure => :present, + :special => 'reboot', + :command => 'echo "Booted" 1>&2', + :environment => 'MAILTO=bob@company.com', + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('create_special_entry') + end + + it "should change existing entry if out of sync" do + resource = Puppet::Type.type(:cron).new( + :name => 'Monthly job', + :ensure => :present, + :special => 'monthly', +# :minute => ['22'], + :command => '/usr/bin/monthly', + :environment => [], + :target => crontab_user1, + :user => crontab_user1 + ) + run_in_catalog(resource) + expect_output('modify_entry') + end + it "should not try to move an entry from one file to another" do + # force the parsedfile provider to also parse user1's crontab + random_resource = Puppet::Type.type(:cron).new( + :name => 'foo', + :ensure => :absent, + :target => crontab_user1, + :user => crontab_user1 + ) + resource = Puppet::Type.type(:cron).new( + :name => 'My daily failure', + :special => 'daily', + :command => "/bin/false", + :target => crontab_user2, + :user => crontab_user2 + ) + run_in_catalog(resource) + File.read(crontab_user1).should == File.read(my_fixture('moved_cronjob_input1')) + File.read(crontab_user2).should == File.read(my_fixture('moved_cronjob_input2')) + end + end + + it "should not add multiple headers" + end + +end diff --git a/spec/integration/provider/service/systemd_spec.rb b/spec/integration/provider/service/systemd_spec.rb new file mode 100644 index 000000000..b48eb06f3 --- /dev/null +++ b/spec/integration/provider/service/systemd_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby + +require 'spec_helper' + +describe Puppet::Type.type(:service).provider(:systemd), '(integration)' do + # TODO: Unfortunately there does not seem a way to stub the executable + # checks in the systemd provider because they happen at load time. + it "should be considered suitable if /bin/systemctl is present", :if => File.executable?('/bin/systemctl') do + described_class.should be_suitable + end + + it "should be considered suitable if /usr/bin/systemctl is present", :if => File.executable?('/usr/bin/systemctl') do + described_class.should be_suitable + end + + it "should not be cosidered suitable if systemctl is absent", + :unless => (File.executable?('/bin/systemctl') or File.executable?('/usr/bin/systemctl')) do + described_class.should_not be_suitable + end +end diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb index 3fce598ef..65206b377 100755 --- a/spec/integration/type/file_spec.rb +++ b/spec/integration/type/file_spec.rb @@ -273,22 +273,20 @@ describe Puppet::Type.type(:file) do describe "that is readable" do it "should set the executable bits when creating the destination (#10315)" do - pending "bug #10315" - catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply + File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end it "should set the executable bits when overwriting the destination (#10315)" do - pending "bug #10315" - FileUtils.touch(path) - catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) + catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow, :backup => false) catalog.apply + File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end end @@ -303,37 +301,41 @@ describe Puppet::Type.type(:file) do set_mode(0700, link_target) end - it "should not set executable bits when creating the destination (#10315)" do - pending "bug #10315" - + it "should set executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply - (get_mode(path) & 07777).should == 0666 + File.should be_directory(path) + (get_mode(path) & 07777).should == 0777 end - it "should not set executable bits when overwriting the destination" do + it "should set executable bits when overwriting the destination" do FileUtils.touch(path) - catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) + catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow, :backup => false) catalog.apply - (get_mode(path) & 07777).should == 0666 + File.should be_directory(path) + (get_mode(path) & 07777).should == 0777 end end end describe "to a file" do - let(:target) { tmpfile('file_target') } + let(:link_target) { tmpfile('file_target') } - it "should create the file, not a symlink (#2817, #10315)" do - pending "bug #2817, #10315" + before :each do + FileUtils.touch(link_target) + File.symlink(link_target, link) + end + + it "should create the file, not a symlink (#2817, #10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_file(path) - (get_mode(path) & 07777) == 0600 + (get_mode(path) & 07777).should == 0600 end it "should overwrite the file" do @@ -343,7 +345,7 @@ describe Puppet::Type.type(:file) do catalog.apply File.should be_file(path) - (get_mode(path) & 07777) == 0600 + (get_mode(path) & 07777).should == 0600 end end @@ -365,13 +367,11 @@ describe Puppet::Type.type(:file) do describe "when following all links" do it "should create the destination and apply executable bits (#10315)" do - pending "bug #10315" - catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_directory(path) - (get_mode(path) & 07777) == 0777 + (get_mode(path) & 07777).should == 0700 end it "should overwrite the destination and apply executable bits" do @@ -381,7 +381,7 @@ describe Puppet::Type.type(:file) do catalog.apply File.should be_directory(path) - (get_mode(path) & 07777) == 0777 + (get_mode(path) & 0111).should == 0100 end end end diff --git a/spec/integration/type/package_spec.rb b/spec/integration/type/package_spec.rb index 34d648d4d..0a1209a93 100755 --- a/spec/integration/type/package_spec.rb +++ b/spec/integration/type/package_spec.rb @@ -11,7 +11,7 @@ describe Puppet::Type.type(:package), "when choosing a default package provider" if Facter.value(:operatingsystem) == 'Solaris' && Puppet::Util::Package.versioncmp(Facter.value(:kernelrelease), '5.11') >= 0 :pkg else - {"Ubuntu" => :apt, "Debian" => :apt, "Darwin" => :pkgdmg, "RedHat" => :up2date, "Fedora" => :yum, "FreeBSD" => :ports, "OpenBSD" => :openbsd, "Solaris" => :sun, "DragonFly" => :pkgin}[os] + {"Ubuntu" => :apt, "Debian" => :apt, "Darwin" => :pkgdmg, "RedHat" => :up2date, "Fedora" => :yum, "FreeBSD" => :ports, "OpenBSD" => :openbsd, "Solaris" => :sun, "DragonFly" => :pkgin, "OpenWrt" => :opkg}[os] end end diff --git a/spec/lib/puppet_spec/database.rb b/spec/lib/puppet_spec/database.rb index e4127ba93..069ca158c 100644 --- a/spec/lib/puppet_spec/database.rb +++ b/spec/lib/puppet_spec/database.rb @@ -22,11 +22,8 @@ end # ready to roll with a serious database and all. Cleanup is handled # automatically for you. Nothing to do there. def setup_scratch_database - Puppet::Rails.stubs(:database_arguments).returns( - :adapter => 'sqlite3', - :log_level => Puppet[:rails_loglevel], - :database => ':memory:' - ) + Puppet[:dbadapter] = 'sqlite3' + Puppet[:dblocation] = ':memory:' Puppet[:railslog] = PuppetSpec::Files.tmpfile('storeconfigs.log') Puppet::Rails.init end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4f67f7218..14d2b0465 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -67,7 +67,12 @@ RSpec.configure do |config| if Puppet::Util::Platform.windows? config.output_stream = $stdout config.error_stream = $stderr - config.formatters.each { |f| f.instance_variable_set(:@output, $stdout) } + + config.formatters.each do |f| + if not f.instance_variable_get(:@output).kind_of?(::File) + f.instance_variable_set(:@output, $stdout) + end + end end Puppet::Test::TestHelper.initialize diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index c2a935c62..dd7897f41 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -20,7 +20,7 @@ describe Puppet::Application::Apply do Puppet::Node.indirection.cache_class = nil end - [:debug,:loadclasses,:verbose,:use_nodes,:detailed_exitcodes,:catalog].each do |option| + [:debug,:loadclasses,:verbose,:use_nodes,:detailed_exitcodes,:catalog, :write_catalog_summary].each do |option| it "should declare handle_#{option} method" do @apply.should respond_to("handle_#{option}".to_sym) end @@ -241,6 +241,21 @@ describe Puppet::Application::Apply do expect { @apply.main }.to exit_with 0 end + it "should not save the classes or resource file by default" do + @catalog.expects(:write_class_file).never + @catalog.expects(:write_resource_file).never + expect { @apply.main }.to exit_with 0 + end + + it "should save the classes and resources files when requested" do + @apply.options[:write_catalog_summary] = true + + @catalog.expects(:write_class_file).once + @catalog.expects(:write_resource_file).once + + expect { @apply.main }.to exit_with 0 + end + it "should call the prerun and postrun commands on a Configurer instance" do Puppet::Configurer.any_instance.expects(:execute_prerun_command).returns(true) Puppet::Configurer.any_instance.expects(:execute_postrun_command).returns(true) diff --git a/spec/unit/application/describe_spec.rb b/spec/unit/application/describe_spec.rb index ffa951905..2d5f13c89 100755 --- a/spec/unit/application/describe_spec.rb +++ b/spec/unit/application/describe_spec.rb @@ -29,7 +29,7 @@ describe Puppet::Application::Describe do describe "in preinit" do - it "should set options[:parameteers] to true" do + it "should set options[:parameters] to true" do @describe.preinit @describe.options[:parameters].should be_true diff --git a/spec/unit/application/doc_spec.rb b/spec/unit/application/doc_spec.rb index 2a408774a..8528c3a49 100755 --- a/spec/unit/application/doc_spec.rb +++ b/spec/unit/application/doc_spec.rb @@ -143,6 +143,61 @@ describe Puppet::Application::Doc do @doc.setup end + describe "configuring logging" do + before :each do + Puppet::Util::Log.stubs(:newdestination) + end + + describe "with --debug" do + before do + @doc.options[:debug] = true + end + + it "should set log level to debug" do + @doc.setup + Puppet::Util::Log.level.should == :debug + end + + it "should set log destination to console" do + Puppet::Util::Log.expects(:newdestination).with(:console) + @doc.setup + end + end + + describe "with --verbose" do + before do + @doc.options[:verbose] = true + end + + it "should set log level to info" do + @doc.setup + Puppet::Util::Log.level.should == :info + end + + it "should set log destination to console" do + Puppet::Util::Log.expects(:newdestination).with(:console) + @doc.setup + end + end + + describe "without --debug or --verbose" do + before do + @doc.options[:debug] = false + @doc.options[:verbose] = false + end + + it "should set log level to warning" do + @doc.setup + Puppet::Util::Log.level.should == :warning + end + + it "should set log destination to console" do + Puppet::Util::Log.expects(:newdestination).with(:console) + @doc.setup + end + end + end + describe "in non-rdoc mode" do it "should get all non-dynamic reference if --all" do @doc.options[:all] = true @@ -163,10 +218,6 @@ describe Puppet::Application::Doc do end describe "in rdoc mode" do - before :each do - Puppet::Util::Log.stubs(:newdestination) - end - describe "when there are unknown args" do it "should expand --modulepath if any" do @doc.unknown_args = [ { :opt => "--modulepath", :arg => "path" } ] @@ -199,34 +250,6 @@ describe Puppet::Application::Doc do @doc.setup_rdoc end - - it "should set log level to debug if --debug" do - @doc.options[:debug] = true - @doc.setup_rdoc - Puppet::Util::Log.level.should == :debug - end - - it "should set log level to info if --verbose" do - @doc.options[:verbose] = true - @doc.setup_rdoc - Puppet::Util::Log.level.should == :info - end - - it "should set log destination to console if --verbose" do - @doc.options[:verbose] = true - - Puppet::Util::Log.expects(:newdestination).with(:console) - - @doc.setup_rdoc - end - - it "should set log destination to console if --debug" do - @doc.options[:debug] = true - - Puppet::Util::Log.expects(:newdestination).with(:console) - - @doc.setup_rdoc - end end end diff --git a/spec/unit/application/kick_spec.rb b/spec/unit/application/kick_spec.rb index 57217685b..cfc38fa23 100755 --- a/spec/unit/application/kick_spec.rb +++ b/spec/unit/application/kick_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' require 'puppet/application/kick' +require 'puppet/run' +require 'puppet/util/ldap/connection' describe Puppet::Application::Kick, :if => Puppet.features.posix? do - before :each do - require 'puppet/util/ldap/connection' Puppet::Util::Ldap::Connection.stubs(:new).returns(stub_everything) @kick = Puppet::Application[:kick] Puppet::Util::Log.stubs(:newdestination) @@ -82,7 +82,7 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do @kick.preinit end - [:all, :foreground, :debug, :ping, :test].each do |option| + [:all, :foreground, :debug, :ping, :test, :ignoreschedules].each do |option| it "should declare handle_#{option} method" do @kick.should respond_to("handle_#{option}".to_sym) end @@ -226,7 +226,7 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do @kick.options.stubs(:[]).with(:debug).returns(false) @kick.options.stubs(:[]).with(:verbose).returns(false) # needed when logging is initialized @kick.options.stubs(:[]).with(:setdest).returns(false) # needed when logging is initialized - + @kick.stubs(:print) @kick.preinit @kick.stubs(:parse_options) @@ -256,7 +256,6 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do describe "during call of run_for_host" do before do - require 'puppet/run' options = { :background => true, :ignoreschedules => false, :tags => [] } @@ -264,13 +263,16 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do @agent_run = Puppet::Run.new( options.dup ) @agent_run.stubs(:status).returns("success") + # ensure that we don't actually run the agent + @agent_run.stubs(:run).returns(@agent_run) + + Puppet::Run.indirection.terminus_class = :local Puppet::Run.indirection.expects(:terminus_class=).with( :rest ) Puppet::Run.expects(:new).with( options ).returns(@agent_run) end it "should call run on a Puppet::Run for the given host" do Puppet::Run.indirection.expects(:save).with(@agent_run, 'https://host:8139/production/run/host').returns(@agent_run) - expect { @kick.run_for_host('host') }.to exit_with 0 end diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index 689a6c692..579303907 100755 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -241,25 +241,25 @@ describe Puppet::Application::Master, :unless => Puppet.features.microsoft_windo before do Puppet[:manifest] = "site.pp" Puppet.stubs(:err) - @master.stubs(:jj) + @master.stubs(:puts) end it "should compile a catalog for the specified node" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).with("foo").returns Puppet::Resource::Catalog.new - $stdout.stubs(:puts) expect { @master.compile }.to exit_with 0 end - it "should convert the catalog to a pure-resource catalog and use 'jj' to pretty-print the catalog" do + it "should convert the catalog to a pure-resource catalog and use 'PSON::pretty_generate' to pretty-print the catalog" do catalog = Puppet::Resource::Catalog.new + PSON.stubs(:pretty_generate) Puppet::Resource::Catalog.indirection.expects(:find).returns catalog catalog.expects(:to_resource).returns("rescat") @master.options[:node] = "foo" - @master.expects(:jj).with("rescat") + PSON.expects(:pretty_generate).with('rescat', :allow_nan => true, :max_nesting => false) expect { @master.compile }.to exit_with 0 end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index 4637e510c..dfb428467 100755 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -39,6 +39,8 @@ describe Puppet::Daemon, :unless => Puppet.features.microsoft_windows? do @daemon.reopen_logs end + let(:server) { stub("Server", :start => nil, :wait_for_shutdown => nil) } + describe "when setting signal traps" do signals = {:INT => :stop, :TERM => :stop } signals.update({:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}) unless Puppet.features.microsoft_windows? @@ -74,12 +76,21 @@ describe Puppet::Daemon, :unless => Puppet.features.microsoft_windows? do end it "should start its server if one is configured" do - server = mock 'server' + @daemon.server = server + server.expects(:start) @daemon.stubs(:server).returns server @daemon.start end + + it "waits for the server to shutdown when there is one" do + @daemon.server = server + + server.expects(:wait_for_shutdown) + + @daemon.start + end end describe "when stopping" do @@ -97,7 +108,6 @@ describe Puppet::Daemon, :unless => Puppet.features.microsoft_windows? do end it "should stop its server if one is configured" do - server = mock 'server' server.expects(:stop) @daemon.stubs(:server).returns server expect { @daemon.stop }.to exit_with 0 @@ -203,7 +213,7 @@ describe Puppet::Daemon, :unless => Puppet.features.microsoft_windows? do it "should run the agent if one is available and it is not running" do @agent.expects(:running?).returns false - @agent.expects :run + @agent.expects(:run).with({:splay => false}) @daemon.agent = @agent diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index 4af3ebbc1..f6a661843 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -3,376 +3,326 @@ require 'spec_helper' require 'puppet/file_serving/fileset' -describe Puppet::FileServing::Fileset, " when initializing" do +describe Puppet::FileServing::Fileset do include PuppetSpec::Files + let(:somefile) { make_absolute("/some/file") } - let(:request) { Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil) } - - before :each do - @somefile = make_absolute("/some/file") - end - - it "should require a path" do - proc { Puppet::FileServing::Fileset.new }.should raise_error(ArgumentError) - end - - it "should fail if its path is not fully qualified" do - proc { Puppet::FileServing::Fileset.new("some/file") }.should raise_error(ArgumentError) - end - - it "should not fail if the path is fully qualified, with a trailing separator" do - path_with_separator = "#{@somefile}#{File::SEPARATOR}" - File.stubs(:lstat).with(@somefile).returns stub('stat') - fileset = Puppet::FileServing::Fileset.new(path_with_separator) - fileset.path.should == @somefile - end - - it "should not fail if the path is just the file separator" do - path = File.expand_path(File::SEPARATOR) - File.stubs(:lstat).with(path).returns stub('stat') - fileset = Puppet::FileServing::Fileset.new(path) - fileset.path.should == path - end - - it "should fail if its path does not exist" do - File.expects(:lstat).with(@somefile).returns nil - proc { Puppet::FileServing::Fileset.new(@somefile) }.should raise_error(ArgumentError) - end - - it "should accept a 'recurse' option" do - File.expects(:lstat).with(@somefile).returns stub("stat") - set = Puppet::FileServing::Fileset.new(@somefile, :recurse => true) - set.recurse.should be_true - end - - it "should accept a 'recurselimit' option" do - File.expects(:lstat).with(@somefile).returns stub("stat") - set = Puppet::FileServing::Fileset.new(@somefile, :recurselimit => 3) - set.recurselimit.should == 3 - end - - it "should accept an 'ignore' option" do - File.expects(:lstat).with(@somefile).returns stub("stat") - set = Puppet::FileServing::Fileset.new(@somefile, :ignore => ".svn") - set.ignore.should == [".svn"] - end - - it "should accept a 'links' option" do - File.expects(:lstat).with(@somefile).returns stub("stat") - set = Puppet::FileServing::Fileset.new(@somefile, :links => :manage) - set.links.should == :manage - end + context "when initializing" do + it "requires a path" do + expect { Puppet::FileServing::Fileset.new }.to raise_error(ArgumentError) + end - it "should accept a 'checksum_type' option" do - File.expects(:lstat).with(@somefile).returns stub("stat") - set = Puppet::FileServing::Fileset.new(@somefile, :checksum_type => :test) - set.checksum_type.should == :test - end + it "fails if its path is not fully qualified" do + expect { Puppet::FileServing::Fileset.new("some/file") }.to raise_error(ArgumentError, "Fileset paths must be fully qualified: some/file") + end - it "should fail if 'links' is set to anything other than :manage or :follow" do - proc { Puppet::FileServing::Fileset.new(@somefile, :links => :whatever) }.should raise_error(ArgumentError) - end + it "removes a trailing file path separator" do + path_with_separator = "#{somefile}#{File::SEPARATOR}" + File.stubs(:lstat).with(somefile).returns stub('stat') + fileset = Puppet::FileServing::Fileset.new(path_with_separator) + fileset.path.should == somefile + end - it "should default to 'false' for recurse" do - File.expects(:lstat).with(@somefile).returns stub("stat") - Puppet::FileServing::Fileset.new(@somefile).recurse.should == false - end + it "can be created from the root directory" do + path = File.expand_path(File::SEPARATOR) + File.stubs(:lstat).with(path).returns stub('stat') + fileset = Puppet::FileServing::Fileset.new(path) + fileset.path.should == path + end - it "should default to :infinite for recurselimit" do - File.expects(:lstat).with(@somefile).returns stub("stat") - Puppet::FileServing::Fileset.new(@somefile).recurselimit.should == :infinite - end + it "fails if its path does not exist" do + File.expects(:lstat).with(somefile).raises(Errno::ENOENT) + expect { Puppet::FileServing::Fileset.new(somefile) }.to raise_error(ArgumentError, "Fileset paths must exist") + end - it "should default to an empty ignore list" do - File.expects(:lstat).with(@somefile).returns stub("stat") - Puppet::FileServing::Fileset.new(@somefile).ignore.should == [] - end + it "accepts a 'recurse' option" do + File.expects(:lstat).with(somefile).returns stub("stat") + set = Puppet::FileServing::Fileset.new(somefile, :recurse => true) + set.recurse.should be_true + end - it "should default to :manage for links" do - File.expects(:lstat).with(@somefile).returns stub("stat") - Puppet::FileServing::Fileset.new(@somefile).links.should == :manage - end + it "accepts a 'recurselimit' option" do + File.expects(:lstat).with(somefile).returns stub("stat") + set = Puppet::FileServing::Fileset.new(somefile, :recurselimit => 3) + set.recurselimit.should == 3 + end - it "should support using an Indirector Request for its options" do - File.expects(:lstat).with(@somefile).returns stub("stat") - lambda { Puppet::FileServing::Fileset.new(@somefile, request) }.should_not raise_error - end + it "accepts an 'ignore' option" do + File.expects(:lstat).with(somefile).returns stub("stat") + set = Puppet::FileServing::Fileset.new(somefile, :ignore => ".svn") + set.ignore.should == [".svn"] + end - describe "using an indirector request" do - before do - File.stubs(:lstat).returns stub("stat") - @values = {:links => :manage, :ignore => %w{a b}, :recurse => true, :recurselimit => 1234} - @myfile = make_absolute("/my/file") + it "accepts a 'links' option" do + File.expects(:lstat).with(somefile).returns stub("stat") + set = Puppet::FileServing::Fileset.new(somefile, :links => :manage) + set.links.should == :manage end - [:recurse, :recurselimit, :ignore, :links].each do |option| - it "should pass :recurse, :recurselimit, :ignore, and :links settings on to the fileset if present" do - request.stubs(:options).returns(option => @values[option]) - Puppet::FileServing::Fileset.new(@myfile, request).send(option).should == @values[option] - end + it "accepts a 'checksum_type' option" do + File.expects(:lstat).with(somefile).returns stub("stat") + set = Puppet::FileServing::Fileset.new(somefile, :checksum_type => :test) + set.checksum_type.should == :test + end - it "should pass :recurse, :recurselimit, :ignore, and :links settings on to the fileset if present with the keys stored as strings" do - request.stubs(:options).returns(option.to_s => @values[option]) - Puppet::FileServing::Fileset.new(@myfile, request).send(option).should == @values[option] - end + it "fails if 'links' is set to anything other than :manage or :follow" do + expect { Puppet::FileServing::Fileset.new(somefile, :links => :whatever) }.to raise_error(ArgumentError, "Invalid :links value 'whatever'") end - it "should convert the integer as a string to their integer counterpart when setting options" do - request.stubs(:options).returns(:recurselimit => "1234") - Puppet::FileServing::Fileset.new(@myfile, request).recurselimit.should == 1234 + it "defaults to 'false' for recurse" do + File.expects(:lstat).with(somefile).returns stub("stat") + Puppet::FileServing::Fileset.new(somefile).recurse.should == false end - it "should convert the string 'true' to the boolean true when setting options" do - request.stubs(:options).returns(:recurse => "true") - Puppet::FileServing::Fileset.new(@myfile, request).recurse.should == true + it "defaults to :infinite for recurselimit" do + File.expects(:lstat).with(somefile).returns stub("stat") + Puppet::FileServing::Fileset.new(somefile).recurselimit.should == :infinite end - it "should convert the string 'false' to the boolean false when setting options" do - request.stubs(:options).returns(:recurse => "false") - Puppet::FileServing::Fileset.new(@myfile, request).recurse.should == false + it "defaults to an empty ignore list" do + File.expects(:lstat).with(somefile).returns stub("stat") + Puppet::FileServing::Fileset.new(somefile).ignore.should == [] end - end -end -describe Puppet::FileServing::Fileset, " when determining whether to recurse" do - include PuppetSpec::Files + it "defaults to :manage for links" do + File.expects(:lstat).with(somefile).returns stub("stat") + Puppet::FileServing::Fileset.new(somefile).links.should == :manage + end - before do - @path = make_absolute("/my/path") - File.expects(:lstat).with(@path).returns stub("stat") - @fileset = Puppet::FileServing::Fileset.new(@path) - end + describe "using an indirector request" do + let(:values) { { :links => :manage, :ignore => %w{a b}, :recurse => true, :recurselimit => 1234 } } - it "should always recurse if :recurse is set to 'true' and with infinite recursion" do - @fileset.recurse = true - @fileset.recurselimit = :infinite - @fileset.recurse?(0).should be_true - end + before :each do + File.stubs(:lstat).returns stub("stat") + end - it "should never recurse if :recurse is set to 'false'" do - @fileset.recurse = false - @fileset.recurse?(-1).should be_false - end + [:recurse, :recurselimit, :ignore, :links].each do |option| + it "passes the #{option} option on to the fileset if present" do + request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {option => values[option]}) - it "should recurse if :recurse is set to true, :recurselimit is set to an integer and the current depth is less than that integer" do - @fileset.recurse = true - @fileset.recurselimit = 1 - @fileset.recurse?(0).should be_true - end + Puppet::FileServing::Fileset.new(somefile, request).send(option).should == values[option] + end + end - it "should recurse if :recurse is set to true, :recurselimit is set to an integer and the current depth is equal to that integer" do - @fileset.recurse = true - @fileset.recurselimit = 1 - @fileset.recurse?(1).should be_true - end + it "converts the integer as a string to their integer counterpart when setting options" do + request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, + {:recurselimit => "1234"}) - it "should not recurse if :recurse is set to true, :recurselimit is set to an integer and the current depth is greater than that integer" do - @fileset.recurse = true - @fileset.recurselimit = 1 - @fileset.recurse?(2).should be_false - end -end + Puppet::FileServing::Fileset.new(somefile, request).recurselimit.should == 1234 + end -describe Puppet::FileServing::Fileset, " when recursing" do - include PuppetSpec::Files + it "converts the string 'true' to the boolean true when setting options" do + request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, + {:recurse => "true"}) - before do - @path = make_absolute("/my/path") - File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) - @fileset = Puppet::FileServing::Fileset.new(@path) + Puppet::FileServing::Fileset.new(somefile, request).recurse.should == true + end - @dirstat = stub 'dirstat', :directory? => true - @filestat = stub 'filestat', :directory? => false - end + it "converts the string 'false' to the boolean false when setting options" do + request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, + {:recurse => "false"}) - def mock_dir_structure(path, stat_method = :lstat) - File.stubs(stat_method).with(path).returns(@dirstat) - Dir.stubs(:entries).with(path).returns(%w{one two .svn CVS}) - - # Keep track of the files we're stubbing. - @files = %w{.} - - %w{one two .svn CVS}.each do |subdir| - @files << subdir # relative path - subpath = File.join(path, subdir) - File.stubs(stat_method).with(subpath).returns(@dirstat) - Dir.stubs(:entries).with(subpath).returns(%w{.svn CVS file1 file2}) - %w{file1 file2 .svn CVS}.each do |file| - @files << File.join(subdir, file) # relative path - File.stubs(stat_method).with(File.join(subpath, file)).returns(@filestat) + Puppet::FileServing::Fileset.new(somefile, request).recurse.should == false end end end - it "should recurse through the whole file tree if :recurse is set to 'true'" do - mock_dir_structure(@path) - @fileset.stubs(:recurse?).returns(true) - @fileset.files.sort.should == @files.sort - end + context "when recursing" do + before do + @path = make_absolute("/my/path") + File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) + @fileset = Puppet::FileServing::Fileset.new(@path) - it "should not recurse if :recurse is set to 'false'" do - mock_dir_structure(@path) - @fileset.stubs(:recurse?).returns(false) - @fileset.files.should == %w{.} - end + @dirstat = stub 'dirstat', :directory? => true + @filestat = stub 'filestat', :directory? => false + end - # It seems like I should stub :recurse? here, or that I shouldn't stub the - # examples above, but... - it "should recurse to the level set if :recurselimit is set to an integer" do - mock_dir_structure(@path) - @fileset.recurse = true - @fileset.recurselimit = 1 - @fileset.files.should == %w{. one two .svn CVS} - end + def mock_dir_structure(path, stat_method = :lstat) + File.stubs(stat_method).with(path).returns(@dirstat) + Dir.stubs(:entries).with(path).returns(%w{one two .svn CVS}) + + # Keep track of the files we're stubbing. + @files = %w{.} + + %w{one two .svn CVS}.each do |subdir| + @files << subdir # relative path + subpath = File.join(path, subdir) + File.stubs(stat_method).with(subpath).returns(@dirstat) + Dir.stubs(:entries).with(subpath).returns(%w{.svn CVS file1 file2}) + %w{file1 file2 .svn CVS}.each do |file| + @files << File.join(subdir, file) # relative path + File.stubs(stat_method).with(File.join(subpath, file)).returns(@filestat) + end + end + end - it "should ignore the '.' and '..' directories in subdirectories" do - mock_dir_structure(@path) - @fileset.recurse = true - @fileset.files.sort.should == @files.sort - end + MockStat = Struct.new(:path, :directory) do + # struct doesn't support thing ending in ? + def directory? + directory + end + end - it "should function if the :ignore value provided is nil" do - mock_dir_structure(@path) - @fileset.recurse = true - @fileset.ignore = nil - lambda { @fileset.files }.should_not raise_error - end + MockDirectory = Struct.new(:name, :entries) do + def mock(base_path) + path = File.join(base_path, name) + File.stubs(:lstat).with(path).returns(MockStat.new(path, true)) + Dir.stubs(:entries).with(path).returns(['.', '..'] + entries.map(&:name)) + entries.each do |entry| + entry.mock(path) + end + end + end - it "should ignore files that match a single pattern in the ignore list" do - mock_dir_structure(@path) - @fileset.recurse = true - @fileset.ignore = ".svn" - @fileset.files.find { |file| file.include?(".svn") }.should be_nil - end + MockFile = Struct.new(:name) do + def mock(base_path) + path = File.join(base_path, name) + File.stubs(:lstat).with(path).returns(MockStat.new(path, false)) + end + end - it "should ignore files that match any of multiple patterns in the ignore list" do - mock_dir_structure(@path) - @fileset.recurse = true - @fileset.ignore = %w{.svn CVS} - @fileset.files.find { |file| file.include?(".svn") or file.include?("CVS") }.should be_nil - end + it "doesn't ignore pending directories when the last entry at the top level is a file" do + structure = MockDirectory.new('path', + [MockDirectory.new('dir1', + [MockDirectory.new('a', [MockFile.new('f')])]), + MockFile.new('file')]) + structure.mock(make_absolute('/your')) + fileset = Puppet::FileServing::Fileset.new(make_absolute('/your/path')) + fileset.recurse = true + fileset.links = :manage + fileset.files.should == [".", "dir1", "file", "dir1/a", "dir1/a/f"] + end - it "should use File.stat if :links is set to :follow" do - mock_dir_structure(@path, :stat) - @fileset.recurse = true - @fileset.links = :follow - @fileset.files.sort.should == @files.sort - end + it "recurses through the whole file tree if :recurse is set to 'true'" do + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.files.sort.should == @files.sort + end - it "should use File.lstat if :links is set to :manage" do - mock_dir_structure(@path, :lstat) - @fileset.recurse = true - @fileset.links = :manage - @fileset.files.sort.should == @files.sort - end + it "does not recurse if :recurse is set to 'false'" do + mock_dir_structure(@path) + @fileset.recurse = false + @fileset.files.should == %w{.} + end - it "should succeed when paths have regexp significant characters" do - @path = make_absolute("/my/path/rV1x2DafFr0R6tGG+1bbk++++TM") - File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) - @fileset = Puppet::FileServing::Fileset.new(@path) - mock_dir_structure(@path) - @fileset.recurse = true - @fileset.files.sort.should == @files.sort - end -end + it "recurses to the level set by :recurselimit" do + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.recurselimit = 1 + @fileset.files.should == %w{. one two .svn CVS} + end -describe Puppet::FileServing::Fileset, " when following links that point to missing files" do - include PuppetSpec::Files + it "ignores the '.' and '..' directories in subdirectories" do + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.files.sort.should == @files.sort + end - before do - @path = make_absolute("/my/path") - File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) - @fileset = Puppet::FileServing::Fileset.new(@path) - @fileset.links = :follow - @fileset.recurse = true + it "does not fail if the :ignore value provided is nil" do + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.ignore = nil + expect { @fileset.files }.to_not raise_error + end - @stat = stub 'stat', :directory? => true + it "ignores files that match a single pattern in the ignore list" do + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.ignore = ".svn" + @fileset.files.find { |file| file.include?(".svn") }.should be_nil + end - File.expects(:stat).with(@path).returns(@stat) - File.expects(:stat).with(File.join(@path, "mylink")).raises(Errno::ENOENT) - Dir.stubs(:entries).with(@path).returns(["mylink"]) - end + it "ignores files that match any of multiple patterns in the ignore list" do + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.ignore = %w{.svn CVS} + @fileset.files.find { |file| file.include?(".svn") or file.include?("CVS") }.should be_nil + end - it "should not fail" do - proc { @fileset.files }.should_not raise_error - end + it "uses File.stat if :links is set to :follow" do + mock_dir_structure(@path, :stat) + @fileset.recurse = true + @fileset.links = :follow + @fileset.files.sort.should == @files.sort + end + + it "uses File.lstat if :links is set to :manage" do + mock_dir_structure(@path, :lstat) + @fileset.recurse = true + @fileset.links = :manage + @fileset.files.sort.should == @files.sort + end - it "should still manage the link" do - @fileset.files.sort.should == %w{. mylink}.sort + it "works when paths have regexp significant characters" do + @path = make_absolute("/my/path/rV1x2DafFr0R6tGG+1bbk++++TM") + File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) + @fileset = Puppet::FileServing::Fileset.new(@path) + mock_dir_structure(@path) + @fileset.recurse = true + @fileset.files.sort.should == @files.sort + end end -end -describe Puppet::FileServing::Fileset, " when ignoring" do - include PuppetSpec::Files + it "manages the links to missing files" do + path = make_absolute("/my/path") + stat = stub 'stat', :directory? => true - before do - @path = make_absolute("/my/path") - File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) - @fileset = Puppet::FileServing::Fileset.new(@path) - end + File.expects(:lstat).with(path).returns(stat) + File.expects(:stat).with(path).returns(stat) + File.expects(:stat).with(File.join(path, "mylink")).raises(Errno::ENOENT) - it "should use ruby's globbing to determine what files should be ignored" do - @fileset.ignore = ".svn" - File.expects(:fnmatch?).with(".svn", "my_file") - @fileset.ignore?("my_file") - end + Dir.stubs(:entries).with(path).returns(["mylink"]) - it "should ignore files whose paths match a single provided ignore value" do - @fileset.ignore = ".svn" - File.stubs(:fnmatch?).with(".svn", "my_file").returns true - @fileset.ignore?("my_file").should be_true - end + fileset = Puppet::FileServing::Fileset.new(path) + + fileset.links = :follow + fileset.recurse = true - it "should ignore files whose paths match any of multiple provided ignore values" do - @fileset.ignore = [".svn", "CVS"] - File.stubs(:fnmatch?).with(".svn", "my_file").returns false - File.stubs(:fnmatch?).with("CVS", "my_file").returns true - @fileset.ignore?("my_file").should be_true + fileset.files.sort.should == %w{. mylink}.sort end -end -describe Puppet::FileServing::Fileset, "when merging other filesets" do - include PuppetSpec::Files + context "when merging other filesets" do + before do + @paths = [make_absolute("/first/path"), make_absolute("/second/path"), make_absolute("/third/path")] + File.stubs(:lstat).returns stub("stat", :directory? => false) - before do - @paths = [make_absolute("/first/path"), make_absolute("/second/path"), make_absolute("/third/path")] - File.stubs(:lstat).returns stub("stat", :directory? => false) + @filesets = @paths.collect do |path| + File.stubs(:lstat).with(path).returns stub("stat", :directory? => true) + Puppet::FileServing::Fileset.new(path, :recurse => true) + end - @filesets = @paths.collect do |path| - File.stubs(:lstat).with(path).returns stub("stat", :directory? => true) - Puppet::FileServing::Fileset.new(path, :recurse => true) + Dir.stubs(:entries).returns [] end - Dir.stubs(:entries).returns [] - end - - it "should return a hash of all files in each fileset with the value being the base path" do - Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one uno}) - Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two dos}) - Dir.expects(:entries).with(make_absolute("/third/path")).returns(%w{three tres}) - - Puppet::FileServing::Fileset.merge(*@filesets).should == { - "." => make_absolute("/first/path"), - "one" => make_absolute("/first/path"), - "uno" => make_absolute("/first/path"), - "two" => make_absolute("/second/path"), - "dos" => make_absolute("/second/path"), - "three" => make_absolute("/third/path"), - "tres" => make_absolute("/third/path"), - } - end + it "returns a hash of all files in each fileset with the value being the base path" do + Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one uno}) + Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two dos}) + Dir.expects(:entries).with(make_absolute("/third/path")).returns(%w{three tres}) + + Puppet::FileServing::Fileset.merge(*@filesets).should == { + "." => make_absolute("/first/path"), + "one" => make_absolute("/first/path"), + "uno" => make_absolute("/first/path"), + "two" => make_absolute("/second/path"), + "dos" => make_absolute("/second/path"), + "three" => make_absolute("/third/path"), + "tres" => make_absolute("/third/path"), + } + end - it "should include the base directory from the first fileset" do - Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) - Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two}) + it "includes the base directory from the first fileset" do + Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) + Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two}) - Puppet::FileServing::Fileset.merge(*@filesets)["."].should == make_absolute("/first/path") - end + Puppet::FileServing::Fileset.merge(*@filesets)["."].should == make_absolute("/first/path") + end - it "should use the base path of the first found file when relative file paths conflict" do - Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) - Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{one}) + it "uses the base path of the first found file when relative file paths conflict" do + Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) + Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{one}) - Puppet::FileServing::Fileset.merge(*@filesets)["one"].should == make_absolute("/first/path") + Puppet::FileServing::Fileset.merge(*@filesets)["one"].should == make_absolute("/first/path") + end end end + diff --git a/spec/unit/forge/errors_spec.rb b/spec/unit/forge/errors_spec.rb index 1d3c9f9f7..686790e3a 100644 --- a/spec/unit/forge/errors_spec.rb +++ b/spec/unit/forge/errors_spec.rb @@ -39,4 +39,44 @@ Could not connect to http://fake.com:1111 end end + describe 'ResponseError' do + subject { Puppet::Forge::Errors::ResponseError } + let(:response) { stub(:body => '{}', :code => '404', :message => "not found") } + + context 'without message' do + let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module') } + + it 'should return a valid single line error' do + exception.message.should == 'Could not execute operation for \'user/module\'. Detail: 404 not found.' + end + + it 'should return a valid multiline error' do + exception.multiline.should == <<-eos.chomp +Could not execute operation for 'user/module' + The server being queried was http://fake.com:1111 + The HTTP response we received was '404 not found' + Check the author and module names are correct. + eos + end + end + + context 'with message' do + let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module', :message => 'no such module') } + + it 'should return a valid single line error' do + exception.message.should == 'Could not execute operation for \'user/module\'. Detail: no such module / 404 not found.' + end + + it 'should return a valid multiline error' do + exception.multiline.should == <<-eos.chomp +Could not execute operation for 'user/module' + The server being queried was http://fake.com:1111 + The HTTP response we received was '404 not found' + The message we received said 'no such module' + Check the author and module names are correct. + eos + end + end + end + end diff --git a/spec/unit/forge/repository_spec.rb b/spec/unit/forge/repository_spec.rb index 0f16fa951..71fe38ecb 100644 --- a/spec/unit/forge/repository_spec.rb +++ b/spec/unit/forge/repository_spec.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'spec_helper' require 'net/http' require 'puppet/forge/repository' @@ -91,7 +92,7 @@ describe Puppet::Forge::Repository do http.expects(:request).with() do |request| puppet_version = /Puppet\/\d+\..*/ os_info = /\(.*\)/ - ruby_version = /Ruby\/\d+\.\d+\.\d+(-p\d+)? \(\d{4}-\d{2}-\d{2}; .*\)/ + ruby_version = /Ruby\/\d+\.\d+\.\d+(-p-?\d+)? \(\d{4}-\d{2}-\d{2}; .*\)/ request["User-Agent"] =~ /^#{consumer_version} #{puppet_version} #{os_info} #{ruby_version}/ end @@ -100,6 +101,15 @@ describe Puppet::Forge::Repository do repository.make_http_request("the_path") end + it "escapes the received URI" do + unescaped_uri = "héllo world !! ç à" + performs_an_http_request do |http| + http.expects(:request).with(responds_with(:path, URI.escape(unescaped_uri))) + end + + repository.make_http_request(unescaped_uri) + end + def performs_an_http_request(result = nil, &block) http = mock("http client") yield http diff --git a/spec/unit/forge_spec.rb b/spec/unit/forge_spec.rb index 7c015f8db..2041149a5 100644 --- a/spec/unit/forge_spec.rb +++ b/spec/unit/forge_spec.rb @@ -36,15 +36,25 @@ describe Puppet::Forge do context "when the connection to the forge fails" do before :each do - repository_responds_with(stub(:body => '{}', :code => '404')) + repository_responds_with(stub(:body => '{}', :code => '404', :message => "not found")) end it "raises an error for search" do - expect { forge.search('bacula') }.to raise_error RuntimeError + expect { forge.search('bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'bacula'. Detail: 404 not found." end it "raises an error for remote_dependency_info" do - expect { forge.remote_dependency_info('puppetlabs', 'bacula', '0.0.1') }.to raise_error RuntimeError + expect { forge.remote_dependency_info('puppetlabs', 'bacula', '0.0.1') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'puppetlabs/bacula'. Detail: 404 not found." + end + end + + context "when the API responses with an error" do + before :each do + repository_responds_with(stub(:body => '{"error":"invalid module"}', :code => '410', :message => "Gone")) + end + + it "raises an error for remote_dependency_info" do + expect { forge.remote_dependency_info('puppetlabs', 'bacula', '0.0.1') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'puppetlabs/bacula'. Detail: invalid module / 410 Gone." end end end diff --git a/spec/unit/hiera/backend/puppet_backend_spec.rb b/spec/unit/hiera/backend/puppet_backend_spec.rb index 545ef257f..4e87430c6 100644 --- a/spec/unit/hiera/backend/puppet_backend_spec.rb +++ b/spec/unit/hiera/backend/puppet_backend_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' require 'hiera/backend/puppet_backend' require 'hiera/scope' +require 'hiera/config' describe Hiera::Backend::Puppet_backend do diff --git a/spec/unit/hiera/scope_spec.rb b/spec/unit/hiera/scope_spec.rb index 90d766972..a60be78a9 100644 --- a/spec/unit/hiera/scope_spec.rb +++ b/spec/unit/hiera/scope_spec.rb @@ -2,59 +2,82 @@ require 'spec_helper' require 'hiera/scope' describe Hiera::Scope do + let(:real) { Puppet::Parser::Scope.new_for_test_harness("test_node") } + let(:scope) { Hiera::Scope.new(real) } + describe "#initialize" do it "should store the supplied puppet scope" do - real = {} - scope = Hiera::Scope.new(real) scope.real.should == real end end describe "#[]" do + it "should return nil when no value is found" do + scope["foo"].should == nil + end + it "should treat '' as nil" do - real = mock - real.expects(:lookupvar).with("foo").returns("") + real["foo"] = "" - scope = Hiera::Scope.new(real) scope["foo"].should == nil end - it "sould return found data" do - real = mock - real.expects(:lookupvar).with("foo").returns("bar") + it "should return found data" do + real["foo"] = "bar" - scope = Hiera::Scope.new(real) scope["foo"].should == "bar" end - it "should get calling_class and calling_module from puppet scope" do - real = mock - resource = mock - resource.expects(:name).returns("Foo::Bar").twice + it "preserves the case of a string that is found" do + real["foo"] = "CAPITAL!" + + scope["foo"].should == "CAPITAL!" + end + + it "aliases $module_name as calling_module" do + real["module_name"] = "the_module" + + scope["calling_module"].should == "the_module" + end + + it "uses the name of the of the scope's class as the calling_class" do + real.source = Puppet::Resource::Type.new(:hostclass, + "testing", + :module_name => "the_module") + + scope["calling_class"].should == "testing" + end + + it "downcases the calling_class" do + real.source = Puppet::Resource::Type.new(:hostclass, + "UPPER CASE", + :module_name => "the_module") + + scope["calling_class"].should == "upper case" + end - real.expects(:resource).returns(resource).twice + it "looks for the class which includes the defined type as the calling_class" do + parent = Puppet::Parser::Scope.new_for_test_harness("parent") + real.parent = parent + parent.source = Puppet::Resource::Type.new(:hostclass, + "name_of_the_class_including_the_definition", + :module_name => "class_module") + real.source = Puppet::Resource::Type.new(:definition, + "definition_name", + :module_name => "definition_module") - scope = Hiera::Scope.new(real) - scope["calling_class"].should == "foo::bar" - scope["calling_module"].should == "foo" + scope["calling_class"].should == "name_of_the_class_including_the_definition" end end describe "#include?" do it "should correctly report missing data" do - real = mock - real.expects(:lookupvar).with("foo").returns("") + real["foo"] = "" - scope = Hiera::Scope.new(real) scope.include?("foo").should == false end it "should always return true for calling_class and calling_module" do - real = mock - real.expects(:lookupvar).with("calling_class").never - real.expects(:lookupvar).with("calling_module").never - - scope = Hiera::Scope.new(real) scope.include?("calling_class").should == true scope.include?("calling_module").should == true end diff --git a/spec/unit/indirector/catalog/active_record_spec.rb b/spec/unit/indirector/catalog/active_record_spec.rb index 7c41e6dbb..183070698 100755 --- a/spec/unit/indirector/catalog/active_record_spec.rb +++ b/spec/unit/indirector/catalog/active_record_spec.rb @@ -4,20 +4,23 @@ require 'spec_helper' describe "Puppet::Resource::Catalog::ActiveRecord", :if => can_use_scratch_database? do include PuppetSpec::Files - require 'puppet/rails' - before :each do require 'puppet/indirector/catalog/active_record' setup_scratch_database end + after :each do + Puppet::Rails.teardown + end + let :terminus do Puppet::Resource::Catalog::ActiveRecord.new end it "should issue a deprecation warning" do Puppet.expects(:deprecation_warning).with() { |msg| msg =~ /ActiveRecord-based storeconfigs and inventory are deprecated/ } - terminus + + Puppet::Resource::Catalog::ActiveRecord.new end it "should be a subclass of the ActiveRecord terminus class" do diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb index 356b9fa8c..0d000451b 100755 --- a/spec/unit/indirector/catalog/compiler_spec.rb +++ b/spec/unit/indirector/catalog/compiler_spec.rb @@ -28,17 +28,12 @@ describe Puppet::Resource::Catalog::Compiler do node1 = stub 'node1', :merge => nil node2 = stub 'node2', :merge => nil compiler.stubs(:compile) - Puppet::Node.indirection.stubs(:find).with('node1', anything).returns(node1) - Puppet::Node.indirection.stubs(:find).with('node2', anything).returns(node2) + Puppet::Node.indirection.stubs(:find).with('node1', has_entry(:environment => anything)).returns(node1) + Puppet::Node.indirection.stubs(:find).with('node2', has_entry(:environment => anything)).returns(node2) compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node1', nil, :node => 'node1')) compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node2', nil, :node => 'node2')) end - - it "should provide a method for determining if the catalog is networked" do - compiler = Puppet::Resource::Catalog::Compiler.new - compiler.should respond_to(:networked?) - end end describe "when finding catalogs" do @@ -134,26 +129,6 @@ describe Puppet::Resource::Catalog::Compiler do Puppet::Parser::Compiler.expects(:compile).returns result @compiler.find(@request).should equal(result) end - - it "should benchmark the compile process" do - Puppet::Node.indirection.stubs(:find).returns(@node) - @compiler.stubs(:networked?).returns(true) - @compiler.expects(:benchmark).with do |level, message| - level == :notice and message =~ /^Compiled catalog/ - end - Puppet::Parser::Compiler.stubs(:compile) - @compiler.find(@request) - end - - it "should log the benchmark result" do - Puppet::Node.indirection.stubs(:find).returns(@node) - @compiler.stubs(:networked?).returns(true) - Puppet::Parser::Compiler.stubs(:compile) - - Puppet.expects(:notice).with { |msg| msg =~ /Compiled catalog/ } - - @compiler.find(@request) - end end describe "when extracting facts from the request" do @@ -180,7 +155,7 @@ describe Puppet::Resource::Catalog::Compiler do @facts.timestamp = Time.parse('2010-11-01') @now = Time.parse('2010-11-02') - Time.expects(:now).returns(@now) + Time.stubs(:now).returns(@now) @compiler.extract_facts_from_request(@request) @facts.timestamp.should == @now diff --git a/spec/unit/indirector/catalog/static_compiler_spec.rb b/spec/unit/indirector/catalog/static_compiler_spec.rb index 2bd255ba9..36480f0a8 100644 --- a/spec/unit/indirector/catalog/static_compiler_spec.rb +++ b/spec/unit/indirector/catalog/static_compiler_spec.rb @@ -137,7 +137,7 @@ describe Puppet::Resource::Catalog::StaticCompiler do # a real fileserver initialized for testing. Puppet::FileServing::Metadata. indirection.stubs(:find). - with() { |*args| args[0] == options[:source].sub('puppet:///','') }. + with() { |*args| args[0] == options[:source].sub('puppet:///','') and args[1] == {:links => :manage, :environment => nil}}. returns(fake_fileserver_metadata) # I want a resource that all the file resources require and another diff --git a/spec/unit/indirector/facts/inventory_active_record_spec.rb b/spec/unit/indirector/facts/inventory_active_record_spec.rb index 641ded751..8851ef718 100755 --- a/spec/unit/indirector/facts/inventory_active_record_spec.rb +++ b/spec/unit/indirector/facts/inventory_active_record_spec.rb @@ -1,171 +1,175 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/rails' describe "Puppet::Node::Facts::InventoryActiveRecord", :if => can_use_scratch_database? do include PuppetSpec::Files let(:terminus) { Puppet::Node::Facts::InventoryActiveRecord.new } - before :all do - require 'puppet/indirector/facts/inventory_active_record' - end - - after :each do - Puppet::Node::Facts.indirection.reset_terminus_class - end - before :each do - Puppet::Node.indirection.reset_terminus_class - Puppet::Node.indirection.cache_class = nil - + require 'puppet/indirector/facts/inventory_active_record' Puppet::Node::Facts.indirection.terminus_class = :inventory_active_record setup_scratch_database end - it "should issue a deprecation warning" do - Puppet.expects(:deprecation_warning).with() { |msg| msg =~ /ActiveRecord-based storeconfigs and inventory are deprecated/ } - terminus - end - - describe "#save" do - let(:node) { - Puppet::Rails::InventoryNode.new(:name => "foo", :timestamp => Time.now) - } - - let(:facts) { - Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") - } - - it "should retry on ActiveRecord error" do - Puppet::Rails::InventoryNode.expects(:create).twice.raises(ActiveRecord::StatementInvalid).returns node - - Puppet::Node::Facts.indirection.save(facts) - end - - it "should use an existing node if possible" do - node.save - Puppet::Node::Facts.indirection.save(facts) - - Puppet::Rails::InventoryNode.count.should == 1 - Puppet::Rails::InventoryNode.first.should == node - end - - it "should create a new node if one can't be found" do - # This test isn't valid if there are nodes to begin with - Puppet::Rails::InventoryNode.count.should == 0 - - Puppet::Node::Facts.indirection.save(facts) - - Puppet::Rails::InventoryNode.count.should == 1 - Puppet::Rails::InventoryNode.first.name.should == "foo" - end - - it "should save the facts" do - Puppet::Node::Facts.indirection.save(facts) - - Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should =~ [["uptime_days","60"],["kernel","Darwin"]] - end - - it "should remove the previous facts for an existing node" do - facts = Puppet::Node::Facts.new("foo", "uptime_days" => "30", "kernel" => "Darwin") - Puppet::Node::Facts.indirection.save(facts) - bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "35", "kernel" => "Linux") - foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "is_virtual" => "false") - Puppet::Node::Facts.indirection.save(bar_facts) - Puppet::Node::Facts.indirection.save(foo_facts) - - Puppet::Node::Facts.indirection.find("bar").should == bar_facts - Puppet::Node::Facts.indirection.find("foo").should == foo_facts - Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should_not include(["uptime_days", "30"], ["kernel", "Darwin"]) - end + after :each do + Puppet::Rails.teardown end - describe "#find" do - before do - @foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") - @bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "30", "kernel" => "Linux") - Puppet::Node::Facts.indirection.save(@foo_facts) - Puppet::Node::Facts.indirection.save(@bar_facts) - end - - it "should identify facts by node name" do - Puppet::Node::Facts.indirection.find("foo").should == @foo_facts - end - - it "should return nil if no node instance can be found" do - Puppet::Node::Facts.indirection.find("non-existent node").should == nil + context "under Ruby 1.x", :if => (RUBY_VERSION[0] == '1' and can_use_scratch_database?) do + describe "#initialize" do + it "should issue a deprecation warning" do + Puppet.expects(:deprecation_warning).with() { |msg| msg =~ /ActiveRecord-based storeconfigs and inventory are deprecated/ } + terminus + end + end + + describe "#save" do + let(:node) { + Puppet::Rails::InventoryNode.new(:name => "foo", :timestamp => Time.now) + } + + let(:facts) { + Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") + } + + it "should retry on ActiveRecord error" do + Puppet::Rails::InventoryNode.expects(:create).twice.raises(ActiveRecord::StatementInvalid).returns node + + Puppet::Node::Facts.indirection.save(facts) + end + + it "should use an existing node if possible" do + node.save + Puppet::Node::Facts.indirection.save(facts) + + Puppet::Rails::InventoryNode.count.should == 1 + Puppet::Rails::InventoryNode.first.should == node + end + + it "should create a new node if one can't be found" do + # This test isn't valid if there are nodes to begin with + Puppet::Rails::InventoryNode.count.should == 0 + + Puppet::Node::Facts.indirection.save(facts) + + Puppet::Rails::InventoryNode.count.should == 1 + Puppet::Rails::InventoryNode.first.name.should == "foo" + end + + it "should save the facts" do + Puppet::Node::Facts.indirection.save(facts) + + Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should =~ [["uptime_days","60"],["kernel","Darwin"]] + end + + it "should remove the previous facts for an existing node" do + facts = Puppet::Node::Facts.new("foo", "uptime_days" => "30", "kernel" => "Darwin") + Puppet::Node::Facts.indirection.save(facts) + bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "35", "kernel" => "Linux") + foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "is_virtual" => "false") + Puppet::Node::Facts.indirection.save(bar_facts) + Puppet::Node::Facts.indirection.save(foo_facts) + + Puppet::Node::Facts.indirection.find("bar").should == bar_facts + Puppet::Node::Facts.indirection.find("foo").should == foo_facts + Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should_not include(["uptime_days", "30"], ["kernel", "Darwin"]) + end + end + + describe "#find" do + before do + @foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") + @bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "30", "kernel" => "Linux") + Puppet::Node::Facts.indirection.save(@foo_facts) + Puppet::Node::Facts.indirection.save(@bar_facts) + end + + it "should identify facts by node name" do + Puppet::Node::Facts.indirection.find("foo").should == @foo_facts + end + + it "should return nil if no node instance can be found" do + Puppet::Node::Facts.indirection.find("non-existent node").should == nil + end + end + + describe "#search" do + def search_request(conditions) + Puppet::Indirector::Request.new(:facts, :search, nil, nil, conditions) + end + + before :each do + @now = Time.now + @foo = Puppet::Node::Facts.new("foo", "fact1" => "value1", "fact2" => "value2", "uptime_days" => "30") + @bar = Puppet::Node::Facts.new("bar", "fact1" => "value1", "uptime_days" => "60") + @baz = Puppet::Node::Facts.new("baz", "fact1" => "value2", "fact2" => "value1", "uptime_days" => "90") + @bat = Puppet::Node::Facts.new("bat") + @foo.timestamp = @now - 3600*1 + @bar.timestamp = @now - 3600*3 + @baz.timestamp = @now - 3600*5 + @bat.timestamp = @now - 3600*7 + [@foo, @bar, @baz, @bat].each {|facts| Puppet::Node::Facts.indirection.save(facts)} + end + + it "should return node names that match 'equal' constraints" do + request = search_request('facts.fact1.eq' => 'value1', + 'facts.fact2.eq' => 'value2') + terminus.search(request).should == ["foo"] + end + + it "should return node names that match 'not equal' constraints" do + request = search_request('facts.fact1.ne' => 'value2') + terminus.search(request).should == ["bar","foo"] + end + + it "should return node names that match strict inequality constraints" do + request = search_request('facts.uptime_days.gt' => '20', + 'facts.uptime_days.lt' => '70') + terminus.search(request).should == ["bar","foo"] + end + + it "should return node names that match non-strict inequality constraints" do + request = search_request('facts.uptime_days.ge' => '30', + 'facts.uptime_days.le' => '60') + terminus.search(request).should == ["bar","foo"] + end + + it "should return node names whose facts are within a given timeframe" do + request = search_request('meta.timestamp.ge' => @now - 3600*5, + 'meta.timestamp.le' => @now - 3600*1) + terminus.search(request).should == ["bar","baz","foo"] + end + + it "should return node names whose facts are from a specific time" do + request = search_request('meta.timestamp.eq' => @now - 3600*3) + terminus.search(request).should == ["bar"] + end + + it "should return node names whose facts are not from a specific time" do + request = search_request('meta.timestamp.ne' => @now - 3600*1) + terminus.search(request).should == ["bar","bat","baz"] + end + + it "should perform strict searches on nodes by timestamp" do + request = search_request('meta.timestamp.gt' => @now - 3600*5, + 'meta.timestamp.lt' => @now - 3600*1) + terminus.search(request).should == ["bar"] + end + + it "should search nodes based on both facts and timestamp values" do + request = search_request('facts.uptime_days.gt' => '45', + 'meta.timestamp.lt' => @now - 3600*4) + terminus.search(request).should == ["baz"] + end end end - describe "#search" do - def search_request(conditions) - Puppet::Indirector::Request.new(:facts, :search, nil, nil, conditions) - end - - before :each do - @now = Time.now - @foo = Puppet::Node::Facts.new("foo", "fact1" => "value1", "fact2" => "value2", "uptime_days" => "30") - @bar = Puppet::Node::Facts.new("bar", "fact1" => "value1", "uptime_days" => "60") - @baz = Puppet::Node::Facts.new("baz", "fact1" => "value2", "fact2" => "value1", "uptime_days" => "90") - @bat = Puppet::Node::Facts.new("bat") - @foo.timestamp = @now - 3600*1 - @bar.timestamp = @now - 3600*3 - @baz.timestamp = @now - 3600*5 - @bat.timestamp = @now - 3600*7 - [@foo, @bar, @baz, @bat].each {|facts| Puppet::Node::Facts.indirection.save(facts)} - end - - it "should return node names that match 'equal' constraints" do - request = search_request('facts.fact1.eq' => 'value1', - 'facts.fact2.eq' => 'value2') - terminus.search(request).should == ["foo"] - end - - it "should return node names that match 'not equal' constraints" do - request = search_request('facts.fact1.ne' => 'value2') - terminus.search(request).should == ["bar","foo"] - end - - it "should return node names that match strict inequality constraints" do - request = search_request('facts.uptime_days.gt' => '20', - 'facts.uptime_days.lt' => '70') - terminus.search(request).should == ["bar","foo"] - end - - it "should return node names that match non-strict inequality constraints" do - request = search_request('facts.uptime_days.ge' => '30', - 'facts.uptime_days.le' => '60') - terminus.search(request).should == ["bar","foo"] - end - - it "should return node names whose facts are within a given timeframe" do - request = search_request('meta.timestamp.ge' => @now - 3600*5, - 'meta.timestamp.le' => @now - 3600*1) - terminus.search(request).should == ["bar","baz","foo"] - end - - it "should return node names whose facts are from a specific time" do - request = search_request('meta.timestamp.eq' => @now - 3600*3) - terminus.search(request).should == ["bar"] - end - - it "should return node names whose facts are not from a specific time" do - request = search_request('meta.timestamp.ne' => @now - 3600*1) - terminus.search(request).should == ["bar","bat","baz"] - end - - it "should perform strict searches on nodes by timestamp" do - request = search_request('meta.timestamp.gt' => @now - 3600*5, - 'meta.timestamp.lt' => @now - 3600*1) - terminus.search(request).should == ["bar"] - end - - it "should search nodes based on both facts and timestamp values" do - request = search_request('facts.uptime_days.gt' => '45', - 'meta.timestamp.lt' => @now - 3600*4) - terminus.search(request).should == ["baz"] + context "under Ruby 2.x", :if => (RUBY_VERSION[0] == '2' and can_use_scratch_database?) do + describe "#initialize" do + it "should raise error under Ruby 2" do + lambda { terminus }.should raise_error(Puppet::Error, /Ruby 2/) + end end end end - diff --git a/spec/unit/indirector/indirection_spec.rb b/spec/unit/indirector/indirection_spec.rb index 0898a2782..43d709acf 100755 --- a/spec/unit/indirector/indirection_spec.rb +++ b/spec/unit/indirector/indirection_spec.rb @@ -229,6 +229,11 @@ describe Puppet::Indirector::Indirection do @indirection.find("me").should equal(@instance) end + it "should return false if the instance is false" do + @terminus.expects(:find).returns(false) + @indirection.find("me").should equal(false) + end + it "should set the expiration date on any instances without one set" do @terminus.stubs(:find).returns(@instance) diff --git a/spec/unit/indirector/resource/active_record_spec.rb b/spec/unit/indirector/resource/active_record_spec.rb index 4045101e3..934c208e4 100755 --- a/spec/unit/indirector/resource/active_record_spec.rb +++ b/spec/unit/indirector/resource/active_record_spec.rb @@ -1,40 +1,27 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/rails' require 'puppet/node/facts' describe "Puppet::Resource::ActiveRecord", :if => can_use_scratch_database? do include PuppetSpec::Files before :each do + require 'puppet/indirector/resource/active_record' setup_scratch_database - Puppet[:storeconfigs] = true end - subject { - require 'puppet/indirector/resource/active_record' - Puppet::Resource.indirection.terminus(:active_record) - } - - it "should automatically initialize Rails" do - # Other tests in the suite may have established the connection, which will - # linger; the assertion is just to enforce our assumption about the call, - # not because I *really* want to test ActiveRecord works. Better to have - # an early failure than wonder why the test overall doesn't DTRT. - ActiveRecord::Base.remove_connection - ActiveRecord::Base.should_not be_connected - subject.should be - ActiveRecord::Base.should be_connected + after :each do + Puppet::Rails.teardown end + subject { Puppet::Resource.indirection.terminus(:active_record) } + it "should issue a deprecation warning" do Puppet.expects(:deprecation_warning).with() { |msg| msg =~ /ActiveRecord-based storeconfigs and inventory are deprecated/ } Puppet::Resource::ActiveRecord.new end describe "#search" do - before :each do Puppet::Rails.init end - def search(type, host = 'default.local', filter = nil) args = { :host => host, :filter => filter } subject.search(Puppet::Resource.indirection.request(:search, type, nil, args)) @@ -79,10 +66,6 @@ describe "Puppet::Resource::ActiveRecord", :if => can_use_scratch_database? do end describe "#build_active_record_query" do - before :each do - Puppet::Rails.init - end - let :type do 'Notify' end def query(type, host, filter = nil) diff --git a/spec/unit/indirector_spec.rb b/spec/unit/indirector_spec.rb index 76f242345..334fdfdc4 100755 --- a/spec/unit/indirector_spec.rb +++ b/spec/unit/indirector_spec.rb @@ -79,6 +79,12 @@ describe Puppet::Indirector, "when registering an indirection" do before do @thingie = Class.new do extend Puppet::Indirector + + # override Class#name, since we're not naming this ephemeral class + def self.name + 'Thingie' + end + attr_reader :name def initialize(name) @name = name @@ -108,7 +114,7 @@ describe Puppet::Indirector, "when registering an indirection" do it "should pass any provided options to the indirection during initialization" do klass = mock 'terminus class' - Puppet::Indirector::Indirection.expects(:new).with(@thingie, :first, {:some => :options}) + Puppet::Indirector::Indirection.expects(:new).with(@thingie, :first, {:some => :options, :indirected_class => 'Thingie'}) @indirection = @thingie.indirects :first, :some => :options end diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb index a750c9844..0fb64641a 100755 --- a/spec/unit/interface/action_builder_spec.rb +++ b/spec/unit/interface/action_builder_spec.rb @@ -1,6 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/interface/action_builder' +require 'puppet/interface' require 'puppet/network/format_handler' describe Puppet::Interface::ActionBuilder do diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb index d1411d7b8..b1cc89fee 100755 --- a/spec/unit/interface/action_manager_spec.rb +++ b/spec/unit/interface/action_manager_spec.rb @@ -1,9 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' -# This is entirely an internal class for Interface, so we have to load it instead of our class. require 'puppet/interface' -require 'puppet/face' class ActionManagerTester include Puppet::Interface::ActionManager diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb index 463388989..1eabfc5e1 100755 --- a/spec/unit/interface/action_spec.rb +++ b/spec/unit/interface/action_spec.rb @@ -1,6 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/interface/action' +require 'puppet/interface' describe Puppet::Interface::Action do describe "when validating the action name" do diff --git a/spec/unit/interface/documentation_spec.rb b/spec/unit/interface/documentation_spec.rb index 40f3e7b3b..27ade8574 100755 --- a/spec/unit/interface/documentation_spec.rb +++ b/spec/unit/interface/documentation_spec.rb @@ -1,8 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/interface' -require 'puppet/interface/option' -require 'puppet/interface/documentation' class Puppet::Interface::TinyDocs::Test include Puppet::Interface::TinyDocs diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb index 062b4bc05..3a2082e95 100755 --- a/spec/unit/interface/face_collection_spec.rb +++ b/spec/unit/interface/face_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'tmpdir' -require 'puppet/interface/face_collection' +require 'puppet/interface' describe Puppet::Interface::FaceCollection do # To avoid cross-pollution we have to save and restore both the hash diff --git a/spec/unit/interface/option_builder_spec.rb b/spec/unit/interface/option_builder_spec.rb index 9ace89e98..a8c5bda39 100755 --- a/spec/unit/interface/option_builder_spec.rb +++ b/spec/unit/interface/option_builder_spec.rb @@ -1,4 +1,4 @@ -require 'puppet/interface/option_builder' +require 'puppet/interface' describe Puppet::Interface::OptionBuilder do let :face do Puppet::Interface.new(:option_builder_testing, '0.0.1') end diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb index 7d32ebc52..55ae6a50f 100755 --- a/spec/unit/interface/option_spec.rb +++ b/spec/unit/interface/option_spec.rb @@ -1,5 +1,4 @@ require 'puppet/interface' -require 'puppet/interface/option' describe Puppet::Interface::Option do let :face do Puppet::Interface.new(:option_testing, '0.0.1') end diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index 24cc16ee9..8bf428917 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -592,30 +592,28 @@ describe Puppet::Module do end it "should be able to tell if there are local changes" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - modpath = tmpdir('modpath') - foo_checksum = 'acbd18db4cc2f85cedef654fccc4a4d8' - checksummed_module = PuppetSpec::Modules.create( - 'changed', - modpath, - :metadata => { - :checksums => { - "foo" => foo_checksum, - } + modpath = tmpdir('modpath') + foo_checksum = 'acbd18db4cc2f85cedef654fccc4a4d8' + checksummed_module = PuppetSpec::Modules.create( + 'changed', + modpath, + :metadata => { + :checksums => { + "foo" => foo_checksum, } - ) + } + ) - foo_path = Pathname.new(File.join(checksummed_module.path, 'foo')) + foo_path = Pathname.new(File.join(checksummed_module.path, 'foo')) - IO.binwrite(foo_path, 'notfoo') - Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should_not == foo_checksum - checksummed_module.has_local_changes?.should be_true + IO.binwrite(foo_path, 'notfoo') + Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should_not == foo_checksum + checksummed_module.has_local_changes?.should be_true - IO.binwrite(foo_path, 'foo') + IO.binwrite(foo_path, 'foo') - Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should == foo_checksum - checksummed_module.has_local_changes?.should be_false - end + Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should == foo_checksum + checksummed_module.has_local_changes?.should be_false end it "should know what other modules require it" do diff --git a/spec/unit/module_tool/application_spec.rb b/spec/unit/module_tool/application_spec.rb index 2be9d9633..d817cca61 100644 --- a/spec/unit/module_tool/application_spec.rb +++ b/spec/unit/module_tool/application_spec.rb @@ -12,9 +12,7 @@ describe Puppet::ModuleTool::Applications::Application do good_versions.each do |ver| it "should accept version string #{ver}" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - app.parse_filename("puppetlabs-ntp-#{ver}") - end + app.parse_filename("puppetlabs-ntp-#{ver}") end end diff --git a/spec/unit/module_tool/applications/application_spec.rb b/spec/unit/module_tool/applications/application_spec.rb deleted file mode 100644 index c559e4dd9..000000000 --- a/spec/unit/module_tool/applications/application_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' -require 'puppet/module_tool/applications' - -describe Puppet::ModuleTool::Applications do - module Puppet::ModuleTool - module Applications - class Fake < Application - end - end - end - - it "should raise an error on microsoft windows" do - Puppet.features.stubs(:microsoft_windows?).returns true - expect { Puppet::ModuleTool::Applications::Fake.new }.to raise_error( - Puppet::Error, - "`puppet module` actions are currently not supported on Microsoft Windows" - ) - end -end diff --git a/spec/unit/module_tool/applications/builder_spec.rb b/spec/unit/module_tool/applications/builder_spec.rb new file mode 100644 index 000000000..3454e0981 --- /dev/null +++ b/spec/unit/module_tool/applications/builder_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'puppet/module_tool/applications' +require 'puppet_spec/modules' + +describe Puppet::ModuleTool::Applications::Builder do + include PuppetSpec::Files + + let(:path) { tmpdir("working_dir") } + let(:module_name) { 'myusername-mytarball' } + let(:version) { '0.0.1' } + let(:release_name) { "#{module_name}-#{version}" } + let(:tarball) { File.join(path, 'pkg', release_name) + ".tar.gz" } + let(:builder) { Puppet::ModuleTool::Applications::Builder.new(path) } + + before :each do + File.open(File.join(path, 'Modulefile'), 'w') do |f| + f.write(<<EOM) +name '#{module_name}' +version '#{version}' +source 'http://github.com/testing/#{module_name}' +author 'testing' +license 'Apache License Version 2.0' +summary 'Puppet testing module' +description 'This module can be used for basic testing' +project_page 'http://github.com/testing/#{module_name}' +EOM + end + end + + it "should attempt to create a module relative to the pkg directory" do + tarrer = mock('tarrer') + Puppet::ModuleTool::Tar.expects(:instance).with(module_name).returns(tarrer) + Dir.expects(:chdir).with(File.join(path, 'pkg')).yields + tarrer.expects(:pack).with(release_name, tarball) + + builder.run + end +end diff --git a/spec/unit/module_tool/applications/checksummer_spec.rb b/spec/unit/module_tool/applications/checksummer_spec.rb new file mode 100644 index 000000000..96010f96c --- /dev/null +++ b/spec/unit/module_tool/applications/checksummer_spec.rb @@ -0,0 +1,134 @@ +require 'spec_helper' +require 'puppet/module_tool/applications' + +describe Puppet::ModuleTool::Applications::Checksummer, :unless => Puppet.features.microsoft_windows? do + subject { + Puppet::ModuleTool::Applications::Checksummer.new(module_install_path) + } + + let(:module_install_path) { 'foo' } + let(:module_metadata_file) { 'metadata.json' } + + let(:module_install_pathname) { + module_install_pathname = mock() + Pathname.expects(:new).with(module_install_path).\ + returns(module_install_pathname) + module_install_pathname + } + + def stub_module_file_pathname(relative_path, present) + module_file_pathname = mock() do + expects(:exist?).with().returns(present) + end + + module_install_pathname.expects(:+).with(relative_path).\ + returns(module_file_pathname) + + module_file_pathname + end + + context %q{when metadata.json doesn't exist in the specified module install path} do + before(:each) do + stub_module_file_pathname(module_metadata_file, false) + subject.expects(:metadata_file).with().\ + returns(module_install_pathname + module_metadata_file) + end + + it 'raises an ArgumentError exception' do + lambda { + subject.run + }.should raise_error(ArgumentError, 'No metadata.json found.') + end + end + + context 'when metadata.json exists in the specified module install path' do + module_files = { + 'README' => '1', + 'CHANGELOG' => '2', + 'Modulefile' => '3', + } + let(:module_files) { module_files } + let(:checksum_computer) { + checksum_computer = mock() + Puppet::ModuleTool::Checksums.\ + expects(:new).with(module_install_pathname).\ + returns(checksum_computer) + checksum_computer + } + # all possible combinations (of all lengths) of the module files + module_files_combination = + 1.upto(module_files.size()).inject([]) { |module_files_combination, n| + module_files.keys.combination(n) { |combination| + module_files_combination << combination + } + module_files_combination + } + + def stub_module_file_pathname_with_checksum(relative_path, checksum) + module_file_pathname = + stub_module_file_pathname(relative_path, present = !checksum.nil?) + # mock the call of Puppet::ModuleTool::Checksums#checksum + expectation = checksum_computer.\ + expects(:checksum).with(module_file_pathname) + if present + # return the cheksum directly + expectation.returns(checksum) + else + # if the file is not present, then the method should not be called + expectation.times(0) + end + module_file_pathname + end + + def stub_module_files(overrides = {}) + overrides.reject! { |key, value| + !module_files.include?(key) + } + module_files.merge(overrides).each { |relative_path, checksum| + stub_module_file_pathname_with_checksum(relative_path, checksum) + } + end + + before(:each) do + stub_module_file_pathname(module_metadata_file, true) + subject.expects(:metadata_file).with().\ + returns(module_install_pathname + module_metadata_file) + subject.expects(:metadata).with().\ + returns({ 'checksums' => module_files }) + end + + module_files_combination.each do |removed_files| + it "reports removed file(s) #{removed_files.inspect}" do + stub_module_files( + removed_files.inject({}) { |overrides, removed_file| + overrides[removed_file] = nil + overrides + } + ) + + subject.run.should == removed_files + end + end + + module_files_combination.each do |modified_files| + it "reports modified file(s) #{modified_files.inspect}" do + stub_module_files( + modified_files.inject({}) { |overrides, modified_file| + modified_checksum = module_files[modified_file].to_s.succ + modified_checksum = ' ' if modified_checksum.empty? + overrides[modified_file] = modified_checksum + overrides + } + ) + + subject.run.should == modified_files + end + end + + it 'does not report unmodified files' do + stub_module_files() + + subject.run.should == [] + end + end +end diff --git a/spec/unit/module_tool/applications/installer_spec.rb b/spec/unit/module_tool/applications/installer_spec.rb index 8aec49a00..dbec21fa1 100644 --- a/spec/unit/module_tool/applications/installer_spec.rb +++ b/spec/unit/module_tool/applications/installer_spec.rb @@ -3,7 +3,7 @@ require 'puppet/module_tool/applications' require 'puppet_spec/modules' require 'semver' -describe Puppet::ModuleTool::Applications::Installer, :fails_on_windows => true do +describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do @@ -80,18 +80,14 @@ describe Puppet::ModuleTool::Applications::Installer, :fails_on_windows => true describe "the behavior of .is_module_package?" do it "should return true when file is a module package" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - installer = installer_class.new("foo", forge, install_dir, options) - installer.send(:is_module_package?, stdlib_pkg).should be_true - end + installer = installer_class.new("foo", forge, install_dir, options) + installer.send(:is_module_package?, stdlib_pkg).should be_true end it "should return false when file is not a module package" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - installer = installer_class.new("foo", forge, install_dir, options) - installer.send(:is_module_package?, "pmtacceptance-apollo-0.0.2.tar"). - should be_false - end + installer = installer_class.new("foo", forge, install_dir, options) + installer.send(:is_module_package?, "pmtacceptance-apollo-0.0.2.tar"). + should be_false end end @@ -102,15 +98,13 @@ describe Puppet::ModuleTool::Applications::Installer, :fails_on_windows => true end it "should install the requested module" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). - returns(unpacker) - results = installer_class.run('pmtacceptance-stdlib', forge, install_dir, options) - results[:installed_modules].length == 1 - results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" - results[:installed_modules][0][:version][:vstring].should == "1.0.0" - end + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). + returns(unpacker) + results = installer_class.run('pmtacceptance-stdlib', forge, install_dir, options) + results[:installed_modules].length == 1 + results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" + results[:installed_modules][0][:version][:vstring].should == "1.0.0" end context "should check the target directory" do @@ -129,98 +123,85 @@ describe Puppet::ModuleTool::Applications::Installer, :fails_on_windows => true end it "(#15202) prepares the install directory" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - expect_normal_unpacker - install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest") + expect_normal_unpacker + install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest") - results = installer.run + results = installer.run - results[:installed_modules].length.should eq 1 - results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" - results[:installed_modules][0][:version][:vstring].should == "1.0.0" - end + results[:installed_modules].length.should eq 1 + results[:installed_modules][0][:module].should == "pmtacceptance-stdlib" + results[:installed_modules][0][:version][:vstring].should == "1.0.0" end it "(#15202) reports an error when the install directory cannot be prepared" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest"). - raises(Puppet::ModuleTool::Errors::PermissionDeniedCreateInstallDirectoryError.new("original", :module => "pmtacceptance-stdlib")) + install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest"). + raises(Puppet::ModuleTool::Errors::PermissionDeniedCreateInstallDirectoryError.new("original", :module => "pmtacceptance-stdlib")) - results = installer.run + results = installer.run - results[:result].should == :failure - results[:error][:oneline].should =~ /Permission is denied/ - end + results[:result].should == :failure + results[:error][:oneline].should =~ /Permission is denied/ end end context "when the requested module has dependencies" do it "should install dependencies" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). - returns(unpacker) - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). - returns(unpacker) - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-java-1.7.1.tar.gz', options). - returns(unpacker) - - results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) - installed_dependencies = results[:installed_modules][0][:dependencies] - - dependencies = installed_dependencies.inject({}) do |result, dep| - result[dep[:module]] = dep[:version][:vstring] - result - end - - dependencies.length.should == 2 - dependencies['pmtacceptance-java'].should == '1.7.1' - dependencies['pmtacceptance-stdlib'].should == '1.0.0' + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options). + returns(unpacker) + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). + returns(unpacker) + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-java-1.7.1.tar.gz', options). + returns(unpacker) + + results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) + installed_dependencies = results[:installed_modules][0][:dependencies] + + dependencies = installed_dependencies.inject({}) do |result, dep| + result[dep[:module]] = dep[:version][:vstring] + result end + + dependencies.length.should == 2 + dependencies['pmtacceptance-java'].should == '1.7.1' + dependencies['pmtacceptance-stdlib'].should == '1.0.0' end it "should install requested module if the '--force' flag is used" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - options = { :force => true, :target_dir => modpath1 } - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). - returns(unpacker) - results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) - results[:installed_modules][0][:module].should == "pmtacceptance-apollo" - end + options = { :force => true, :target_dir => modpath1 } + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). + returns(unpacker) + results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) + results[:installed_modules][0][:module].should == "pmtacceptance-apollo" end it "should not install dependencies if the '--force' flag is used" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - options = { :force => true, :target_dir => modpath1 } - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). - returns(unpacker) - results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) - dependencies = results[:installed_modules][0][:dependencies] - dependencies.should == [] - end + options = { :force => true, :target_dir => modpath1 } + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). + returns(unpacker) + results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) + dependencies = results[:installed_modules][0][:dependencies] + dependencies.should == [] end it "should not install dependencies if the '--ignore-dependencies' flag is used" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - options = { :ignore_dependencies => true, :target_dir => modpath1 } - Puppet::ModuleTool::Applications::Unpacker.expects(:new). - with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). - returns(unpacker) - results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) - dependencies = results[:installed_modules][0][:dependencies] - dependencies.should == [] - end + options = { :ignore_dependencies => true, :target_dir => modpath1 } + Puppet::ModuleTool::Applications::Unpacker.expects(:new). + with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options). + returns(unpacker) + results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) + dependencies = results[:installed_modules][0][:dependencies] + dependencies.should == [] end it "should set an error if dependencies can't be resolved" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - options = { :version => '0.0.1', :target_dir => modpath1 } - oneline = "'pmtacceptance-apollo' (v0.0.1) requested; Invalid dependency cycle" - multiline = <<-MSG.strip + options = { :version => '0.0.1', :target_dir => modpath1 } + oneline = "'pmtacceptance-apollo' (v0.0.1) requested; Invalid dependency cycle" + multiline = <<-MSG.strip Could not install module 'pmtacceptance-apollo' (v0.0.1) No version of 'pmtacceptance-stdlib' will satisfy dependencies You specified 'pmtacceptance-apollo' (v0.0.1), @@ -229,11 +210,10 @@ Could not install module 'pmtacceptance-apollo' (v0.0.1) Use `puppet module install --force` to install this module anyway MSG - results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) - results[:result].should == :failure - results[:error][:oneline].should == oneline - results[:error][:multiline].should == multiline - end + results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options) + results[:result].should == :failure + results[:error][:oneline].should == oneline + results[:error][:multiline].should == multiline end end diff --git a/spec/unit/module_tool/applications/searcher_spec.rb b/spec/unit/module_tool/applications/searcher_spec.rb index 2e9a3688f..53e52dd2b 100644 --- a/spec/unit/module_tool/applications/searcher_spec.rb +++ b/spec/unit/module_tool/applications/searcher_spec.rb @@ -8,9 +8,7 @@ describe Puppet::ModuleTool::Applications::Searcher do describe "when searching" do let(:forge) { mock 'forge' } let(:searcher) do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - described_class.new('search_term', forge) - end + described_class.new('search_term', forge) end it "should return results from a forge query when successful" do diff --git a/spec/unit/module_tool/applications/uninstaller_spec.rb b/spec/unit/module_tool/applications/uninstaller_spec.rb index 099b1d7ba..9ec52fc4c 100644 --- a/spec/unit/module_tool/applications/uninstaller_spec.rb +++ b/spec/unit/module_tool/applications/uninstaller_spec.rb @@ -64,23 +64,19 @@ describe Puppet::ModuleTool::Applications::Uninstaller do context "when the module is installed" do it "should uninstall the module" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) + PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) - results = @uninstaller.new("puppetlabs-foo", options).run - results[:affected_modules].first.forge_name.should == "puppetlabs/foo" - end + results = @uninstaller.new("puppetlabs-foo", options).run + results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end it "should only uninstall the requested module" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) - PuppetSpec::Modules.create('bar', modpath1, :metadata => bar_metadata) + PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) + PuppetSpec::Modules.create('bar', modpath1, :metadata => bar_metadata) - results = @uninstaller.new("puppetlabs-foo", options).run - results[:affected_modules].length == 1 - results[:affected_modules].first.forge_name.should == "puppetlabs/foo" - end + results = @uninstaller.new("puppetlabs-foo", options).run + results[:affected_modules].length == 1 + results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end it "should uninstall fail if a module exists twice in the modpath" do @@ -93,16 +89,14 @@ describe Puppet::ModuleTool::Applications::Uninstaller do context "when options[:version] is specified" do it "should uninstall the module if the version matches" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) + PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) - options[:version] = "1.0.0" + options[:version] = "1.0.0" - results = @uninstaller.new("puppetlabs-foo", options).run - results[:affected_modules].length.should == 1 - results[:affected_modules].first.forge_name.should == "puppetlabs/foo" - results[:affected_modules].first.version.should == "1.0.0" - end + results = @uninstaller.new("puppetlabs-foo", options).run + results[:affected_modules].length.should == 1 + results[:affected_modules].first.forge_name.should == "puppetlabs/foo" + results[:affected_modules].first.version.should == "1.0.0" end it "should not uninstall the module if the version does not match" do @@ -137,13 +131,11 @@ describe Puppet::ModuleTool::Applications::Uninstaller do context "when the module does not have local changes" do it "should uninstall the module" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) + PuppetSpec::Modules.create('foo', modpath1, :metadata => foo_metadata) - results = @uninstaller.new("puppetlabs-foo", options).run - results[:affected_modules].length.should == 1 - results[:affected_modules].first.forge_name.should == "puppetlabs/foo" - end + results = @uninstaller.new("puppetlabs-foo", options).run + results[:affected_modules].length.should == 1 + results[:affected_modules].first.forge_name.should == "puppetlabs/foo" end end diff --git a/spec/unit/module_tool/applications/unpacker_spec.rb b/spec/unit/module_tool/applications/unpacker_spec.rb index b383c3905..15d095003 100644 --- a/spec/unit/module_tool/applications/unpacker_spec.rb +++ b/spec/unit/module_tool/applications/unpacker_spec.rb @@ -5,63 +5,30 @@ require 'puppet_spec/modules' describe Puppet::ModuleTool::Applications::Unpacker do include PuppetSpec::Files - let(:target) { tmpdir("unpacker") } - - context "initialization" do - it "should support filename and basic options" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - Puppet::ModuleTool::Applications::Unpacker.new("myusername-mytarball-1.0.0.tar.gz", :target_dir => target) - end - end - - it "should raise ArgumentError when filename is invalid" do - expect { Puppet::ModuleTool::Applications::Unpacker.new("invalid.tar.gz", :target_dir => target) }.to raise_error(ArgumentError) - end + let(:target) { tmpdir("unpacker") } + let(:module_name) { 'myusername-mytarball' } + let(:filename) { tmpdir("module") + "/#{module_name}-1.0.0.tar.gz" } + let(:working_dir) { tmpdir("working_dir") } + let(:unpacker) do + Puppet::ModuleTool::Applications::Unpacker.new(filename, :target_dir => target) end - context "#run" do - let(:cache_base_path) { Pathname.new(tmpdir("unpacker")) } - let(:filename) { tmpdir("module") + "/myusername-mytarball-1.0.0.tar.gz" } - let(:build_dir) { Pathname.new(tmpdir("build_dir")) } - let(:unpacker) do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - Puppet::ModuleTool::Applications::Unpacker.new(filename, :target_dir => target) - end - end + before :each do + Puppet.settings[:module_working_dir] = working_dir + end - before :each do - # Mock redhat for most test cases - Facter.stubs(:value).with("osfamily").returns("Redhat") - build_dir.stubs(:mkpath => nil, :rmtree => nil, :children => []) - unpacker.stubs(:build_dir).at_least_once.returns(build_dir) - FileUtils.stubs(:mv) - end + it "should raise ArgumentError when filename is invalid" do + expect { Puppet::ModuleTool::Applications::Unpacker.new("invalid.tar.gz", :target_dir => target) }.to raise_error(ArgumentError) + end - context "on linux" do - it "should attempt to untar file to temporary location using system tar" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - Puppet::Util::Execution.expects(:execute).with("tar xzf #{filename} -C #{build_dir}").returns(true) - unpacker.run - end - end + it "should attempt to untar file to temporary location" do + untarrer = mock('untarrer') + Puppet::ModuleTool::Tar.expects(:instance).with(module_name).returns(untarrer) + untarrer.expects(:unpack).with(filename, regexp_matches(/^#{Regexp.escape(working_dir)}/)) do |src, dest| + FileUtils.mkdir(File.join(dest, 'extractedmodule')) end - context "on solaris" do - before :each do - Facter.expects(:value).with("osfamily").returns("Solaris") - end - - it "should attempt to untar file to temporary location using gnu tar" do - Puppet::Util.stubs(:which).with('gtar').returns('/usr/sfw/bin/gtar') - Puppet::Util::Execution.expects(:execute).with("gtar xzf #{filename} -C #{build_dir}").returns(true) - unpacker.run - end - - it "should throw exception if gtar is not in the path exists" do - Puppet::Util.stubs(:which).with('gtar').returns(nil) - expect { unpacker.run }.to raise_error RuntimeError, "Cannot find the command 'gtar'. Make sure GNU tar is installed, and is in your PATH." - end - end + unpacker.run + File.should be_directory(File.join(target, 'mytarball')) end - end diff --git a/spec/unit/module_tool/tar/gnu_spec.rb b/spec/unit/module_tool/tar/gnu_spec.rb new file mode 100644 index 000000000..0d0e9594b --- /dev/null +++ b/spec/unit/module_tool/tar/gnu_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' +require 'puppet/module_tool' + +describe Puppet::ModuleTool::Tar::Gnu do + let(:sourcefile) { '/the/module.tar.gz' } + let(:destdir) { '/the/dest/dir' } + let(:sourcedir) { '/the/src/dir' } + let(:destfile) { '/the/dest/file.tar.gz' } + + it "unpacks a tar file" do + Puppet::Util::Execution.expects(:execute).with("tar xzf #{sourcefile} -C #{destdir}") + subject.unpack(sourcefile, destdir) + end + + it "packs a tar file" do + Puppet::Util::Execution.expects(:execute).with("tar cf - #{sourcedir} | gzip -c > #{destfile}") + subject.pack(sourcedir, destfile) + end +end diff --git a/spec/unit/module_tool/tar/mini_spec.rb b/spec/unit/module_tool/tar/mini_spec.rb new file mode 100644 index 000000000..e7e295207 --- /dev/null +++ b/spec/unit/module_tool/tar/mini_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' +require 'puppet/module_tool' + +describe Puppet::ModuleTool::Tar::Mini, :if => (Puppet.features.minitar? and Puppet.features.zlib?) do + let(:sourcefile) { '/the/module.tar.gz' } + let(:destdir) { File.expand_path '/the/dest/dir' } + let(:sourcedir) { '/the/src/dir' } + let(:destfile) { '/the/dest/file.tar.gz' } + let(:minitar) { described_class.new('nginx') } + + it "unpacks a tar file" do + unpacks_the_entry(:file_start, 'thefile') + + minitar.unpack(sourcefile, destdir) + end + + it "does not allow an absolute path" do + unpacks_the_entry(:file_start, '/thefile') + + expect { + minitar.unpack(sourcefile, destdir) + }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, + "Attempt to install file into \"/thefile\" under \"#{destdir}\"") + end + + it "does not allow a file to be written outside the destination directory" do + unpacks_the_entry(:file_start, '../../thefile') + + expect { + minitar.unpack(sourcefile, destdir) + }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, + "Attempt to install file into \"#{File.expand_path('/the/thefile')}\" under \"#{destdir}\"") + end + + it "does not allow a directory to be written outside the destination directory" do + unpacks_the_entry(:dir, '../../thedir') + + expect { + minitar.unpack(sourcefile, destdir) + }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, + "Attempt to install file into \"#{File.expand_path('/the/thedir')}\" under \"#{destdir}\"") + end + + it "packs a tar file" do + writer = mock('GzipWriter') + + Zlib::GzipWriter.expects(:open).with(destfile).yields(writer) + Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer) + + minitar.pack(sourcedir, destfile) + end + + def unpacks_the_entry(type, name) + reader = mock('GzipReader') + + Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader) + Archive::Tar::Minitar.expects(:unpack).with(reader, destdir).yields(type, name, nil) + end +end diff --git a/spec/unit/module_tool/tar/solaris_spec.rb b/spec/unit/module_tool/tar/solaris_spec.rb new file mode 100644 index 000000000..20e3e2e2e --- /dev/null +++ b/spec/unit/module_tool/tar/solaris_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' +require 'puppet/module_tool' + +describe Puppet::ModuleTool::Tar::Solaris do + let(:sourcefile) { '/the/module.tar.gz' } + let(:destdir) { '/the/dest/dir' } + let(:sourcedir) { '/the/src/dir' } + let(:destfile) { '/the/dest/file.tar.gz' } + + it "unpacks a tar file" do + Puppet::Util::Execution.expects(:execute).with("gtar xzf #{sourcefile} -C #{destdir}") + subject.unpack(sourcefile, destdir) + end + + it "packs a tar file" do + Puppet::Util::Execution.expects(:execute).with("tar cf - #{sourcedir} | gzip -c > #{destfile}") + subject.pack(sourcedir, destfile) + end +end diff --git a/spec/unit/network/http/connection_spec.rb b/spec/unit/network/http/connection_spec.rb index b3755a4ae..c0b293edd 100644 --- a/spec/unit/network/http/connection_spec.rb +++ b/spec/unit/network/http/connection_spec.rb @@ -199,9 +199,14 @@ describe Puppet::Network::HTTP::Connection do def a_store_context(args) Puppet[:confdir] = tmpdir('conf') - cert = Puppet::SSL::CertificateAuthority.new.generate(args[:for_server], :dns_alt_names => args[:for_aliases]).content ssl_context = mock('OpenSSL::X509::StoreContext') - ssl_context.stubs(:current_cert).returns(cert) + if args[:verify_raises] + ssl_context.stubs(:current_cert).raises("oh noes") + else + cert = Puppet::SSL::CertificateAuthority.new.generate(args[:for_server], :dns_alt_names => args[:for_aliases]).content + ssl_context.stubs(:current_cert).returns(cert) + end + ssl_context.stubs(:chain).returns([]) ssl_context.stubs(:error_string).returns(args[:with_error_string]) ssl_context end @@ -217,6 +222,16 @@ describe Puppet::Network::HTTP::Connection do end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature for /CN=not_my_server]") end + it "should provide a useful error message when verify_callback raises", :unless => Puppet.features.microsoft_windows? do + subject.stubs(:create_connection). + returns(a_connection_that_verifies(:has_passed_pre_checks => false, + :in_context => a_store_context(:verify_raises => true), + :fails_with => 'certificate verify failed')) + expect do + subject.request(:get, stub('request')) + end.to raise_error(Puppet::Error, "certificate verify failed: [oh noes]") + end + it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do subject.stubs(:create_connection). returns(a_connection_that_verifies(:has_passed_pre_checks => true, diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb index eb368a25e..4b471b127 100755 --- a/spec/unit/network/http/handler_spec.rb +++ b/spec/unit/network/http/handler_spec.rb @@ -1,17 +1,12 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'puppet/network/http' require 'puppet/network/http/handler' require 'puppet/network/authorization' require 'puppet/network/authentication' -class HttpHandled - include Puppet::Network::HTTP::Handler -end - describe Puppet::Network::HTTP::Handler do - before do - @handler = HttpHandled.new - end + let(:handler) { TestingHandler.new } it "should include the v1 REST API" do Puppet::Network::HTTP::Handler.ancestors.should be_include(Puppet::Network::HTTP::API::V1) @@ -21,126 +16,134 @@ describe Puppet::Network::HTTP::Handler do Puppet::Network::HTTP::Handler.ancestors.should be_include(Puppet::Network::Authorization) end - it "should have a method for initializing" do - @handler.should respond_to(:initialize_for_puppet) - end - describe "when initializing" do it "should fail when no server type has been provided" do - lambda { @handler.initialize_for_puppet }.should raise_error(ArgumentError) + lambda { handler.initialize_for_puppet }.should raise_error(ArgumentError) end it "should set server type" do - @handler.initialize_for_puppet("foo") - @handler.server.should == "foo" + handler.initialize_for_puppet("foo") + handler.server.should == "foo" end end - it "should be able to process requests" do - @handler.should respond_to(:process) - end - describe "when processing a request" do + let(:request) do + { + :accept_header => "format_one,format_two", + :content_type_header => "text/yaml", + :http_method => "GET", + :path => "/my_handler/my_result", + :params => {}, + :client_cert => nil + } + end + + let(:response) { mock('http response') } + before do - @request = stub('http request') - @request.stubs(:[]).returns "foo" - @response = stub('http response') @model_class = stub('indirected model class') @indirection = stub('indirection') @model_class.stubs(:indirection).returns(@indirection) @result = stub 'result', :render => "mytext" - @handler.stubs(:check_authorization) - @handler.stubs(:warn_if_near_expiration) + request[:headers] = { + "Content-Type" => request[:content_type_header], + "Accept" => request[:accept_header] + } - stub_server_interface - end - - # Stub out the interface we require our including classes to - # implement. - def stub_server_interface - @handler.stubs(:accept_header ).returns "format_one,format_two" - @handler.stubs(:content_type_header).returns "text/yaml" - @handler.stubs(:set_content_type ).returns "my_result" - @handler.stubs(:set_response ).returns "my_result" - @handler.stubs(:path ).returns "/my_handler/my_result" - @handler.stubs(:http_method ).returns("GET") - @handler.stubs(:params ).returns({}) - @handler.stubs(:content_type ).returns("text/plain") - @handler.stubs(:client_cert ).returns(nil) + handler.stubs(:check_authorization) + handler.stubs(:warn_if_near_expiration) + handler.stubs(:headers).returns(request[:headers]) end it "should check the client certificate for upcoming expiration" do cert = mock 'cert' - @handler.stubs(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) - @handler.expects(:client_cert).returns(cert).with(@request) - @handler.expects(:warn_if_near_expiration).with(cert) + handler.stubs(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) + handler.expects(:client_cert).returns(cert).with(request) + handler.expects(:warn_if_near_expiration).with(cert) + + handler.process(request, response) + end + + it "should setup a profiler when the puppet-profiling header exists" do + request[:headers][Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase] = "true" + + handler.process(request, response) + + Puppet::Util::Profiler.current.should be_kind_of(Puppet::Util::Profiler::WallClock) + end + + it "should not setup profiler when the profile parameter is missing" do + request[:params] = { } + + handler.process(request, response) - @handler.process(@request, @response) + Puppet::Util::Profiler.current.should == Puppet::Util::Profiler::NONE end it "should create an indirection request from the path, parameters, and http method" do - @handler.expects(:path).with(@request).returns "mypath" - @handler.expects(:http_method).with(@request).returns "mymethod" - @handler.expects(:params).with(@request).returns "myparams" + request[:path] = "mypath" + request[:http_method] = "mymethod" + request[:params] = { :params => "mine" } - @handler.expects(:uri2indirection).with("mymethod", "mypath", "myparams").returns stub("request", :method => :find) + handler.expects(:uri2indirection).with("mymethod", "mypath", { :params => "mine" }).returns stub("request", :method => :find) - @handler.stubs(:do_find) + handler.stubs(:do_find) - @handler.process(@request, @response) + handler.process(request, response) end it "should call the 'do' method and delegate authorization to the authorization layer" do - @handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) + handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) - @handler.expects(:do_mymethod).with("facts", "key", {:node => "name"}, @request, @response) + handler.expects(:do_mymethod).with("facts", "key", {:node => "name"}, request, response) - @handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}) + handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}) - @handler.process(@request, @response) + handler.process(request, response) end it "should return 403 if the request is not authorized" do - @handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) + handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) - @handler.expects(:do_mymethod).never + handler.expects(:do_mymethod).never - @handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}).raises(Puppet::Network::AuthorizationError.new("forbidden")) + handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}).raises(Puppet::Network::AuthorizationError.new("forbidden")) - @handler.expects(:set_response).with { |response, body, status| status == 403 } + handler.expects(:set_response).with(anything, anything, 403) - @handler.process(@request, @response) + handler.process(request, response) end it "should serialize a controller exception when an exception is thrown while finding the model instance" do - @handler.expects(:uri2indirection).returns(["facts", :find, "key", {:node => "name"}]) + handler.expects(:uri2indirection).returns(["facts", :find, "key", {:node => "name"}]) - @handler.expects(:do_find).raises(ArgumentError, "The exception") - @handler.expects(:set_response).with { |response, body, status| body == "The exception" and status == 400 } - @handler.process(@request, @response) + handler.expects(:do_find).raises(ArgumentError, "The exception") + handler.expects(:set_response).with(anything, "The exception", 400) + handler.process(request, response) end it "should set the format to text/plain when serializing an exception" do - @handler.expects(:set_content_type).with(@response, "text/plain") - @handler.do_exception(@response, "A test", 404) + handler.expects(:set_content_type).with(response, "text/plain") + handler.do_exception(response, "A test", 404) end it "should raise an error if the request is formatted in an unknown format" do - @handler.stubs(:content_type_header).returns "unknown format" - lambda { @handler.request_format(@request) }.should raise_error + handler.stubs(:content_type_header).returns "unknown format" + lambda { handler.request_format(request) }.should raise_error end it "should still find the correct format if content type contains charset information" do - @handler.stubs(:content_type_header).returns "text/plain; charset=UTF-8" - @handler.request_format(@request).should == "s" + handler.stubs(:content_type_header).returns "text/plain; charset=UTF-8" + handler.request_format(request).should == "s" end it "should deserialize YAML parameters" do params = {'my_param' => [1,2,3].to_yaml} - decoded_params = @handler.send(:decode_params, params) + decoded_params = handler.send(:decode_params, params) decoded_params.should == {:my_param => [1,2,3]} end @@ -148,7 +151,7 @@ describe Puppet::Network::HTTP::Handler do it "should accept YAML parameters with !ruby/hash tags on Ruby 1.8", :if => RUBY_VERSION =~ /^1\.8/ do params = {'my_param' => "--- !ruby/hash:Array {}"} - decoded_params = @handler.send(:decode_params, params) + decoded_params = handler.send(:decode_params, params) decoded_params[:my_param].should be_an(Array) end @@ -159,7 +162,7 @@ describe Puppet::Network::HTTP::Handler do it "should fail if YAML parameters have !ruby/hash tags on Ruby 1.9", :unless => RUBY_VERSION =~ /^1\.8/ do params = {'my_param' => "--- !ruby/hash:Array {}"} - expect { @handler.send(:decode_params, params) }.to raise_error(ArgumentError, /Illegal YAML mapping found/) + expect { handler.send(:decode_params, params) }.to raise_error(ArgumentError, /Illegal YAML mapping found/) end describe "when finding a model instance" do @@ -175,84 +178,81 @@ describe Puppet::Network::HTTP::Handler do end it "should use the indirection request to find the model class" do - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should use the escaped request key" do - @indirection.expects(:find).with do |key, args| - key == "my_result" - end.returns @result - @handler.do_find("my_handler", "my_result", {}, @request, @response) + @indirection.expects(:find).with("my_result", anything).returns @result + handler.do_find("my_handler", "my_result", {}, request, response) end it "should use a common method for determining the request parameters" do - @indirection.expects(:find).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy - end.returns @result - @handler.do_find("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response) + @indirection.expects(:find).with(anything, has_entries(:foo => :baz, :bar => :xyzzy)).returns @result + + handler.do_find("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, request, response) end it "should set the content type to the first format specified in the accept header" do - @handler.expects(:accept_header).with(@request).returns "one,two" - @handler.expects(:set_content_type).with(@response, @oneformat) - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.expects(:accept_header).with(request).returns "one,two" + handler.expects(:set_content_type).with(response, @oneformat) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should fail if no accept header is provided" do - @handler.expects(:accept_header).with(@request).returns nil - lambda { @handler.do_find("my_handler", "my_result", {}, @request, @response) }.should raise_error(ArgumentError) + handler.expects(:accept_header).with(request).returns nil + lambda { handler.do_find("my_handler", "my_result", {}, request, response) }.should raise_error(ArgumentError) end it "should fail if the accept header does not contain a valid format" do - @handler.expects(:accept_header).with(@request).returns "" - lambda { @handler.do_find("my_handler", "my_result", {}, @request, @response) }.should raise_error(RuntimeError) + handler.expects(:accept_header).with(request).returns "" + lambda { handler.do_find("my_handler", "my_result", {}, request, response) }.should raise_error(RuntimeError) end it "should not use an unsuitable format" do - @handler.expects(:accept_header).with(@request).returns "foo,bar" + handler.expects(:accept_header).with(request).returns "foo,bar" foo = mock 'foo', :suitable? => false bar = mock 'bar', :suitable? => true Puppet::Network::FormatHandler.expects(:format).with("foo").returns foo Puppet::Network::FormatHandler.expects(:format).with("bar").returns bar - @handler.expects(:set_content_type).with(@response, bar) # the suitable one + handler.expects(:set_content_type).with(response, bar) # the suitable one - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should render the result using the first format specified in the accept header" do - @handler.expects(:accept_header).with(@request).returns "one,two" + handler.expects(:accept_header).with(request).returns "one,two" @result.expects(:render).with(@oneformat) - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should pass the result through without rendering it if the result is a string" do @indirection.stubs(:find).returns "foo" - @handler.expects(:set_response).with(@response, "foo") - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_response).with(response, "foo") + handler.do_find("my_handler", "my_result", {}, request, response) end it "should use the default status when a model find call succeeds" do - @handler.expects(:set_response).with { |response, body, status| status.nil? } - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_response).with(anything, anything, nil) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should return a serialized object when a model find call succeeds" do @model_instance = stub('model instance') @model_instance.expects(:render).returns "my_rendered_object" - @handler.expects(:set_response).with { |response, body, status| body == "my_rendered_object" } + handler.expects(:set_response).with(anything, "my_rendered_object", anything) @indirection.stubs(:find).returns(@model_instance) - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should return a 404 when no model instance can be found" do @model_class.stubs(:name).returns "my name" - @handler.expects(:set_response).with { |response, body, status| status == 404 } + handler.expects(:set_response).with(anything, anything, 404) @indirection.stubs(:find).returns(nil) - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should write a log message when no model instance can be found" do @@ -261,46 +261,44 @@ describe Puppet::Network::HTTP::Handler do Puppet.expects(:info).with("Could not find my_handler for 'my_result'") - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end it "should serialize the result in with the appropriate format" do @model_instance = stub('model instance') - @handler.expects(:format_to_use).returns(@oneformat) + handler.expects(:format_to_use).returns(@oneformat) @model_instance.expects(:render).with(@oneformat).returns "my_rendered_object" @indirection.stubs(:find).returns(@model_instance) - @handler.do_find("my_handler", "my_result", {}, @request, @response) + handler.do_find("my_handler", "my_result", {}, request, response) end end describe "when performing head operation" do before do - @handler.stubs(:model).with("my_handler").returns(stub 'model', :indirection => @model_class) - @handler.stubs(:http_method).with(@request).returns("HEAD") - @handler.stubs(:path).with(@request).returns("/production/my_handler/my_result") - @handler.stubs(:params).with(@request).returns({}) + handler.stubs(:model).with("my_handler").returns(stub 'model', :indirection => @model_class) + request[:http_method] = "HEAD" + request[:path] = "/production/my_handler/my_result" + request[:params] = {} @model_class.stubs(:head).returns true end it "should use the escaped request key" do - @model_class.expects(:head).with do |key, args| - key == "my_result" - end.returns true - @handler.process(@request, @response) + @model_class.expects(:head).with("my_result", anything).returns true + handler.process(request, response) end it "should not generate a response when a model head call succeeds" do - @handler.expects(:set_response).never - @handler.process(@request, @response) + handler.expects(:set_response).never + handler.process(request, response) end it "should return a 404 when the model head call returns false" do - @handler.expects(:set_response).with { |response, body, status| status == 404 } + handler.expects(:set_response).with(anything, anything, 404) @model_class.stubs(:head).returns(false) - @handler.process(@request, @response) + handler.process(request, response) end end @@ -308,10 +306,10 @@ describe Puppet::Network::HTTP::Handler do before do Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class ) - @result1 = mock 'result1' - @result2 = mock 'results' + result1 = mock 'result1' + result2 = mock 'results' - @result = [@result1, @result2] + @result = [result1, result2] @model_class.stubs(:render_multiple).returns "my rendered instances" @indirection.stubs(:search).returns(@result) @@ -323,54 +321,52 @@ describe Puppet::Network::HTTP::Handler do end it "should use the indirection request to find the model" do - @handler.do_search("my_handler", "my_result", {}, @request, @response) + handler.do_search("my_handler", "my_result", {}, request, response) end it "should use a common method for determining the request parameters" do - @indirection.expects(:search).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy - end.returns @result - @handler.do_search("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response) + @indirection.expects(:search).with(anything, has_entries(:foo => :baz, :bar => :xyzzy)).returns @result + handler.do_search("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, request, response) end it "should use the default status when a model search call succeeds" do @indirection.stubs(:search).returns(@result) - @handler.do_search("my_handler", "my_result", {}, @request, @response) + handler.do_search("my_handler", "my_result", {}, request, response) end it "should set the content type to the first format returned by the accept header" do - @handler.expects(:accept_header).with(@request).returns "one,two" - @handler.expects(:set_content_type).with(@response, @oneformat) + handler.expects(:accept_header).with(request).returns "one,two" + handler.expects(:set_content_type).with(response, @oneformat) - @handler.do_search("my_handler", "my_result", {}, @request, @response) + handler.do_search("my_handler", "my_result", {}, request, response) end it "should return a list of serialized objects when a model search call succeeds" do - @handler.expects(:accept_header).with(@request).returns "one,two" + handler.expects(:accept_header).with(request).returns "one,two" @indirection.stubs(:search).returns(@result) @model_class.expects(:render_multiple).with(@oneformat, @result).returns "my rendered instances" - @handler.expects(:set_response).with { |response, data| data == "my rendered instances" } - @handler.do_search("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_response).with(anything, "my rendered instances") + handler.do_search("my_handler", "my_result", {}, request, response) end it "should return [] when searching returns an empty array" do - @handler.expects(:accept_header).with(@request).returns "one,two" + handler.expects(:accept_header).with(request).returns "one,two" @indirection.stubs(:search).returns([]) @model_class.expects(:render_multiple).with(@oneformat, []).returns "[]" - @handler.expects(:set_response).with { |response, data| data == "[]" } - @handler.do_search("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_response).with(anything, "[]") + handler.do_search("my_handler", "my_result", {}, request, response) end it "should return a 404 when searching returns nil" do @model_class.stubs(:name).returns "my name" - @handler.expects(:set_response).with { |response, body, status| status == 404 } + handler.expects(:set_response).with(anything, anything, 404) @indirection.stubs(:search).returns(nil) - @handler.do_search("my_handler", "my_result", {}, @request, @response) + handler.do_search("my_handler", "my_result", {}, request, response) end end @@ -383,43 +379,39 @@ describe Puppet::Network::HTTP::Handler do end it "should use the indirection request to find the model" do - @handler.do_destroy("my_handler", "my_result", {}, @request, @response) + handler.do_destroy("my_handler", "my_result", {}, request, response) end it "should use the escaped request key to destroy the instance in the model" do - @indirection.expects(:destroy).with do |key, args| - key == "foo bar" - end - @handler.do_destroy("my_handler", "foo bar", {}, @request, @response) + @indirection.expects(:destroy).with("foo bar", anything) + handler.do_destroy("my_handler", "foo bar", {}, request, response) end it "should use a common method for determining the request parameters" do - @indirection.expects(:destroy).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy - end - @handler.do_destroy("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response) + @indirection.expects(:destroy).with(anything, has_entries(:foo => :baz, :bar => :xyzzy)) + handler.do_destroy("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, request, response) end it "should use the default status code a model destroy call succeeds" do - @handler.expects(:set_response).with { |response, body, status| status.nil? } - @handler.do_destroy("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_response).with(anything, anything, nil) + handler.do_destroy("my_handler", "my_result", {}, request, response) end it "should return a yaml-encoded result when a model destroy call succeeds" do @result = stub 'result', :to_yaml => "the result" @indirection.expects(:destroy).returns(@result) - @handler.expects(:set_response).with { |response, body, status| body == "the result" } + handler.expects(:set_response).with(anything, "the result", anything) - @handler.do_destroy("my_handler", "my_result", {}, @request, @response) + handler.do_destroy("my_handler", "my_result", {}, request, response) end end describe "when saving a model instance" do before do Puppet::Indirector::Indirection.stubs(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class ) - @handler.stubs(:body).returns('my stuff') - @handler.stubs(:content_type_header).returns("text/yaml") + handler.stubs(:body).returns('my stuff') + handler.stubs(:content_type_header).returns("text/yaml") @result = stub 'result', :render => "the result" @@ -434,50 +426,50 @@ describe Puppet::Network::HTTP::Handler do end it "should use the indirection request to find the model" do - @handler.do_save("my_handler", "my_result", {}, @request, @response) + handler.do_save("my_handler", "my_result", {}, request, response) end it "should use the 'body' hook to retrieve the body of the request" do - @handler.expects(:body).returns "my body" - @model_class.expects(:convert_from).with { |format, body| body == "my body" }.returns @model_instance + handler.expects(:body).returns "my body" + @model_class.expects(:convert_from).with(anything, "my body").returns @model_instance - @handler.do_save("my_handler", "my_result", {}, @request, @response) + handler.do_save("my_handler", "my_result", {}, request, response) end it "should fail to save model if data is not specified" do - @handler.stubs(:body).returns('') + handler.stubs(:body).returns('') - lambda { @handler.do_save("my_handler", "my_result", {}, @request, @response) }.should raise_error(ArgumentError) + lambda { handler.do_save("my_handler", "my_result", {}, request, response) }.should raise_error(ArgumentError) end it "should use a common method for determining the request parameters" do @indirection.expects(:save).with(@model_instance, 'key').once - @handler.do_save("my_handler", "key", {}, @request, @response) + handler.do_save("my_handler", "key", {}, request, response) end it "should use the default status when a model save call succeeds" do - @handler.expects(:set_response).with { |response, body, status| status.nil? } - @handler.do_save("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_response).with(anything, anything, nil) + handler.do_save("my_handler", "my_result", {}, request, response) end it "should return the yaml-serialized result when a model save call succeeds" do @indirection.stubs(:save).returns(@model_instance) @model_instance.expects(:to_yaml).returns('foo') - @handler.do_save("my_handler", "my_result", {}, @request, @response) + handler.do_save("my_handler", "my_result", {}, request, response) end it "should set the content to yaml" do - @handler.expects(:set_content_type).with(@response, @yamlformat) - @handler.do_save("my_handler", "my_result", {}, @request, @response) + handler.expects(:set_content_type).with(response, @yamlformat) + handler.do_save("my_handler", "my_result", {}, request, response) end it "should use the content-type header to know the body format" do - @handler.expects(:content_type_header).returns("text/format") + handler.expects(:content_type_header).returns("text/format") Puppet::Network::FormatHandler.stubs(:mime).with("text/format").returns @format - @model_class.expects(:convert_from).with { |format, body| format == "format" }.returns @model_instance + @model_class.expects(:convert_from).with("format", anything).returns @model_instance - @handler.do_save("my_handler", "my_result", {}, @request, @response) + handler.do_save("my_handler", "my_result", {}, request, response) end end end @@ -486,19 +478,55 @@ describe Puppet::Network::HTTP::Handler do it "should use a look-up from the ip address" do Resolv.expects(:getname).with("1.2.3.4").returns("host.domain.com") - @handler.resolve_node(:ip => "1.2.3.4") + handler.resolve_node(:ip => "1.2.3.4") end it "should return the look-up result" do Resolv.stubs(:getname).with("1.2.3.4").returns("host.domain.com") - @handler.resolve_node(:ip => "1.2.3.4").should == "host.domain.com" + handler.resolve_node(:ip => "1.2.3.4").should == "host.domain.com" end it "should return the ip address if resolving fails" do Resolv.stubs(:getname).with("1.2.3.4").raises(RuntimeError, "no such host") - @handler.resolve_node(:ip => "1.2.3.4").should == "1.2.3.4" + handler.resolve_node(:ip => "1.2.3.4").should == "1.2.3.4" + end + end + + class TestingHandler + include Puppet::Network::HTTP::Handler + + def accept_header(request) + request[:accept_header] + end + + def content_type_header(request) + request[:content_type_header] + end + + def set_content_type(response, format) + "my_result" + end + + def set_response(response, body, status = 200) + "my_result" + end + + def http_method(request) + request[:http_method] + end + + def path(request) + request[:path] + end + + def params(request) + request[:params] + end + + def client_cert(request) + request[:client_cert] end end end diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb index 2b5b539bd..08e88be40 100755 --- a/spec/unit/network/http/rack/rest_spec.rb +++ b/spec/unit/network/http/rack/rest_spec.rb @@ -31,6 +31,16 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do Rack::Request.new(env) end + describe "#headers" do + it "should return the headers (parsed from env with prefix 'HTTP_')" do + req = mk_req('/', {'HTTP_Accept' => 'myaccept', + 'HTTP_X-Custom-Header' => 'mycustom', + 'NOT_HTTP_foo' => 'not an http header'}) + @handler.headers(req).should == {"accept" => 'myaccept', + "x-custom-header" => 'mycustom'} + end + end + describe "and using the HTTP Handler interface" do it "should return the HTTP_ACCEPT parameter as the accept header" do req = mk_req('/', 'HTTP_ACCEPT' => 'myaccept') @@ -198,30 +208,42 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do end describe "with pre-validated certificates" do - it "should retrieve the hostname by matching the certificate parameter given in :ssl_client_header" do + it "should retrieve the hostname by finding the CN given in :ssl_client_header, in the format returned by Apache (RFC2253)" do + Puppet[:ssl_client_header] = "myheader" + req = mk_req('/', "myheader" => "O=Foo\\, Inc,CN=host.domain.com") + @handler.params(req)[:node].should == "host.domain.com" + end + + it "should retrieve the hostname by finding the CN given in :ssl_client_header, in the format returned by nginx" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "/CN=host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end + it "should retrieve the hostname by finding the CN given in :ssl_client_header, ignoring other fields" do + Puppet[:ssl_client_header] = "myheader" + req = mk_req('/', "myheader" => 'ST=Denial,CN=host.domain.com,O=Domain\\, Inc.') + @handler.params(req)[:node].should == "host.domain.com" + end + it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" - req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") + req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "CN=host.domain.com") @handler.params(req)[:authenticated].should be_true end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" - req = mk_req('/', "myheader" => "whatever", "certheader" => "/CN=host.domain.com") + req = mk_req('/', "myheader" => "whatever", "certheader" => "CN=host.domain.com") @handler.params(req)[:authenticated].should be_false end it "should consider the host unauthenticated if no certificate information is present" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" - req = mk_req('/', "myheader" => nil, "certheader" => "/CN=host.domain.com") + req = mk_req('/', "myheader" => nil, "certheader" => "CN=host.domain.com") @handler.params(req)[:authenticated].should be_false end @@ -231,6 +253,23 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do @handler.expects(:resolve_node).returns("host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end + + it "should resolve the node name with an ip address look-up if a certificate without a CN is present" do + Puppet[:ssl_client_header] = "myheader" + req = mk_req('/', "myheader" => "O=no CN") + @handler.expects(:resolve_node).returns("host.domain.com") + @handler.params(req)[:node].should == "host.domain.com" + end + + it "should not allow authentication via the verify header if there is no CN available" do + Puppet[:ssl_client_header] = "dn_header" + Puppet[:ssl_client_verify_header] = "verify_header" + req = mk_req('/', "dn_header" => "O=no CN", "verify_header" => 'SUCCESS') + + @handler.expects(:resolve_node).returns("host.domain.com") + + @handler.params(req)[:authenticated].should be_false + end end end end diff --git a/spec/unit/network/http/webrick/rest_spec.rb b/spec/unit/network/http/webrick/rest_spec.rb index 2f1a3129c..5108ae880 100755 --- a/spec/unit/network/http/webrick/rest_spec.rb +++ b/spec/unit/network/http/webrick/rest_spec.rb @@ -41,6 +41,20 @@ describe Puppet::Network::HTTP::WEBrickREST do @handler.service(@request, @response).should == "stuff" end + describe "#headers" do + let(:fake_request) { {"Foo" => "bar", "BAZ" => "bam" } } + + it "should iterate over the request object using #each" do + fake_request.expects(:each) + @handler.headers(fake_request) + end + + it "should return a hash with downcased header names" do + result = @handler.headers(fake_request) + result.should == fake_request.inject({}) { |m,(k,v)| m[k.downcase] = v; m } + end + end + describe "when using the Handler interface" do it "should use the 'accept' request parameter as the Accept header" do @request.expects(:[]).with("accept").returns "foobar" @@ -173,8 +187,10 @@ describe Puppet::Network::HTTP::WEBrickREST do end it "should pass the client's certificate name to model method if a certificate is present" do - cert = stub 'cert', :subject => [%w{CN host.domain.com}] + subj = stub 'subj' + cert = stub 'cert', :subject => subj @request.stubs(:client_cert).returns cert + Puppet::Util::SSL.expects(:cn_from_subject).with(subj).returns 'host.domain.com' @handler.params(@request)[:node].should == "host.domain.com" end @@ -185,6 +201,17 @@ describe Puppet::Network::HTTP::WEBrickREST do @handler.params(@request)[:node].should == :resolved_node end + + it "should resolve the node name with an ip address look-up if CN parsing fails" do + subj = stub 'subj' + cert = stub 'cert', :subject => subj + @request.stubs(:client_cert).returns cert + Puppet::Util::SSL.expects(:cn_from_subject).with(subj).returns nil + + @handler.expects(:resolve_node).returns(:resolved_node) + + @handler.params(@request)[:node].should == :resolved_node + end end end end diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index 24f41507e..17f61e339 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -43,9 +43,18 @@ describe Puppet::Network::HTTP::WEBrick do end it "should tell webrick to listen on the specified address and port" do - WEBrick::HTTPServer.expects(:new).with {|args| - args[:Port] == 31337 and args[:BindAddress] == "127.0.0.1" - }.returns(mock_webrick) + WEBrick::HTTPServer.expects(:new).with( + has_entries(:Port => 31337, :BindAddress => "127.0.0.1") + ).returns(mock_webrick) + server.listen(address, port) + end + + it "should not perform reverse lookups" do + WEBrick::HTTPServer.expects(:new).with( + has_entry(:DoNotReverseLookup => true) + ).returns(mock_webrick) + BasicSocket.expects(:do_not_reverse_lookup=).with(true) + server.listen(address, port) end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index f7aacc277..754be7b5a 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -6,6 +6,7 @@ require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' +require 'puppet/parser/parser_factory' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.new("testing") } @@ -15,438 +16,454 @@ describe Puppet::Node::Environment do Puppet::Node::Environment.clear end - it "should use the filetimeout for the ttl for the modulepath" do - Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout]) - end - - it "should use the filetimeout for the ttl for the module list" do - Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) - end - - it "should use the default environment if no name is provided while initializing an environment" do - Puppet[:environment] = "one" - Puppet::Node::Environment.new.name.should == :one - end - - it "should treat environment instances as singletons" do - Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) - end - - it "should treat an environment specified as names or strings as equivalent" do - Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) - end - - it "should return its name when converted to a string" do - Puppet::Node::Environment.new(:one).to_s.should == "one" - end - - it "should just return any provided environment if an environment is provided as the name" do - one = Puppet::Node::Environment.new(:one) - Puppet::Node::Environment.new(one).should equal(one) - end - - describe "when managing known resource types" do - before do - @collection = Puppet::Resource::TypeCollection.new(env) - env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) - Thread.current[:known_resource_types] = nil - end - - it "should create a resource type collection if none exists" do - Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection - env.known_resource_types.should equal(@collection) - end - - it "should reuse any existing resource type collection" do - env.known_resource_types.should equal(env.known_resource_types) - end - - it "should perform the initial import when creating a new collection" do - env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) - env.known_resource_types - end - - it "should return the same collection even if stale if it's the same thread" do - Puppet::Resource::TypeCollection.stubs(:new).returns @collection - env.known_resource_types.stubs(:stale?).returns true - - env.known_resource_types.should equal(@collection) + shared_examples_for 'the environment' do + it "should use the filetimeout for the ttl for the modulepath" do + Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout]) end - - it "should return the current thread associated collection if there is one" do - Thread.current[:known_resource_types] = @collection - - env.known_resource_types.should equal(@collection) + + it "should use the filetimeout for the ttl for the module list" do + Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) end - - it "should give to all threads using the same environment the same collection if the collection isn't stale" do - @original_thread_type_collection = Puppet::Resource::TypeCollection.new(env) - Puppet::Resource::TypeCollection.expects(:new).with(env).returns @original_thread_type_collection - env.known_resource_types.should equal(@original_thread_type_collection) - - @original_thread_type_collection.expects(:require_reparse?).returns(false) - Puppet::Resource::TypeCollection.stubs(:new).with(env).returns @collection - - t = Thread.new { - env.known_resource_types.should equal(@original_thread_type_collection) - } - t.join - end - - it "should generate a new TypeCollection if the current one requires reparsing" do - old_type_collection = env.known_resource_types - old_type_collection.stubs(:require_reparse?).returns true - Thread.current[:known_resource_types] = nil - new_type_collection = env.known_resource_types - - new_type_collection.should be_a Puppet::Resource::TypeCollection - new_type_collection.should_not equal(old_type_collection) - end - end - - it "should validate the modulepath directories" do - real_file = tmpdir('moduledir') - path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) - - Puppet[:modulepath] = path - - env.modulepath.should == [real_file] - end - - it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do - Puppet::Util.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do - module_path = %w{/one /two}.join(File::PATH_SEPARATOR) - env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two} - env.expects(:[]).with(:modulepath).returns module_path - - env.modulepath.should == %w{/l1 /l2 /one /two} - end - end - - describe "when validating modulepath or manifestdir directories" do - before :each do - @path_one = tmpdir("path_one") - @path_two = tmpdir("path_one") - sep = File::PATH_SEPARATOR - Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}" + + it "should use the default environment if no name is provided while initializing an environment" do + Puppet[:environment] = "one" + Puppet::Node::Environment.new.name.should == :one end - - it "should not return non-directories" do - FileTest.expects(:directory?).with(@path_one).returns true - FileTest.expects(:directory?).with(@path_two).returns false - - env.validate_dirs([@path_one, @path_two]).should == [@path_one] + + it "should treat environment instances as singletons" do + Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end - - it "should use the current working directory to fully-qualify unqualified paths" do - FileTest.stubs(:directory?).returns true - - two = File.expand_path(File.join(Dir.getwd, "two")) - env.validate_dirs([@path_one, 'two']).should == [@path_one, two] + + it "should treat an environment specified as names or strings as equivalent" do + Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end - end - - describe "when modeling a specific environment" do - it "should have a method for returning the environment name" do - Puppet::Node::Environment.new("testing").name.should == :testing + + it "should return its name when converted to a string" do + Puppet::Node::Environment.new(:one).to_s.should == "one" end - - it "should provide an array-like accessor method for returning any environment-specific setting" do - env.should respond_to(:[]) + + it "should just return any provided environment if an environment is provided as the name" do + one = Puppet::Node::Environment.new(:one) + Puppet::Node::Environment.new(one).should equal(one) end - - it "should ask the Puppet settings instance for the setting qualified with the environment name" do - Puppet.settings.set_value(:server, "myval", :testing) - env[:server].should == "myval" + + describe "when managing known resource types" do + before do + @collection = Puppet::Resource::TypeCollection.new(env) + env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) + Thread.current[:known_resource_types] = nil + end + + it "should create a resource type collection if none exists" do + Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection + env.known_resource_types.should equal(@collection) + end + + it "should reuse any existing resource type collection" do + env.known_resource_types.should equal(env.known_resource_types) + end + + it "should perform the initial import when creating a new collection" do + env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) + env.known_resource_types + end + + it "should return the same collection even if stale if it's the same thread" do + Puppet::Resource::TypeCollection.stubs(:new).returns @collection + env.known_resource_types.stubs(:stale?).returns true + + env.known_resource_types.should equal(@collection) + end + + it "should return the current thread associated collection if there is one" do + Thread.current[:known_resource_types] = @collection + + env.known_resource_types.should equal(@collection) + end + + it "should give to all threads using the same environment the same collection if the collection isn't stale" do + @original_thread_type_collection = Puppet::Resource::TypeCollection.new(env) + Puppet::Resource::TypeCollection.expects(:new).with(env).returns @original_thread_type_collection + env.known_resource_types.should equal(@original_thread_type_collection) + + @original_thread_type_collection.expects(:require_reparse?).returns(false) + Puppet::Resource::TypeCollection.stubs(:new).with(env).returns @collection + + t = Thread.new { + env.known_resource_types.should equal(@original_thread_type_collection) + } + t.join + end + + it "should generate a new TypeCollection if the current one requires reparsing" do + old_type_collection = env.known_resource_types + old_type_collection.stubs(:require_reparse?).returns true + Thread.current[:known_resource_types] = nil + new_type_collection = env.known_resource_types + + new_type_collection.should be_a Puppet::Resource::TypeCollection + new_type_collection.should_not equal(old_type_collection) + end end - - it "should be able to return an individual module that exists in its module path" do - env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] - - mod = env.module('one') - mod.should be_a(Puppet::Module) - mod.name.should == 'one' + + it "should validate the modulepath directories" do + real_file = tmpdir('moduledir') + path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) + + Puppet[:modulepath] = path + + env.modulepath.should == [real_file] end - - it "should not return a module if the module doesn't exist" do - env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] - - env.module('two').should be_nil + + it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do + Puppet::Util.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do + module_path = %w{/one /two}.join(File::PATH_SEPARATOR) + env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two} + env.expects(:[]).with(:modulepath).returns module_path + + env.modulepath.should == %w{/l1 /l2 /one /two} + end end - - it "should return nil if asked for a module that does not exist in its path" do - modpath = tmpdir('modpath') - env.modulepath = [modpath] - - env.module("one").should be_nil + + describe "when validating modulepath or manifestdir directories" do + before :each do + @path_one = tmpdir("path_one") + @path_two = tmpdir("path_one") + sep = File::PATH_SEPARATOR + Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}" + end + + it "should not return non-directories" do + FileTest.expects(:directory?).with(@path_one).returns true + FileTest.expects(:directory?).with(@path_two).returns false + + env.validate_dirs([@path_one, @path_two]).should == [@path_one] + end + + it "should use the current working directory to fully-qualify unqualified paths" do + FileTest.stubs(:directory?).returns true + + two = File.expand_path(File.join(Dir.getwd, "two")) + env.validate_dirs([@path_one, 'two']).should == [@path_one, two] + end end - - describe "module data" do - before do - dir = tmpdir("deep_path") - - @first = File.join(dir, "first") - @second = File.join(dir, "second") - Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}" - - FileUtils.mkdir_p(@first) - FileUtils.mkdir_p(@second) + + describe "when modeling a specific environment" do + it "should have a method for returning the environment name" do + Puppet::Node::Environment.new("testing").name.should == :testing end - - describe "#modules_by_path" do - it "should return an empty list if there are no modules" do - env.modules_by_path.should == { - @first => [], - @second => [] - } - end - - it "should include modules even if they exist in multiple dirs in the modulepath" do - modpath1 = File.join(@first, "foo") - FileUtils.mkdir_p(modpath1) - modpath2 = File.join(@second, "foo") - FileUtils.mkdir_p(modpath2) - - env.modules_by_path.should == { - @first => [Puppet::Module.new('foo', modpath1, env)], - @second => [Puppet::Module.new('foo', modpath2, env)] - } - end - - it "should ignore modules with invalid names" do - FileUtils.mkdir_p(File.join(@first, 'foo')) - FileUtils.mkdir_p(File.join(@first, 'foo2')) - FileUtils.mkdir_p(File.join(@first, 'foo-bar')) - FileUtils.mkdir_p(File.join(@first, 'foo_bar')) - FileUtils.mkdir_p(File.join(@first, 'foo=bar')) - FileUtils.mkdir_p(File.join(@first, 'foo bar')) - FileUtils.mkdir_p(File.join(@first, 'foo.bar')) - FileUtils.mkdir_p(File.join(@first, '-foo')) - FileUtils.mkdir_p(File.join(@first, 'foo-')) - FileUtils.mkdir_p(File.join(@first, 'foo--bar')) - - env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} - end - + + it "should provide an array-like accessor method for returning any environment-specific setting" do + env.should respond_to(:[]) end - - describe "#module_requirements" do - it "should return a list of what modules depend on other modules" do - PuppetSpec::Modules.create( - 'foo', - @first, - :metadata => { - :author => 'puppetlabs', - :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] - } - ) - PuppetSpec::Modules.create( - 'bar', - @second, - :metadata => { - :author => 'puppetlabs', - :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] - } - ) - PuppetSpec::Modules.create( - 'baz', - @first, - :metadata => { - :author => 'puppetlabs', - :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }] - } - ) - PuppetSpec::Modules.create( - 'alpha', - @first, - :metadata => { - :author => 'puppetlabs', - :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] - } - ) - - env.module_requirements.should == { - 'puppetlabs/alpha' => [], - 'puppetlabs/foo' => [ - { - "name" => "puppetlabs/bar", - "version" => "9.9.9", - "version_requirement" => "<= 2.0.0" - } - ], - 'puppetlabs/bar' => [ - { - "name" => "puppetlabs/alpha", - "version" => "9.9.9", - "version_requirement" => "~3.0.0" - }, - { - "name" => "puppetlabs/baz", - "version" => "9.9.9", - "version_requirement" => "3.0.0" - }, - { - "name" => "puppetlabs/foo", - "version" => "9.9.9", - "version_requirement" => ">= 1.0.0" - } - ], - 'puppetlabs/baz' => [] - } - end + + it "should ask the Puppet settings instance for the setting qualified with the environment name" do + Puppet.settings.set_value(:server, "myval", :testing) + env[:server].should == "myval" end - - describe ".module_by_forge_name" do - it "should find modules by forge_name" do - mod = PuppetSpec::Modules.create( - 'baz', - @first, - :metadata => {:author => 'puppetlabs'}, - :environment => env - ) - env.module_by_forge_name('puppetlabs/baz').should == mod - end - - it "should not find modules with same name by the wrong author" do - mod = PuppetSpec::Modules.create( - 'baz', - @first, - :metadata => {:author => 'sneakylabs'}, - :environment => env - ) - env.module_by_forge_name('puppetlabs/baz').should == nil - end - - it "should return nil when the module can't be found" do - env.module_by_forge_name('ima/nothere').should be_nil - end + + it "should be able to return an individual module that exists in its module path" do + env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] + + mod = env.module('one') + mod.should be_a(Puppet::Module) + mod.name.should == 'one' end - - describe ".modules" do - it "should return an empty list if there are no modules" do - env.modules.should == [] + + it "should not return a module if the module doesn't exist" do + env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] + + env.module('two').should be_nil + end + + it "should return nil if asked for a module that does not exist in its path" do + modpath = tmpdir('modpath') + env.modulepath = [modpath] + + env.module("one").should be_nil + end + + describe "module data" do + before do + dir = tmpdir("deep_path") + + @first = File.join(dir, "first") + @second = File.join(dir, "second") + Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}" + + FileUtils.mkdir_p(@first) + FileUtils.mkdir_p(@second) end - - it "should return a module named for every directory in each module path" do - %w{foo bar}.each do |mod_name| - FileUtils.mkdir_p(File.join(@first, mod_name)) + + describe "#modules_by_path" do + it "should return an empty list if there are no modules" do + env.modules_by_path.should == { + @first => [], + @second => [] + } end - %w{bee baz}.each do |mod_name| - FileUtils.mkdir_p(File.join(@second, mod_name)) + + it "should include modules even if they exist in multiple dirs in the modulepath" do + modpath1 = File.join(@first, "foo") + FileUtils.mkdir_p(modpath1) + modpath2 = File.join(@second, "foo") + FileUtils.mkdir_p(modpath2) + + env.modules_by_path.should == { + @first => [Puppet::Module.new('foo', modpath1, env)], + @second => [Puppet::Module.new('foo', modpath2, env)] + } + end + + it "should ignore modules with invalid names" do + FileUtils.mkdir_p(File.join(@first, 'foo')) + FileUtils.mkdir_p(File.join(@first, 'foo2')) + FileUtils.mkdir_p(File.join(@first, 'foo-bar')) + FileUtils.mkdir_p(File.join(@first, 'foo_bar')) + FileUtils.mkdir_p(File.join(@first, 'foo=bar')) + FileUtils.mkdir_p(File.join(@first, 'foo bar')) + FileUtils.mkdir_p(File.join(@first, 'foo.bar')) + FileUtils.mkdir_p(File.join(@first, '-foo')) + FileUtils.mkdir_p(File.join(@first, 'foo-')) + FileUtils.mkdir_p(File.join(@first, 'foo--bar')) + + env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end - env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort + end - - it "should remove duplicates" do - FileUtils.mkdir_p(File.join(@first, 'foo')) - FileUtils.mkdir_p(File.join(@second, 'foo')) - - env.modules.collect{|mod| mod.name}.sort.should == %w{foo} + + describe "#module_requirements" do + it "should return a list of what modules depend on other modules" do + PuppetSpec::Modules.create( + 'foo', + @first, + :metadata => { + :author => 'puppetlabs', + :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] + } + ) + PuppetSpec::Modules.create( + 'bar', + @second, + :metadata => { + :author => 'puppetlabs', + :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] + } + ) + PuppetSpec::Modules.create( + 'baz', + @first, + :metadata => { + :author => 'puppetlabs', + :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }] + } + ) + PuppetSpec::Modules.create( + 'alpha', + @first, + :metadata => { + :author => 'puppetlabs', + :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] + } + ) + + env.module_requirements.should == { + 'puppetlabs/alpha' => [], + 'puppetlabs/foo' => [ + { + "name" => "puppetlabs/bar", + "version" => "9.9.9", + "version_requirement" => "<= 2.0.0" + } + ], + 'puppetlabs/bar' => [ + { + "name" => "puppetlabs/alpha", + "version" => "9.9.9", + "version_requirement" => "~3.0.0" + }, + { + "name" => "puppetlabs/baz", + "version" => "9.9.9", + "version_requirement" => "3.0.0" + }, + { + "name" => "puppetlabs/foo", + "version" => "9.9.9", + "version_requirement" => ">= 1.0.0" + } + ], + 'puppetlabs/baz' => [] + } + end end - - it "should ignore modules with invalid names" do - FileUtils.mkdir_p(File.join(@first, 'foo')) - FileUtils.mkdir_p(File.join(@first, 'foo2')) - FileUtils.mkdir_p(File.join(@first, 'foo-bar')) - FileUtils.mkdir_p(File.join(@first, 'foo_bar')) - FileUtils.mkdir_p(File.join(@first, 'foo=bar')) - FileUtils.mkdir_p(File.join(@first, 'foo bar')) - - env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} + + describe ".module_by_forge_name" do + it "should find modules by forge_name" do + mod = PuppetSpec::Modules.create( + 'baz', + @first, + :metadata => {:author => 'puppetlabs'}, + :environment => env + ) + env.module_by_forge_name('puppetlabs/baz').should == mod + end + + it "should not find modules with same name by the wrong author" do + mod = PuppetSpec::Modules.create( + 'baz', + @first, + :metadata => {:author => 'sneakylabs'}, + :environment => env + ) + env.module_by_forge_name('puppetlabs/baz').should == nil + end + + it "should return nil when the module can't be found" do + env.module_by_forge_name('ima/nothere').should be_nil + end end - - it "should create modules with the correct environment" do - FileUtils.mkdir_p(File.join(@first, 'foo')) - env.modules.each {|mod| mod.environment.should == env } + + describe ".modules" do + it "should return an empty list if there are no modules" do + env.modules.should == [] + end + + it "should return a module named for every directory in each module path" do + %w{foo bar}.each do |mod_name| + FileUtils.mkdir_p(File.join(@first, mod_name)) + end + %w{bee baz}.each do |mod_name| + FileUtils.mkdir_p(File.join(@second, mod_name)) + end + env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort + end + + it "should remove duplicates" do + FileUtils.mkdir_p(File.join(@first, 'foo')) + FileUtils.mkdir_p(File.join(@second, 'foo')) + + env.modules.collect{|mod| mod.name}.sort.should == %w{foo} + end + + it "should ignore modules with invalid names" do + FileUtils.mkdir_p(File.join(@first, 'foo')) + FileUtils.mkdir_p(File.join(@first, 'foo2')) + FileUtils.mkdir_p(File.join(@first, 'foo-bar')) + FileUtils.mkdir_p(File.join(@first, 'foo_bar')) + FileUtils.mkdir_p(File.join(@first, 'foo=bar')) + FileUtils.mkdir_p(File.join(@first, 'foo bar')) + + env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} + end + + it "should create modules with the correct environment" do + FileUtils.mkdir_p(File.join(@first, 'foo')) + env.modules.each {|mod| mod.environment.should == env } + end + end - + end + + it "should cache the module list" do + env.modulepath = %w{/a} + Dir.expects(:entries).once.with("/a").returns %w{foo} + + env.modules + env.modules end end - - it "should cache the module list" do - env.modulepath = %w{/a} - Dir.expects(:entries).once.with("/a").returns %w{foo} - - env.modules - env.modules - end - end - - describe Puppet::Node::Environment::Helper do - before do - @helper = Object.new - @helper.extend(Puppet::Node::Environment::Helper) - end - - it "should be able to set and retrieve the environment as a symbol" do - @helper.environment = :foo - @helper.environment.name.should == :foo - end - - it "should accept an environment directly" do - @helper.environment = Puppet::Node::Environment.new(:foo) - @helper.environment.name.should == :foo + + describe Puppet::Node::Environment::Helper do + before do + @helper = Object.new + @helper.extend(Puppet::Node::Environment::Helper) + end + + it "should be able to set and retrieve the environment as a symbol" do + @helper.environment = :foo + @helper.environment.name.should == :foo + end + + it "should accept an environment directly" do + @helper.environment = Puppet::Node::Environment.new(:foo) + @helper.environment.name.should == :foo + end + + it "should accept an environment as a string" do + @helper.environment = 'foo' + @helper.environment.name.should == :foo + end end - - it "should accept an environment as a string" do - @helper.environment = 'foo' - @helper.environment.name.should == :foo + + describe "when performing initial import" do + before do + @parser = Puppet::Parser::ParserFactory.parser("test") +# @parser = Puppet::Parser::EParserAdapter.new(Puppet::Parser::Parser.new("test")) # TODO: FIX PARSER FACTORY + Puppet::Parser::ParserFactory.stubs(:parser).returns @parser + end + + it "should set the parser's string to the 'code' setting and parse if code is available" do + Puppet.settings[:code] = "my code" + @parser.expects(:string=).with "my code" + @parser.expects(:parse) + env.instance_eval { perform_initial_import } + end + + it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do + filename = tmpfile('myfile') + File.open(filename, 'w'){|f| } + Puppet.settings[:manifest] = filename + @parser.expects(:file=).with filename + @parser.expects(:parse) + env.instance_eval { perform_initial_import } + end + + it "should pass the manifest file to the parser even if it does not exist on disk" do + filename = tmpfile('myfile') + Puppet.settings[:code] = "" + Puppet.settings[:manifest] = filename + @parser.expects(:file=).with(filename).once + @parser.expects(:parse).once + env.instance_eval { perform_initial_import } + end + + it "should fail helpfully if there is an error importing" do + File.stubs(:exist?).returns true + env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) + @parser.expects(:file=).once + @parser.expects(:parse).raises ArgumentError + lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error) + end + + it "should not do anything if the ignore_import settings is set" do + Puppet.settings[:ignoreimport] = true + @parser.expects(:string=).never + @parser.expects(:file=).never + @parser.expects(:parse).never + env.instance_eval { perform_initial_import } + end + + it "should mark the type collection as needing a reparse when there is an error parsing" do + @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") + env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) + + lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../) + env.known_resource_types.require_reparse?.should be_true + end end end - - describe "when performing initial import" do - before do - @parser = Puppet::Parser::Parser.new("test") - Puppet::Parser::Parser.stubs(:new).returns @parser - end - - it "should set the parser's string to the 'code' setting and parse if code is available" do - Puppet.settings[:code] = "my code" - @parser.expects(:string=).with "my code" - @parser.expects(:parse) - env.instance_eval { perform_initial_import } - end - - it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do - filename = tmpfile('myfile') - File.open(filename, 'w'){|f| } - Puppet.settings[:manifest] = filename - @parser.expects(:file=).with filename - @parser.expects(:parse) - env.instance_eval { perform_initial_import } - end - - it "should pass the manifest file to the parser even if it does not exist on disk" do - filename = tmpfile('myfile') - Puppet.settings[:code] = "" - Puppet.settings[:manifest] = filename - @parser.expects(:file=).with(filename).once - @parser.expects(:parse).once - env.instance_eval { perform_initial_import } - end - - it "should fail helpfully if there is an error importing" do - File.stubs(:exist?).returns true - env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) - @parser.expects(:file=).once - @parser.expects(:parse).raises ArgumentError - lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error) - end - - it "should not do anything if the ignore_import settings is set" do - Puppet.settings[:ignoreimport] = true - @parser.expects(:string=).never - @parser.expects(:file=).never - @parser.expects(:parse).never - env.instance_eval { perform_initial_import } + describe 'with classic parser' do + before :each do + Puppet[:parser] = 'current' end - - it "should mark the type collection as needing a reparse when there is an error parsing" do - @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") - env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) - - lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../) - env.known_resource_types.require_reparse?.should be_true + it_behaves_like 'the environment' + end + describe 'with future parser' do + before :each do + Puppet[:parser] = 'future' end + it_behaves_like 'the environment' end + end diff --git a/spec/unit/parser/ast/arithmetic_operator_spec.rb b/spec/unit/parser/ast/arithmetic_operator_spec.rb index de6941fb7..cdd01e375 100755 --- a/spec/unit/parser/ast/arithmetic_operator_spec.rb +++ b/spec/unit/parser/ast/arithmetic_operator_spec.rb @@ -24,7 +24,7 @@ describe Puppet::Parser::AST::ArithmeticOperator do end it "should fail for an unknown operator" do - lambda { operator = ast::ArithmeticOperator.new :lval => @one, :operator => "%", :rval => @two }.should raise_error + lambda { operator = ast::ArithmeticOperator.new :lval => @one, :operator => "^", :rval => @two }.should raise_error end it "should call Puppet::Parser::Scope.number?" do @@ -35,7 +35,7 @@ describe Puppet::Parser::AST::ArithmeticOperator do end - %w{ + - * / << >>}.each do |op| + %w{ + - * / % << >>}.each do |op| it "should call ruby Numeric '#{op}'" do one = stub 'one' two = stub 'two' @@ -61,4 +61,100 @@ describe Puppet::Parser::AST::ArithmeticOperator do operator.evaluate(@scope).should == 4.33 end + context "when applied to array" do + before :each do + Puppet[:parser] = 'future' + end + + it "+ should concatenate an array" do + one = stub 'one', :safeevaluate => [1,2,3] + two = stub 'two', :safeevaluate => [4,5] + operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two + operator.evaluate(@scope).should == [1,2,3,4,5] + end + + it "<< should append array to an array" do + one = stub 'one', :safeevaluate => [1,2,3] + two = stub 'two', :safeevaluate => [4,5] + operator = ast::ArithmeticOperator.new :lval => one, :operator => "<<", :rval => two + operator.evaluate(@scope).should == [1,2,3, [4,5]] + end + + it "<< should append object to an array" do + one = stub 'one', :safeevaluate => [1,2,3] + two = stub 'two', :safeevaluate => 'a b c' + operator = ast::ArithmeticOperator.new :lval => one, :operator => "<<", :rval => two + operator.evaluate(@scope).should == [1,2,3, 'a b c'] + end + + context "and input is invalid" do + it "should raise error for + if left is not an array" do + one = stub 'one', :safeevaluate => 4 + two = stub 'two', :safeevaluate => [4,5] + operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two + lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/left/) + end + + it "should raise error for << if left is not an array" do + one = stub 'one', :safeevaluate => 4 + two = stub 'two', :safeevaluate => [4,5] + operator = ast::ArithmeticOperator.new :lval => one, :operator => "<<", :rval => two + lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/left/) + end + + it "should raise error for + if right is not an array" do + one = stub 'one', :safeevaluate => [1,2] + two = stub 'two', :safeevaluate => 45 + operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two + lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/right/) + end + + %w{ - * / % >>}.each do |op| + it "should raise error for '#{op}'" do + one = stub 'one', :safeevaluate => [1,2,3] + two = stub 'two', :safeevaluate => [4,5] + operator = ast::ArithmeticOperator.new :lval => @one, :operator => op, :rval => @two + lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error + end + end + end + + context "when applied to hash" do + before :each do + Puppet[:parser] = 'future' + end + + it "+ should merge two hashes" do + one = stub 'one', :safeevaluate => {'a' => 1, 'b' => 2} + two = stub 'two', :safeevaluate => {'c' => 3 } + operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two + operator.evaluate(@scope).should == {'a' => 1, 'b' => 2, 'c' => 3} + end + + context "and input is invalid" do + it "should raise error for + if left is not a hash" do + one = stub 'one', :safeevaluate => 4 + two = stub 'two', :safeevaluate => {'a' => 1} + operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two + lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/left/) + end + + it "should raise error for + if right is not a hash" do + one = stub 'one', :safeevaluate => {'a' => 1} + two = stub 'two', :safeevaluate => 1 + operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two + lambda { operator.evaluate(@scope).should == {'a'=>1, 1=>nil} }.should raise_error(/right/) + end + + %w{ - * / % << >>}.each do |op| + it "should raise error for '#{op}'" do + one = stub 'one', :safeevaluate => {'a' => 1, 'b' => 2} + two = stub 'two', :safeevaluate => {'c' => 3 } + operator = ast::ArithmeticOperator.new :lval => @one, :operator => op, :rval => @two + lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error + end + end + end + end + end end diff --git a/spec/unit/parser/collector_spec.rb b/spec/unit/parser/collector_spec.rb index 132bddbc5..5d4af0c11 100755 --- a/spec/unit/parser/collector_spec.rb +++ b/spec/unit/parser/collector_spec.rb @@ -288,6 +288,10 @@ describe Puppet::Parser::Collector, "when collecting exported resources", :if => Puppet[:storeconfigs_backend] = "active_record" end + after :each do + Puppet::Rails.teardown + end + it "should return all matching resources from the current compile and mark them non-virtual and non-exported" do one = Puppet::Parser::Resource.new('notify', 'one', :virtual => true, @@ -304,10 +308,6 @@ describe Puppet::Parser::Collector, "when collecting exported resources", :if => @collector.evaluate.should == [one, two] one.should_not be_virtual two.should_not be_virtual - - # REVISIT: Apparently we never actually marked local resources as - # non-exported. So, this is what the previous test asserted, and checking - # what it claims to do causes test failures. --daniel 2011-08-23 end it "should mark all returned resources as not virtual" do diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 2c4b5221b..d64e57e8b 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -152,7 +152,7 @@ describe Puppet::Parser::Compiler do it "should transform node class hashes into a class list" do node = Puppet::Node.new("mynode") - node.classes = {'foo'=>{'one'=>'1'}, 'bar'=>{'two'=>'2'}} + node.classes = {'foo'=>{'one'=>'p1'}, 'bar'=>{'two'=>'p2'}} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] @@ -231,7 +231,7 @@ describe Puppet::Parser::Compiler do end it "should evaluate any parameterized classes named in the node" do - classes = {'foo'=>{'1'=>'one'}, 'bar'=>{'2'=>'two'}} + classes = {'foo'=>{'p1'=>'one'}, 'bar'=>{'p2'=>'two'}} @node.stubs(:classes).returns(classes) @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) @compiler.compile @@ -609,7 +609,7 @@ describe Puppet::Parser::Compiler do # Define the given class with default parameters def define_class(name, parameters) @node.classes[name] = parameters - klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => @ast_obj, '2' => @ast_obj}) + klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => @ast_obj, 'p2' => @ast_obj}) @compiler.topscope.known_resource_types.add klass end @@ -627,20 +627,20 @@ describe Puppet::Parser::Compiler do it "should provide default values for parameters that have no values specified" do define_class('foo', {}) compile() - @catalog.resource(:class, 'foo')['1'].should == "foo" + @catalog.resource(:class, 'foo')['p1'].should == "foo" end it "should use any provided values" do - define_class('foo', {'1' => 'real_value'}) + define_class('foo', {'p1' => 'real_value'}) compile() - @catalog.resource(:class, 'foo')['1'].should == "real_value" + @catalog.resource(:class, 'foo')['p1'].should == "real_value" end it "should support providing some but not all values" do - define_class('foo', {'1' => 'real_value'}) + define_class('foo', {'p1' => 'real_value'}) compile() - @catalog.resource(:class, 'Foo')['1'].should == "real_value" - @catalog.resource(:class, 'Foo')['2'].should == "foo" + @catalog.resource(:class, 'Foo')['p1'].should == "real_value" + @catalog.resource(:class, 'Foo')['p2'].should == "foo" end it "should ensure each node class is in catalog and has appropriate tags" do @@ -648,7 +648,7 @@ describe Puppet::Parser::Compiler do @node.classes = klasses ast_obj = Puppet::Parser::AST::String.new(:value => 'foo') klasses.each do |name| - klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => ast_obj, '2' => ast_obj}) + klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => ast_obj, 'p2' => ast_obj}) @compiler.topscope.known_resource_types.add klass end catalog = @compiler.compile @@ -659,11 +659,11 @@ describe Puppet::Parser::Compiler do end it "should fail if required parameters are missing" do - klass = {'foo'=>{'1'=>'one'}} + klass = {'foo'=>{'a'=>'one'}} @node.classes = klass - klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'1' => nil, '2' => nil}) + klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'a' => nil, 'b' => nil}) @compiler.topscope.known_resource_types.add klass - lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass 2 to Class[Foo]") + lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass b to Class[Foo]") end it "should fail if invalid parameters are passed" do diff --git a/spec/unit/parser/eparser_adapter_spec.rb b/spec/unit/parser/eparser_adapter_spec.rb new file mode 100644 index 000000000..051633434 --- /dev/null +++ b/spec/unit/parser/eparser_adapter_spec.rb @@ -0,0 +1,407 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/parser/e_parser_adapter' + +describe Puppet::Parser do + + Puppet::Parser::AST + + before :each do + @known_resource_types = Puppet::Resource::TypeCollection.new("development") + @classic_parser = Puppet::Parser::Parser.new "development" + @parser = Puppet::Parser::EParserAdapter.new(@classic_parser) + @classic_parser.stubs(:known_resource_types).returns @known_resource_types + @true_ast = Puppet::Parser::AST::Boolean.new :value => true + end + + it "should require an environment at initialization" do + expect { + Puppet::Parser::EParserAdapter.new + }.to raise_error(ArgumentError, /wrong number of arguments/) + end + + describe "when parsing append operator" do + + it "should not raise syntax errors" do + expect { @parser.parse("$var += something") }.to_not raise_error + end + + it "should raise syntax error on incomplete syntax " do + expect { + @parser.parse("$var += ") + }.to raise_error(Puppet::ParseError, /Syntax error at end of file/) + end + + it "should create ast::VarDef with append=true" do + vardef = @parser.parse("$var += 2").code[0] + vardef.should be_a(Puppet::Parser::AST::VarDef) + vardef.append.should == true + end + + it "should work with arrays too" do + vardef = @parser.parse("$var += ['test']").code[0] + vardef.should be_a(Puppet::Parser::AST::VarDef) + vardef.append.should == true + end + + end + + describe "when parsing selector" do + it "should support hash access on the left hand side" do + expect { @parser.parse("$h = { 'a' => 'b' } $a = $h['a'] ? { 'b' => 'd', default => undef }") }.to_not raise_error + end + end + + describe "parsing 'unless'" do + it "should create the correct ast objects" do + Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } + @parser.parse("unless false { $var = 1 }") + end + + it "should not raise an error with empty statements" do + expect { @parser.parse("unless false { }") }.to_not raise_error + end + + #test for bug #13296 + it "should not override 'unless' as a parameter inside resources" do + lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error + end + end + + describe "when parsing parameter names" do + Puppet::Parser::Lexer::KEYWORDS.sort_tokens.each do |keyword| + it "should allow #{keyword} as a keyword" do + lambda { @parser.parse("exec {'/bin/echo foo': #{keyword} => '/usr/bin/false',}") }.should_not raise_error + end + end + end + + describe "when parsing 'if'" do + it "not, it should create the correct ast objects" do + Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } + @parser.parse("if ! true { $var = 1 }") + end + + it "boolean operation, it should create the correct ast objects" do + Puppet::Parser::AST::BooleanOperator.expects(:new).with { + |h| h[:rval].is_a?(Puppet::Parser::AST::Boolean) and h[:lval].is_a?(Puppet::Parser::AST::Boolean) and h[:operator]=="or" + } + @parser.parse("if true or true { $var = 1 }") + + end + + it "comparison operation, it should create the correct ast objects" do + Puppet::Parser::AST::ComparisonOperator.expects(:new).with { + |h| h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="<" + } + @parser.parse("if 1 < 2 { $var = 1 }") + + end + + end + + describe "when parsing if complex expressions" do + it "should create a correct ast tree" do + aststub = stub_everything 'ast' + Puppet::Parser::AST::ComparisonOperator.expects(:new).with { + |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]==">" + }.returns(aststub) + Puppet::Parser::AST::ComparisonOperator.expects(:new).with { + |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="==" + }.returns(aststub) + Puppet::Parser::AST::BooleanOperator.expects(:new).with { + |h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and" + } + @parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }") + end + + it "should raise an error on incorrect expression" do + expect { + @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }") + }.to raise_error(Puppet::ParseError, /Syntax error at '\)'/) + end + + end + + describe "when parsing resource references" do + + it "should not raise syntax errors" do + expect { @parser.parse('exec { test: param => File["a"] }') }.to_not raise_error + end + + it "should not raise syntax errors with multiple references" do + expect { @parser.parse('exec { test: param => File["a","b"] }') }.to_not raise_error + end + + it "should create an ast::ResourceReference" do + # NOTE: In egrammar, type and name are unified immediately to lower case whereas the regular grammar + # keeps the UC name in some contexts - it gets downcased later as the name of the type is in lower case. + # + Puppet::Parser::AST::ResourceReference.expects(:new).with { |arg| + arg[:line]==1 and arg[:pos] ==25 and arg[:type]=="file" and arg[:title].is_a?(Puppet::Parser::AST::ASTArray) + } + @parser.parse('exec { test: command => File["a","b"] }') + end + end + + describe "when parsing resource overrides" do + + it "should not raise syntax errors" do + expect { @parser.parse('Resource["title"] { param => value }') }.to_not raise_error + end + + it "should not raise syntax errors with multiple overrides" do + expect { @parser.parse('Resource["title1","title2"] { param => value }') }.to_not raise_error + end + + it "should create an ast::ResourceOverride" do + ro = @parser.parse('Resource["title1","title2"] { param => value }').code[0] + ro.should be_a(Puppet::Parser::AST::ResourceOverride) + ro.line.should == 1 + ro.object.should be_a(Puppet::Parser::AST::ResourceReference) + ro.parameters[0].should be_a(Puppet::Parser::AST::ResourceParam) + end + + end + + describe "when parsing if statements" do + + it "should not raise errors with empty if" do + expect { @parser.parse("if true { }") }.to_not raise_error + end + + it "should not raise errors with empty else" do + expect { @parser.parse("if false { notice('if') } else { }") }.to_not raise_error + end + + it "should not raise errors with empty if and else" do + expect { @parser.parse("if false { } else { }") }.to_not raise_error + end + + it "should create a nop node for empty branch" do + Puppet::Parser::AST::Nop.expects(:new).twice + @parser.parse("if true { }") + end + + it "should create a nop node for empty else branch" do + Puppet::Parser::AST::Nop.expects(:new) + @parser.parse("if true { notice('test') } else { }") + end + + it "should build a chain of 'ifs' if there's an 'elsif'" do + expect { @parser.parse(<<-PP) }.to_not raise_error + if true { notice('test') } elsif true {} else { } + PP + end + + end + + describe "when parsing function calls" do + it "should not raise errors with no arguments" do + expect { @parser.parse("tag()") }.to_not raise_error + end + + it "should not raise errors with rvalue function with no args" do + expect { @parser.parse("$a = template()") }.to_not raise_error + end + + it "should not raise errors with arguments" do + expect { @parser.parse("notice(1)") }.to_not raise_error + end + + it "should not raise errors with multiple arguments" do + expect { @parser.parse("notice(1,2)") }.to_not raise_error + end + + it "should not raise errors with multiple arguments and a trailing comma" do + expect { @parser.parse("notice(1,2,)") }.to_not raise_error + end + + end + + describe "when parsing arrays" do + it "should parse an array" do + expect { @parser.parse("$a = [1,2]") }.to_not raise_error + end + + it "should not raise errors with a trailing comma" do + expect { @parser.parse("$a = [1,2,]") }.to_not raise_error + end + + it "should accept an empty array" do + expect { @parser.parse("$var = []\n") }.to_not raise_error + end + end + + describe "when parsing classes" do + before :each do + @krt = Puppet::Resource::TypeCollection.new("development") + @classic_parser = Puppet::Parser::Parser.new "development" + @parser = Puppet::Parser::EParserAdapter.new(@classic_parser) + @classic_parser.stubs(:known_resource_types).returns @krt + end + + it "should not create new classes" do + @parser.parse("class foobar {}").code[0].should be_a(Puppet::Parser::AST::Hostclass) + @krt.hostclass("foobar").should be_nil + end + + it "should correctly set the parent class when one is provided" do + @parser.parse("class foobar inherits yayness {}").code[0].instantiate('')[0].parent.should == "yayness" + end + + it "should correctly set the parent class for multiple classes at a time" do + statements = @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}").code + statements[0].instantiate('')[0].parent.should == "yayness" + statements[1].instantiate('')[0].parent.should == "bar" + end + + it "should define the code when some is provided" do + @parser.parse("class foobar { $var = val }").code[0].code.should_not be_nil + end + + it "should accept parameters with trailing comma" do + @parser.parse("file { '/example': ensure => file, }").should be + end + + it "should accept parametrized classes with trailing comma" do + @parser.parse("class foobar ($var1 = 0,) { $var = val }").code[0].code.should_not be_nil + end + + it "should define parameters when provided" do + foobar = @parser.parse("class foobar($biz,$baz) {}").code[0].instantiate('')[0] + foobar.arguments.should == {"biz" => nil, "baz" => nil} + end + end + + describe "when parsing resources" do + before :each do + @krt = Puppet::Resource::TypeCollection.new("development") + @classic_parser = Puppet::Parser::Parser.new "development" + @parser = Puppet::Parser::EParserAdapter.new(@classic_parser) + @classic_parser.stubs(:known_resource_types).returns @krt + end + + it "should be able to parse class resources" do + @krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil})) + expect { @parser.parse("class { foobar: biz => stuff }") }.to_not raise_error + end + + it "should correctly mark exported resources as exported" do + @parser.parse("@@file { '/file': }").code[0].exported.should be_true + end + + it "should correctly mark virtual resources as virtual" do + @parser.parse("@file { '/file': }").code[0].virtual.should be_true + end + end + + describe "when parsing nodes" do + it "should be able to parse a node with a single name" do + node = @parser.parse("node foo { }").code[0] + node.should be_a Puppet::Parser::AST::Node + node.names.length.should == 1 + node.names[0].value.should == "foo" + end + + it "should be able to parse a node with two names" do + node = @parser.parse("node foo, bar { }").code[0] + node.should be_a Puppet::Parser::AST::Node + node.names.length.should == 2 + node.names[0].value.should == "foo" + node.names[1].value.should == "bar" + end + + it "should be able to parse a node with three names" do + node = @parser.parse("node foo, bar, baz { }").code[0] + node.should be_a Puppet::Parser::AST::Node + node.names.length.should == 3 + node.names[0].value.should == "foo" + node.names[1].value.should == "bar" + node.names[2].value.should == "baz" + end + end + + it "should fail if trying to collect defaults" do + expect { + @parser.parse("@Port { protocols => tcp }") + }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/) + end + + context "when parsing collections" do + it "should parse basic collections" do + @parser.parse("Port <| |>").code. + should be_all {|x| x.is_a? Puppet::Parser::AST::Collection } + end + + it "should parse fully qualified collections" do + @parser.parse("Port::Range <| |>").code. + should be_all {|x| x.is_a? Puppet::Parser::AST::Collection } + end + end + + it "should not assign to a fully qualified variable" do + expect { + @parser.parse("$one::two = yay") + }.to raise_error(Puppet::ParseError, /Cannot assign to variables in other namespaces/) + end + + it "should parse assignment of undef" do + tree = @parser.parse("$var = undef") + tree.code.children[0].should be_an_instance_of Puppet::Parser::AST::VarDef + tree.code.children[0].value.should be_an_instance_of Puppet::Parser::AST::Undef + end + + it "should treat classes as case insensitive" do + @classic_parser.known_resource_types.import_ast(@parser.parse("class yayness {}"), '') + @classic_parser.known_resource_types.hostclass('yayness'). + should == @classic_parser.find_hostclass("", "YayNess") + end + + it "should treat defines as case insensitive" do + @classic_parser.known_resource_types.import_ast(@parser.parse("define funtest {}"), '') + @classic_parser.known_resource_types.hostclass('funtest'). + should == @classic_parser.find_hostclass("", "fUntEst") + end + context "when parsing method calls" do + it "should parse method call with one param lambda" do + expect { @parser.parse("$a.foreach {|$a| debug $a }") }.to_not raise_error + end + it "should parse method call with two param lambda" do + expect { @parser.parse("$a.foreach {|$a,$b| debug $a }") }.to_not raise_error + end + it "should parse method call with two param lambda and default value" do + expect { @parser.parse("$a.foreach {|$a,$b=1| debug $a }") }.to_not raise_error + end + it "should parse method call without lambda (statement)" do + expect { @parser.parse("$a.foreach") }.to_not raise_error + end + it "should parse method call without lambda (expression)" do + expect { @parser.parse("$x = $a.foreach + 1") }.to_not raise_error + end + context "a receiver expression of type" do + it "variable should be allowed" do + expect { @parser.parse("$a.foreach") }.to_not raise_error + end + it "hasharrayaccess should be allowed" do + expect { @parser.parse("$a[0][1].foreach") }.to_not raise_error + end + it "quoted text should be allowed" do + expect { @parser.parse("\"monkey\".foreach") }.to_not raise_error + expect { @parser.parse("'monkey'.foreach") }.to_not raise_error + end + it "selector text should be allowed" do + expect { @parser.parse("$a ? { 'banana'=>[1,2,3]}.foreach") }.to_not raise_error + end + it "function call should be allowed" do + expect { @parser.parse("duh(1,2,3).foreach") }.to_not raise_error + end + it "method call should be allowed" do + expect { @parser.parse("$a.foo.bar") }.to_not raise_error + end + it "chained method calls with lambda should be allowed" do + expect { @parser.parse("$a.foo{||}.bar{||}") }.to_not raise_error + end + end + end +end diff --git a/spec/unit/parser/functions/extlookup_spec.rb b/spec/unit/parser/functions/extlookup_spec.rb index fae32fa97..ecd7a817a 100755 --- a/spec/unit/parser/functions/extlookup_spec.rb +++ b/spec/unit/parser/functions/extlookup_spec.rb @@ -1,6 +1,5 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'tempfile' describe "the extlookup function" do include PuppetSpec::Files @@ -33,36 +32,40 @@ describe "the extlookup function" do end it "should lookup the key in a supplied datafile" do - t = Tempfile.new('extlookup.csv') do + dir = tmpdir("extlookup_spec") + File.open("#{dir}/extlookup.csv", "w") do |t| t.puts 'key,value' t.puts 'nonkey,nonvalue' - t.close - - result = @scope.function_extlookup([ "key", "default", t.path]) - result.should == "value" end + + @scope.stubs(:[]).with('::extlookup_datadir').returns(dir) + @scope.stubs(:[]).with('::extlookup_precedence').returns(nil) + result = @scope.function_extlookup([ "key", "default", "extlookup"]) + result.should == "value" end it "should return an array if the datafile contains more than two columns" do - t = Tempfile.new('extlookup.csv') do + dir = tmpdir("extlookup_spec") + File.open("#{dir}/extlookup.csv", "w") do |t| t.puts 'key,value1,value2' t.puts 'nonkey,nonvalue,nonvalue' - t.close - - result = @scope.function_extlookup([ "key", "default", t.path]) - result.should == ["value1", "value2"] end + + @scope.stubs(:[]).with('::extlookup_datadir').returns(dir) + @scope.stubs(:[]).with('::extlookup_precedence').returns(nil) + result = @scope.function_extlookup([ "key", "default", "extlookup"]) + result.should == ["value1", "value2"] end it "should raise an error if there's no matching key and no default" do - t = Tempfile.new('extlookup.csv') do - t.puts 'key,value' + dir = tmpdir("extlookup_spec") + File.open("#{dir}/extlookup.csv", "w") do |t| t.puts 'nonkey,nonvalue' - t.close - - result = @scope.function_extlookup([ "key", nil, t.path]) - result.should == "value" end + + @scope.stubs(:[]).with('::extlookup_datadir').returns(dir) + @scope.stubs(:[]).with('::extlookup_precedence').returns(nil) + lambda { @scope.function_extlookup([ "key", nil, "extlookup"]) }.should raise_error(Puppet::ParseError, /No match found.*key/) end describe "should look in $extlookup_datadir for data files listed by $extlookup_precedence" do diff --git a/spec/unit/parser/functions/fqdn_rand_spec.rb b/spec/unit/parser/functions/fqdn_rand_spec.rb index 1facda6e8..e28dbff26 100755 --- a/spec/unit/parser/functions/fqdn_rand_spec.rb +++ b/spec/unit/parser/functions/fqdn_rand_spec.rb @@ -55,4 +55,9 @@ describe "the fqdn_rand function" do val2 = @scope.function_fqdn_rand([10000000,42]) val1.should_not eql(val2) end + + it "should use the Puppet::Util function" do + Puppet::Util.expects(:deterministic_rand).with(177093203648075535190027737376590689559,4) + @scope.function_fqdn_rand([4]) + end end diff --git a/spec/unit/parser/functions/hiera_include_spec.rb b/spec/unit/parser/functions/hiera_include_spec.rb index f06d8c69c..a71bdb6c5 100644 --- a/spec/unit/parser/functions/hiera_include_spec.rb +++ b/spec/unit/parser/functions/hiera_include_spec.rb @@ -1,6 +1,4 @@ require 'spec_helper' -require 'hiera_puppet' -require 'puppet/parser/functions/hiera_include' describe 'Puppet::Parser::Functions#hiera_include' do let :scope do Puppet::Parser::Scope.new_for_test_harness('foo') end diff --git a/spec/unit/parser/functions/hiera_spec.rb b/spec/unit/parser/functions/hiera_spec.rb index 0abcb1b28..f22eee9d4 100755 --- a/spec/unit/parser/functions/hiera_spec.rb +++ b/spec/unit/parser/functions/hiera_spec.rb @@ -1,5 +1,3 @@ -#! /usr/bin/env ruby - require 'spec_helper' describe 'Puppet::Parser::Functions#hiera' do diff --git a/spec/unit/parser/functions_spec.rb b/spec/unit/parser/functions_spec.rb index 1430ff26f..41ef54436 100755 --- a/spec/unit/parser/functions_spec.rb +++ b/spec/unit/parser/functions_spec.rb @@ -2,6 +2,9 @@ require 'spec_helper' describe Puppet::Parser::Functions do + def callable_functions_from(mod) + Class.new { include mod }.new + end it "should have a method for returning an environment-specific module" do Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.new("myenv")).should be_instance_of(Module) @@ -16,15 +19,15 @@ describe Puppet::Parser::Functions do end describe "when calling newfunction" do + let(:function_module) { Module.new } before do - @module = Module.new - Puppet::Parser::Functions.stubs(:environment_module).returns @module + Puppet::Parser::Functions.stubs(:environment_module).returns(function_module) end it "should create the function in the environment module" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } - @module.should be_method_defined :function_name + function_module.should be_method_defined :function_name end it "should warn if the function already exists" do @@ -37,12 +40,22 @@ describe Puppet::Parser::Functions do it "should raise an error if the function type is not correct" do lambda { Puppet::Parser::Functions.newfunction("name", :type => :unknown) { |args| } }.should raise_error Puppet::DevError, "Invalid statement type :unknown" end + + it "instruments the function to profiles the execution" do + messages = [] + Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id") + + Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } + callable_functions_from(function_module).function_name([]) + + messages.first.should =~ /Called name/ + end end describe "when calling function to test function existence" do + let(:function_module) { Module.new } before do - @module = Module.new - Puppet::Parser::Functions.stubs(:environment_module).returns @module + Puppet::Parser::Functions.stubs(:environment_module).returns(function_module) end it "should return false if the function doesn't exist" do @@ -65,40 +78,39 @@ describe Puppet::Parser::Functions do end describe "when calling function to test arity" do - before :each do - Puppet::Node::Environment.stubs(:current).returns(nil) - @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) - @scope = Puppet::Parser::Scope.new(@compiler) + let(:function_module) { Module.new } + before do + Puppet::Parser::Functions.stubs(:environment_module).returns(function_module) end it "should raise an error if the function is called with too many arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } - lambda { @scope.function_name([1,2,3]) }.should raise_error ArgumentError + lambda { callable_functions_from(function_module).function_name([1,2,3]) }.should raise_error ArgumentError end it "should raise an error if the function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } - lambda { @scope.function_name([1]) }.should raise_error ArgumentError + lambda { callable_functions_from(function_module).function_name([1]) }.should raise_error ArgumentError end it "should not raise an error if the function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } - lambda { @scope.function_name([1,2]) }.should_not raise_error ArgumentError + lambda { callable_functions_from(function_module).function_name([1,2]) }.should_not raise_error ArgumentError end it "should raise an error if the variable arg function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } - lambda { @scope.function_name([1]) }.should raise_error ArgumentError + lambda { callable_functions_from(function_module).function_name([1]) }.should raise_error ArgumentError end it "should not raise an error if the variable arg function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } - lambda { @scope.function_name([1,2]) }.should_not raise_error ArgumentError + lambda { callable_functions_from(function_module).function_name([1,2]) }.should_not raise_error ArgumentError end it "should not raise an error if the variable arg function is called with more number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } - lambda { @scope.function_name([1,2,3]) }.should_not raise_error ArgumentError + lambda { callable_functions_from(function_module).function_name([1,2,3]) }.should_not raise_error ArgumentError end end diff --git a/spec/unit/parser/methods/collect_spec.rb b/spec/unit/parser/methods/collect_spec.rb new file mode 100644 index 000000000..13cd9eda6 --- /dev/null +++ b/spec/unit/parser/methods/collect_spec.rb @@ -0,0 +1,110 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' + +require 'unit/parser/methods/shared' + +describe 'the collect method' do + include PuppetSpec::Compiler + + before :each do + Puppet[:parser] = "future" + end + + context "using future parser" do + context "in Ruby style should be callable as" do + it 'collect on an array (multiplying each value by 2)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $a.collect {|$x| $x*2}.foreach {|$v| + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_4")['ensure'].should == 'present' + catalog.resource(:file, "/file_6")['ensure'].should == 'present' + end + + it 'collect on a hash selecting keys' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.collect {|$x| $x[0]}.foreach {|$k| + file { "/file_$k": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'present' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + it 'foreach on a hash selecting value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.collect {|$x| $x[1]}.foreach {|$k| + file { "/file_$k": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + end + context "in Java style should be callable as" do + shared_examples_for 'java style' do + it 'collect on an array (multiplying each value by 2)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $a.collect |$x| #{farr}{ $x*2}.foreach |$v| #{farr}{ + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_4")['ensure'].should == 'present' + catalog.resource(:file, "/file_6")['ensure'].should == 'present' + end + + it 'collect on a hash selecting keys' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.collect |$x| #{farr}{ $x[0]}.foreach |$k| #{farr}{ + file { "/file_$k": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'present' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + + it 'foreach on a hash selecting value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.collect |$x| #{farr} {$x[1]}.foreach |$k|#{farr}{ + file { "/file_$k": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + end + describe 'without fat arrow' do + it_should_behave_like 'java style' do + let(:farr) { '' } + end + end + describe 'with fat arrow' do + it_should_behave_like 'java style' do + let(:farr) { '=>' } + end + end + end + end + + it_should_behave_like 'all iterative functions argument checks', 'collect' + it_should_behave_like 'all iterative functions hash handling', 'collect' +end diff --git a/spec/unit/parser/methods/each_spec.rb b/spec/unit/parser/methods/each_spec.rb new file mode 100644 index 000000000..4d276e95d --- /dev/null +++ b/spec/unit/parser/methods/each_spec.rb @@ -0,0 +1,91 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' +require 'rubygems' + +describe 'the each method' do + include PuppetSpec::Compiler + + before :each do + Puppet[:parser] = 'future' + end + + context "should be callable as" do + it 'each on an array selecting each value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $a.each |$v| { + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + it 'each on an array selecting each value - function call style' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + each ($a) |$index, $v| => { + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + it 'each on an array with index' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [present, absent, present] + $a.each |$k,$v| { + file { "/file_${$k+1}": ensure => $v } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'absent' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + it 'each on a hash selecting entries' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>'present','b'=>'absent','c'=>'present'} + $a.each |$e| { + file { "/file_${e[0]}": ensure => $e[1] } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'absent' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + it 'each on a hash selecting key and value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>present,'b'=>absent,'c'=>present} + $a.each |$k, $v| { + file { "/file_$k": ensure => $v } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'absent' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + end + context "should produce receiver" do + it 'each checking produced value using single expression' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, 3, 2] + $b = $a.each |$x| { "unwanted" } + file { "/file_${b[1]}": + ensure => present + } + MANIFEST + + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + end +end diff --git a/spec/unit/parser/methods/foreach_spec.rb b/spec/unit/parser/methods/foreach_spec.rb new file mode 100755 index 000000000..72a9379a4 --- /dev/null +++ b/spec/unit/parser/methods/foreach_spec.rb @@ -0,0 +1,91 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' +require 'rubygems' + +describe 'the foreach method' do + include PuppetSpec::Compiler + + before :each do + Puppet[:parser] = 'future' + end + + context "should be callable as" do + it 'foreach on an array selecting each value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $a.foreach {|$v| + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + it 'foreach on an array selecting each value - function call style' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + foreach ($a) {|$v| + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + it 'foreach on an array with index' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [present, absent, present] + $a.foreach {|$k,$v| + file { "/file_${$k+1}": ensure => $v } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'absent' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + it 'foreach on a hash selecting entries' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>'present','b'=>'absent','c'=>'present'} + $a.foreach {|$e| + file { "/file_${e[0]}": ensure => $e[1] } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'absent' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + it 'foreach on a hash selecting key and value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>present,'b'=>absent,'c'=>present} + $a.foreach {|$k, $v| + file { "/file_$k": ensure => $v } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'absent' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + end + context "should produce receiver" do + it 'each checking produced value using single expression' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, 3, 2] + $b = $a.each |$x| { $x } + file { "/file_${b[1]}": + ensure => present + } + MANIFEST + + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + end +end diff --git a/spec/unit/parser/methods/reduce_spec.rb b/spec/unit/parser/methods/reduce_spec.rb new file mode 100644 index 000000000..99ecfd18d --- /dev/null +++ b/spec/unit/parser/methods/reduce_spec.rb @@ -0,0 +1,67 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' + +describe 'the reduce method' do + include PuppetSpec::Compiler + + before :all do + # enable switching back + @saved_parser = Puppet[:parser] + # These tests only work with future parser + end + after :all do + # switch back to original + Puppet[:parser] = @saved_parser + end + + before :each do + node = Puppet::Node.new("floppy", :environment => 'production') + @compiler = Puppet::Parser::Compiler.new(node) + @scope = Puppet::Parser::Scope.new(@compiler) + @topscope = @scope.compiler.topscope + @scope.parent = @topscope + Puppet[:parser] = 'future' + end + + context "should be callable as" do + it 'reduce on an array' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $b = $a.reduce {|$memo, $x| $memo + $x } + file { "/file_$b": ensure => present } + MANIFEST + + catalog.resource(:file, "/file_6")['ensure'].should == 'present' + end + it 'reduce on an array with start value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $b = $a.reduce(4) {|$memo, $x| $memo + $x } + file { "/file_$b": ensure => present } + MANIFEST + + catalog.resource(:file, "/file_10")['ensure'].should == 'present' + end + it 'reduce on a hash' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {a=>1, b=>2, c=>3} + $start = [ignored, 4] + $b = $a.reduce {|$memo, $x| ['sum', $memo[1] + $x[1]] } + file { "/file_${$b[0]}_${$b[1]}": ensure => present } + MANIFEST + + catalog.resource(:file, "/file_sum_6")['ensure'].should == 'present' + end + it 'reduce on a hash with start value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {a=>1, b=>2, c=>3} + $start = ['ignored', 4] + $b = $a.reduce($start) {|$memo, $x| ['sum', $memo[1] + $x[1]] } + file { "/file_${$b[0]}_${$b[1]}": ensure => present } + MANIFEST + + catalog.resource(:file, "/file_sum_10")['ensure'].should == 'present' + end + end +end diff --git a/spec/unit/parser/methods/reject_spec.rb b/spec/unit/parser/methods/reject_spec.rb new file mode 100644 index 000000000..e37eaebc5 --- /dev/null +++ b/spec/unit/parser/methods/reject_spec.rb @@ -0,0 +1,73 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' + +require 'unit/parser/methods/shared' + +describe 'the reject method' do + include PuppetSpec::Compiler + + before :each do + Puppet[:parser] = 'future' + end + + it 'rejects on an array (no berries)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = ['strawberry','blueberry','orange'] + $a.reject {|$x| $x =~ /berry$/}.foreach {|$v| + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_orange")['ensure'].should == 'present' + catalog.resource(:file, "/file_strawberry").should == nil + end + + it 'produces an array when acting on an array' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = ['strawberry','blueberry','orange'] + $b = $a.reject {|$x| $x =~ /berry$/} + file { "/file_${b[0]}": ensure => present } + + MANIFEST + + catalog.resource(:file, "/file_orange")['ensure'].should == 'present' + catalog.resource(:file, "/file_strawberry").should == nil + end + + it 'rejects on a hash (all berries) by key' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} + $a.reject {|$x| $x[0] =~ /berry$/}.foreach {|$v| + file { "/file_${v[0]}": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_orange")['ensure'].should == 'present' + end + + it 'produces a hash when acting on a hash' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'strawberry'=>'red','blueberry'=>'blue','grape'=>'purple'} + $b = $a.reject {|$x| $x[0] =~ /berry$/} + file { "/file_${b[grape]}": ensure => present } + + MANIFEST + + catalog.resource(:file, "/file_purple")['ensure'].should == 'present' + end + + it 'rejects on a hash (all berries) by value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} + $a.reject {|$x| $x[1] =~ /berry$/}.foreach {|$v| + file { "/file_${v[0]}": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_orange")['ensure'].should == 'present' + end + + it_should_behave_like 'all iterative functions argument checks', 'reject' + it_should_behave_like 'all iterative functions hash handling', 'reject' +end diff --git a/spec/unit/parser/methods/select_spec.rb b/spec/unit/parser/methods/select_spec.rb new file mode 100644 index 000000000..e61ee3a31 --- /dev/null +++ b/spec/unit/parser/methods/select_spec.rb @@ -0,0 +1,79 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' + +require 'unit/parser/methods/shared' + +describe 'the select method' do + include PuppetSpec::Compiler + + before :each do + Puppet[:parser] = 'future' + end + + it 'should select on an array (all berries)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = ['strawberry','blueberry','orange'] + $a.select {|$x| $x =~ /berry$/}.foreach {|$v| + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' + catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + end + + it 'should produce an array when acting on an array' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = ['strawberry','blueberry','orange'] + $b = $a.select {|$x| $x =~ /berry$/} + file { "/file_${b[0]}": ensure => present } + file { "/file_${b[1]}": ensure => present } + MANIFEST + + catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' + catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + end + + it 'selects on a hash (all berries) by key' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} + $a.select {|$x| $x[0] =~ /berry$/}.foreach {|$v| + file { "/file_${v[0]}": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' + catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + end + + it 'should produce a hash when acting on a hash' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} + $b = $a.select {|$x| $x[0] =~ /berry$/} + file { "/file_${b['strawberry']}": ensure => present } + file { "/file_${b['blueberry']}": ensure => present } + file { "/file_${b['orange']}": ensure => present } + + MANIFEST + + catalog.resource(:file, "/file_red")['ensure'].should == 'present' + catalog.resource(:file, "/file_blue")['ensure'].should == 'present' + catalog.resource(:file, "/file_")['ensure'].should == 'present' + end + + it 'selects on a hash (all berries) by value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} + $a.select {|$x| $x[1] =~ /berry$/}.foreach {|$v| + file { "/file_${v[0]}": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_strawb")['ensure'].should == 'present' + catalog.resource(:file, "/file_blueb")['ensure'].should == 'present' + end + + it_should_behave_like 'all iterative functions argument checks', 'select' + it_should_behave_like 'all iterative functions hash handling', 'select' +end diff --git a/spec/unit/parser/methods/shared.rb b/spec/unit/parser/methods/shared.rb new file mode 100644 index 000000000..bf9c9f05f --- /dev/null +++ b/spec/unit/parser/methods/shared.rb @@ -0,0 +1,61 @@ + +shared_examples_for 'all iterative functions hash handling' do |func| + it 'passes a hash entry as an array of the key and value' do + catalog = compile_to_catalog(<<-MANIFEST) + {a=>1}.#{func} { |$v| notify { "${v[0]} ${v[1]}": } } + MANIFEST + + catalog.resource(:notify, "a 1").should_not be_nil + end +end + +shared_examples_for 'all iterative functions argument checks' do |func| + + it 'raises an error when defined with more than 1 argument' do + expect do + compile_to_catalog(<<-MANIFEST) + [1].#{func} { |$x, $yikes| } + MANIFEST + end.to raise_error(Puppet::Error, /Too few arguments/) + end + + it 'raises an error when defined with fewer than 1 argument' do + expect do + compile_to_catalog(<<-MANIFEST) + [1].#{func} { || } + MANIFEST + end.to raise_error(Puppet::Error, /Too many arguments/) + end + + it 'raises an error when used against an unsupported type' do + expect do + compile_to_catalog(<<-MANIFEST) + "not correct".#{func} { |$v| } + MANIFEST + end.to raise_error(Puppet::Error, /must be an Array or a Hash/) + end + + it 'raises an error when called with any parameters besides a block' do + expect do + compile_to_catalog(<<-MANIFEST) + [1].#{func}(1) { |$v| } + MANIFEST + end.to raise_error(Puppet::Error, /Wrong number of arguments/) + end + + it 'raises an error when called without a block' do + expect do + compile_to_catalog(<<-MANIFEST) + [1].#{func}() + MANIFEST + end.to raise_error(Puppet::Error, /Wrong number of arguments/) + end + + it 'raises an error when called without a block' do + expect do + compile_to_catalog(<<-MANIFEST) + [1].#{func}(1) + MANIFEST + end.to raise_error(Puppet::Error, /must be a parameterized block/) + end +end diff --git a/spec/unit/parser/methods/slice_spec.rb b/spec/unit/parser/methods/slice_spec.rb new file mode 100644 index 000000000..b213415a1 --- /dev/null +++ b/spec/unit/parser/methods/slice_spec.rb @@ -0,0 +1,97 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' +require 'rubygems' + +describe 'methods' do + include PuppetSpec::Compiler + + before :all do + # enable switching back + @saved_parser = Puppet[:parser] + # These tests only work with future parser + Puppet[:parser] = 'future' + end + after :all do + # switch back to original + Puppet[:parser] = @saved_parser + end + + before :each do + node = Puppet::Node.new("floppy", :environment => 'production') + @compiler = Puppet::Parser::Compiler.new(node) + @scope = Puppet::Parser::Scope.new(@compiler) + @topscope = @scope.compiler.topscope + @scope.parent = @topscope + Puppet[:parser] = 'future' + end + + context "should be callable on array as" do + + it 'slice with explicit parameters' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, present, 2, absent, 3, present] + $a.slice(2) |$k,$v| { + file { "/file_${$k}": ensure => $v } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'absent' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + it 'slice with one parameter' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, present, 2, absent, 3, present] + $a.slice(2) |$k| { + file { "/file_${$k[0]}": ensure => $k[1] } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'absent' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + it 'slice with shorter last slice' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, present, 2, present, 3, absent] + $a.slice(4) |$a, $b, $c, $d| { + file { "/file_$a.$c": ensure => $b } + } + MANIFEST + + catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' + end + end + context "should be callable on hash as" do + + it 'slice with explicit parameters, missing are empty' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {1=>present, 2=>present, 3=>absent} + $a.slice(2) |$a,$b| { + file { "/file_${a[0]}.${b[0]}": ensure => $a[1] } + } + MANIFEST + + catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' + end + + end + context "when called without a block" do + it "should produce an array with the result" do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, present, 2, absent, 3, present] + $a.slice(2).each |$k| { + file { "/file_${$k[0]}": ensure => $k[1] } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'absent' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + + end + end +end diff --git a/spec/unit/parser/parser_spec.rb b/spec/unit/parser/parser_spec.rb index 7ff0c17b2..07ab6780c 100755 --- a/spec/unit/parser/parser_spec.rb +++ b/spec/unit/parser/parser_spec.rb @@ -100,11 +100,11 @@ describe Puppet::Parser do Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } @parser.parse("unless false { $var = 1 }") end - + it "should not raise an error with empty statements" do expect { @parser.parse("unless false { }") }.to_not raise_error end - + #test for bug #13296 it "should not override 'unless' as a parameter inside resources" do lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error diff --git a/spec/unit/parser/scope_spec.rb b/spec/unit/parser/scope_spec.rb index 86e9a8b10..8540ad20d 100755 --- a/spec/unit/parser/scope_spec.rb +++ b/spec/unit/parser/scope_spec.rb @@ -84,32 +84,34 @@ describe Puppet::Parser::Scope do end describe "when custom functions are called" do - before :each do - @env = Puppet::Node::Environment.new('testing') - @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new('foo', :environment => @env)) - @scope = Puppet::Parser::Scope.new(@compiler) - end + let(:env) { Puppet::Node::Environment.new('testing') } + let(:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new('foo', :environment => env)) } + let(:scope) { Puppet::Parser::Scope.new(compiler) } - it "should load and call the method if it looks like a function and it exists" do - @scope.function_sprintf(["%b", 123]).should == "1111011" + it "calls methods prefixed with function_ as custom functions" do + scope.function_sprintf(["%b", 123]).should == "1111011" end - it "should raise and error when called without an Array" do - expect { @scope.function_sprintf("%b", 123) }.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ + it "raises an error when arguments are not passed in an Array" do + expect do + scope.function_sprintf("%b", 123) + end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end - it "should raise and error when subsequent calls are without an Array" do - @scope.function_sprintf(["first call"]) + it "raises an error on subsequent calls when arguments are not passed in an Array" do + scope.function_sprintf(["first call"]) - expect { @scope.function_sprintf("%b", 123) }.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ + expect do + scope.function_sprintf("%b", 123) + end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end - it "should raise NoMethodError if the method doesn't look like a function" do - expect { @scope.sprintf(["%b", 123]) }.to raise_error(NoMethodError) + it "raises NoMethodError when the not prefixed" do + expect { scope.sprintf(["%b", 123]) }.to raise_error(NoMethodError) end - it "should raise NoMethodError if the method looks like a function but doesn't exist" do - expect { @scope.function_fake_bs(['cows']) }.to raise_error(NoMethodError) + it "raises NoMethodError when prefixed with function_ but it doesn't exist" do + expect { scope.function_fake_bs(['cows']) }.to raise_error(NoMethodError) end end @@ -493,6 +495,27 @@ describe Puppet::Parser::Scope do end end + context "when using ephemeral as local scope" do + it "should store all variables in local scope" do + @scope.new_ephemeral true + @scope.setvar("apple", :fruit) + @scope["apple"].should == :fruit + end + + it "should remove all local scope variables on unset" do + @scope.new_ephemeral true + @scope.setvar("apple", :fruit) + @scope["apple"].should == :fruit + @scope.unset_ephemeral_var + @scope["apple"].should == nil + end + it "should be created from a hash" do + @scope.ephemeral_from({ "apple" => :fruit, "strawberry" => :berry}) + @scope["apple"].should == :fruit + @scope["strawberry"].should == :berry + end + end + describe "when setting ephemeral vars from matches" do before :each do @match = stub 'match', :is_a? => true diff --git a/spec/unit/parser/templatewrapper_spec.rb b/spec/unit/parser/templatewrapper_spec.rb index 1b95b0a73..81e0d846d 100755 --- a/spec/unit/parser/templatewrapper_spec.rb +++ b/spec/unit/parser/templatewrapper_spec.rb @@ -70,6 +70,12 @@ describe Puppet::Parser::TemplateWrapper do tw.tags.should == ["tag1","tag2"] end + it "warns about deprecated access to in-scope variables via method calls" do + Puppet.expects(:deprecation_warning).with("Variable access via 'in_scope_variable' is deprecated. Use '@in_scope_variable' instead. template[inline]:1") + scope["in_scope_variable"] = "is good" + tw.result("<%= in_scope_variable %>") + end + it "provides access to in-scope variables via method calls" do scope["in_scope_variable"] = "is good" tw.result("<%= in_scope_variable %>").should == "is good" @@ -93,11 +99,6 @@ describe Puppet::Parser::TemplateWrapper do tw.result("<%= @one %>").should == "foo" end - it "should not error out if one of the variables is a symbol" do - scope.expects(:to_hash).returns(:_timestamp => "1234") - tw.result("<%= @_timestamp %>").should == "1234" - end - %w{! . ; :}.each do |badchar| it "translates #{badchar} to _ in instance variables" do scope["one#{badchar}"] = "foo" diff --git a/spec/unit/parser/type_loader_spec.rb b/spec/unit/parser/type_loader_spec.rb index e15c05fea..2938d4563 100755 --- a/spec/unit/parser/type_loader_spec.rb +++ b/spec/unit/parser/type_loader_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' require 'puppet/parser/type_loader' +require 'puppet/parser/parser_factory' +require 'puppet/parser/e_parser_adapter' require 'puppet_spec/modules' require 'puppet_spec/files' @@ -9,222 +11,246 @@ describe Puppet::Parser::TypeLoader do include PuppetSpec::Modules include PuppetSpec::Files - before do - @loader = Puppet::Parser::TypeLoader.new(:myenv) - Puppet.expects(:deprecation_warning).never - end - - it "should support an environment" do - loader = Puppet::Parser::TypeLoader.new(:myenv) - loader.environment.name.should == :myenv - end - - it "should include the Environment Helper" do - @loader.class.ancestors.should be_include(Puppet::Node::Environment::Helper) - end - - it "should delegate its known resource types to its environment" do - @loader.known_resource_types.should be_instance_of(Puppet::Resource::TypeCollection) - end - - describe "when loading names from namespaces" do - it "should do nothing if the name to import is an empty string" do - @loader.expects(:name2files).never - @loader.try_load_fqname(:hostclass, "") { |filename, modname| raise :should_not_occur }.should be_nil + shared_examples_for 'the typeloader' do + before do + @loader = Puppet::Parser::TypeLoader.new(:myenv) + Puppet.expects(:deprecation_warning).never end - it "should attempt to import each generated name" do - @loader.expects(:import).with("foo/bar",nil).returns([]) - @loader.expects(:import).with("foo",nil).returns([]) - @loader.try_load_fqname(:hostclass, "foo::bar") { |f| false } + it "should support an environment" do + loader = Puppet::Parser::TypeLoader.new(:myenv) + loader.environment.name.should == :myenv end - end - describe "when importing" do - before do - Puppet::Parser::Files.stubs(:find_manifests).returns ["modname", %w{file}] - Puppet::Parser::Parser.any_instance.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) - Puppet::Parser::Parser.any_instance.stubs(:file=) + it "should include the Environment Helper" do + @loader.class.ancestors.should be_include(Puppet::Node::Environment::Helper) end - it "should return immediately when imports are being ignored" do - Puppet::Parser::Files.expects(:find_manifests).never - Puppet[:ignoreimport] = true - @loader.import("foo").should be_nil + it "should delegate its known resource types to its environment" do + @loader.known_resource_types.should be_instance_of(Puppet::Resource::TypeCollection) end - it "should find all manifests matching the file or pattern" do - Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| pat == "myfile" }.returns ["modname", %w{one}] - @loader.import("myfile") - end + describe "when loading names from namespaces" do + it "should do nothing if the name to import is an empty string" do + @loader.expects(:name2files).never + @loader.try_load_fqname(:hostclass, "") { |filename, modname| raise :should_not_occur }.should be_nil + end - it "should use the directory of the current file if one is set" do - Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:cwd] == make_absolute("/current") }.returns ["modname", %w{one}] - @loader.import("myfile", make_absolute("/current/file")) + it "should attempt to import each generated name" do + @loader.expects(:import).with("foo/bar",nil).returns([]) + @loader.expects(:import).with("foo",nil).returns([]) + @loader.try_load_fqname(:hostclass, "foo::bar") { |f| false } + end end - it "should pass the environment when looking for files" do - Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:environment] == @loader.environment }.returns ["modname", %w{one}] - @loader.import("myfile") - end + describe "when importing" do + before do + Puppet::Parser::Files.stubs(:find_manifests).returns ["modname", %w{file}] + parser_class.any_instance.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) + parser_class.any_instance.stubs(:file=) + end - it "should fail if no files are found" do - Puppet::Parser::Files.expects(:find_manifests).returns [nil, []] - lambda { @loader.import("myfile") }.should raise_error(Puppet::ImportError) - end + it "should return immediately when imports are being ignored" do + Puppet::Parser::Files.expects(:find_manifests).never + Puppet[:ignoreimport] = true + @loader.import("foo").should be_nil + end - it "should parse each found file" do - Puppet::Parser::Files.expects(:find_manifests).returns ["modname", [make_absolute("/one")]] - @loader.expects(:parse_file).with(make_absolute("/one")).returns(Puppet::Parser::AST::Hostclass.new('')) - @loader.import("myfile") - end + it "should find all manifests matching the file or pattern" do + Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| pat == "myfile" }.returns ["modname", %w{one}] + @loader.import("myfile") + end - it "should make each file qualified before attempting to parse it" do - Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{one}] - @loader.expects(:parse_file).with(make_absolute("/current/one")).returns(Puppet::Parser::AST::Hostclass.new('')) - @loader.import("myfile", make_absolute("/current/file")) - end + it "should use the directory of the current file if one is set" do + Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:cwd] == make_absolute("/current") }.returns ["modname", %w{one}] + @loader.import("myfile", make_absolute("/current/file")) + end - it "should not attempt to import files that have already been imported" do - Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}] - Puppet::Parser::Parser.any_instance.expects(:parse).once.returns(Puppet::Parser::AST::Hostclass.new('')) - @loader.import("myfile") + it "should pass the environment when looking for files" do + Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:environment] == @loader.environment }.returns ["modname", %w{one}] + @loader.import("myfile") + end - # This will fail if it tries to reimport the file. - @loader.import("myfile") - end - end + it "should fail if no files are found" do + Puppet::Parser::Files.expects(:find_manifests).returns [nil, []] + lambda { @loader.import("myfile") }.should raise_error(Puppet::ImportError) + end - describe "when importing all" do - before do - @base = tmpdir("base") + it "should parse each found file" do + Puppet::Parser::Files.expects(:find_manifests).returns ["modname", [make_absolute("/one")]] + @loader.expects(:parse_file).with(make_absolute("/one")).returns(Puppet::Parser::AST::Hostclass.new('')) + @loader.import("myfile") + end - # Create two module path directories - @modulebase1 = File.join(@base, "first") - FileUtils.mkdir_p(@modulebase1) - @modulebase2 = File.join(@base, "second") - FileUtils.mkdir_p(@modulebase2) + it "should make each file qualified before attempting to parse it" do + Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{one}] + @loader.expects(:parse_file).with(make_absolute("/current/one")).returns(Puppet::Parser::AST::Hostclass.new('')) + @loader.import("myfile", make_absolute("/current/file")) + end - Puppet[:modulepath] = "#{@modulebase1}#{File::PATH_SEPARATOR}#{@modulebase2}" - end + it "should not attempt to import files that have already been imported" do + @loader = Puppet::Parser::TypeLoader.new(:myenv) - def mk_module(basedir, name) - PuppetSpec::Modules.create(name, basedir) + Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}] + parser_class.any_instance.expects(:parse).once.returns(Puppet::Parser::AST::Hostclass.new('')) + other_parser_class.any_instance.expects(:parse).never.returns(Puppet::Parser::AST::Hostclass.new('')) + @loader.import("myfile") + + # This will fail if it tries to reimport the file. + @loader.import("myfile") + end end - # We have to pass the base path so that we can - # write to modules that are in the second search path - def mk_manifests(base, mod, type, files) - exts = {"ruby" => ".rb", "puppet" => ".pp"} - files.collect do |file| - name = mod.name + "::" + file.gsub("/", "::") - path = File.join(base, mod.name, "manifests", file + exts[type]) - FileUtils.mkdir_p(File.split(path)[0]) - - # write out the class - if type == "ruby" - File.open(path, "w") { |f| f.print "hostclass '#{name}' do\nend" } - else - File.open(path, "w") { |f| f.print "class #{name} {}" } + describe "when importing all" do + before do + @base = tmpdir("base") + + # Create two module path directories + @modulebase1 = File.join(@base, "first") + FileUtils.mkdir_p(@modulebase1) + @modulebase2 = File.join(@base, "second") + FileUtils.mkdir_p(@modulebase2) + + Puppet[:modulepath] = "#{@modulebase1}#{File::PATH_SEPARATOR}#{@modulebase2}" + end + + def mk_module(basedir, name) + PuppetSpec::Modules.create(name, basedir) + end + + # We have to pass the base path so that we can + # write to modules that are in the second search path + def mk_manifests(base, mod, type, files) + exts = {"ruby" => ".rb", "puppet" => ".pp"} + files.collect do |file| + name = mod.name + "::" + file.gsub("/", "::") + path = File.join(base, mod.name, "manifests", file + exts[type]) + FileUtils.mkdir_p(File.split(path)[0]) + + # write out the class + if type == "ruby" + File.open(path, "w") { |f| f.print "hostclass '#{name}' do\nend" } + else + File.open(path, "w") { |f| f.print "class #{name} {}" } + end + name end - name end - end - it "should load all puppet manifests from all modules in the specified environment" do - @module1 = mk_module(@modulebase1, "one") - @module2 = mk_module(@modulebase2, "two") + it "should load all puppet manifests from all modules in the specified environment" do + @module1 = mk_module(@modulebase1, "one") + @module2 = mk_module(@modulebase2, "two") - mk_manifests(@modulebase1, @module1, "puppet", %w{a b}) - mk_manifests(@modulebase2, @module2, "puppet", %w{c d}) + mk_manifests(@modulebase1, @module1, "puppet", %w{a b}) + mk_manifests(@modulebase2, @module2, "puppet", %w{c d}) - @loader.import_all + @loader.import_all - @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type) - end + @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type) + end - it "should load all ruby manifests from all modules in the specified environment" do - Puppet.expects(:deprecation_warning).at_least(1) + it "should load all ruby manifests from all modules in the specified environment" do + Puppet.expects(:deprecation_warning).at_least(1) - @module1 = mk_module(@modulebase1, "one") - @module2 = mk_module(@modulebase2, "two") + @module1 = mk_module(@modulebase1, "one") + @module2 = mk_module(@modulebase2, "two") - mk_manifests(@modulebase1, @module1, "ruby", %w{a b}) - mk_manifests(@modulebase2, @module2, "ruby", %w{c d}) + mk_manifests(@modulebase1, @module1, "ruby", %w{a b}) + mk_manifests(@modulebase2, @module2, "ruby", %w{c d}) - @loader.import_all + @loader.import_all - @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type) - end + @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type) + end - it "should not load manifests from duplicate modules later in the module path" do - @module1 = mk_module(@modulebase1, "one") + it "should not load manifests from duplicate modules later in the module path" do + @module1 = mk_module(@modulebase1, "one") - # duplicate - @module2 = mk_module(@modulebase2, "one") + # duplicate + @module2 = mk_module(@modulebase2, "one") - mk_manifests(@modulebase1, @module1, "puppet", %w{a}) - mk_manifests(@modulebase2, @module2, "puppet", %w{c}) + mk_manifests(@modulebase1, @module1, "puppet", %w{a}) + mk_manifests(@modulebase2, @module2, "puppet", %w{c}) - @loader.import_all + @loader.import_all - @loader.environment.known_resource_types.hostclass("one::c").should be_nil - end + @loader.environment.known_resource_types.hostclass("one::c").should be_nil + end - it "should load manifests from subdirectories" do - @module1 = mk_module(@modulebase1, "one") + it "should load manifests from subdirectories" do + @module1 = mk_module(@modulebase1, "one") - mk_manifests(@modulebase1, @module1, "puppet", %w{a a/b a/b/c}) + mk_manifests(@modulebase1, @module1, "puppet", %w{a a/b a/b/c}) - @loader.import_all + @loader.import_all - @loader.environment.known_resource_types.hostclass("one::a::b").should be_instance_of(Puppet::Resource::Type) - @loader.environment.known_resource_types.hostclass("one::a::b::c").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::a::b").should be_instance_of(Puppet::Resource::Type) + @loader.environment.known_resource_types.hostclass("one::a::b::c").should be_instance_of(Puppet::Resource::Type) + end end - end - describe "when parsing a file" do - before do - @parser = Puppet::Parser::Parser.new(@loader.environment) - @parser.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) - @parser.stubs(:file=) - Puppet::Parser::Parser.stubs(:new).with(@loader.environment).returns @parser - end + describe "when parsing a file" do + before do + @parser = Puppet::Parser::ParserFactory.parser(@loader.environment) + @parser.class.should == parser_class + @parser.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) + @parser.stubs(:file=) + Puppet::Parser::ParserFactory.stubs(:parser).with(@loader.environment).returns @parser + end - it "should create a new parser instance for each file using the current environment" do - Puppet::Parser::Parser.expects(:new).with(@loader.environment).returns @parser - @loader.parse_file("/my/file") - end + it "should create a new parser instance for each file using the current environment" do + Puppet::Parser::ParserFactory.expects(:parser).with(@loader.environment).returns @parser + @loader.parse_file("/my/file") + end - it "should assign the parser its file and parse" do - @parser.expects(:file=).with("/my/file") - @parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) - @loader.parse_file("/my/file") + it "should assign the parser its file and parse" do + @parser.expects(:file=).with("/my/file") + @parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')) + @loader.parse_file("/my/file") + end end - end - it "should be able to add classes to the current resource type collection" do - file = tmpfile("simple_file.pp") - File.open(file, "w") { |f| f.puts "class foo {}" } - @loader.import(file) + it "should be able to add classes to the current resource type collection" do + file = tmpfile("simple_file.pp") + File.open(file, "w") { |f| f.puts "class foo {}" } + @loader.import(file) - @loader.known_resource_types.hostclass("foo").should be_instance_of(Puppet::Resource::Type) - end + @loader.known_resource_types.hostclass("foo").should be_instance_of(Puppet::Resource::Type) + end - describe "when deciding where to look for files" do - { 'foo' => ['foo'], - 'foo::bar' => ['foo/bar', 'foo'], - 'foo::bar::baz' => ['foo/bar/baz', 'foo/bar', 'foo'] - }.each do |fqname, expected_paths| - it "should look for #{fqname.inspect} in #{expected_paths.inspect}" do - @loader.instance_eval { name2files(fqname) }.should == expected_paths + describe "when deciding where to look for files" do + { 'foo' => ['foo'], + 'foo::bar' => ['foo/bar', 'foo'], + 'foo::bar::baz' => ['foo/bar/baz', 'foo/bar', 'foo'] + }.each do |fqname, expected_paths| + it "should look for #{fqname.inspect} in #{expected_paths.inspect}" do + @loader.instance_eval { name2files(fqname) }.should == expected_paths + end end end end + describe 'when using the classic parser' do + before :each do + Puppet[:parser] = 'current' + end + it_should_behave_like 'the typeloader' do + let(:parser_class) { Puppet::Parser::Parser} + let(:other_parser_class) { Puppet::Parser::EParserAdapter} + end + end + describe 'when using the future parser' do + before :each do + Puppet[:parser] = 'future' + end + it_should_behave_like 'the typeloader' do + let(:parser_class) { Puppet::Parser::EParserAdapter} + let(:other_parser_class) { Puppet::Parser::Parser} + end + end end diff --git a/spec/unit/pops/adaptable_spec.rb b/spec/unit/pops/adaptable_spec.rb new file mode 100644 index 000000000..e9b5b80d0 --- /dev/null +++ b/spec/unit/pops/adaptable_spec.rb @@ -0,0 +1,143 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +describe Puppet::Pops::Adaptable::Adapter do + class ValueAdapter < Puppet::Pops::Adaptable::Adapter + attr_accessor :value + end + + class OtherAdapter < Puppet::Pops::Adaptable::Adapter + attr_accessor :value + def OtherAdapter.create_adapter(o) + x = new + x.value="I am calling you Daffy." + x + end + end + + module Farm + class FarmAdapter < Puppet::Pops::Adaptable::Adapter + attr_accessor :value + end + end + + class Duck + include Puppet::Pops::Adaptable + end + + it "should create specialized adapter instance on call to adapt" do + d = Duck.new + a = ValueAdapter.adapt(d) + a.class.should == ValueAdapter + end + + it "should produce the same instance on multiple adaptations" do + d = Duck.new + a = ValueAdapter.adapt(d) + a.value = 10 + b = ValueAdapter.adapt(d) + b.value.should == 10 + end + + it "should return the correct adapter if there are several" do + d = Duck.new + other = OtherAdapter.adapt(d) + a = ValueAdapter.adapt(d) + a.value = 10 + b = ValueAdapter.adapt(d) + b.value.should == 10 + end + + it "should allow specialization to override creating" do + d = Duck.new + a = OtherAdapter.adapt(d) + a.value.should == "I am calling you Daffy." + end + + it "should create a new adapter overriding existing" do + d = Duck.new + a = OtherAdapter.adapt(d) + a.value.should == "I am calling you Daffy." + a.value = "Something different" + a.value.should == "Something different" + b = OtherAdapter.adapt(d) + b.value.should == "Something different" + b = OtherAdapter.adapt_new(d) + b.value.should == "I am calling you Daffy." + end + + it "should not create adapter on get" do + d = Duck.new + a = OtherAdapter.get(d) + a.should == nil + end + + it "should return same adapter from get after adapt" do + d = Duck.new + a = OtherAdapter.get(d) + a.should == nil + a = OtherAdapter.adapt(d) + a.value.should == "I am calling you Daffy." + b = OtherAdapter.get(d) + b.value.should == "I am calling you Daffy." + a.should == b + end + + it "should handle adapters in nested namespaces" do + d = Duck.new + a = Farm::FarmAdapter.get(d) + a.should == nil + a = Farm::FarmAdapter.adapt(d) + a.value = 10 + b = Farm::FarmAdapter.get(d) + b.value.should == 10 + # Test implementation detail + d.instance_variables.include?(:@Farm_FarmAdapter).should == true + end + + it "should be able to clear the adapter" do + d = Duck.new + a = OtherAdapter.adapt(d) + a.value.should == "I am calling you Daffy." + # The adapter cleared should be returned + OtherAdapter.clear(d).value.should == "I am calling you Daffy." + OtherAdapter.get(d).should == nil + end + + context "When adapting with #adapt it" do + it "should be possible to pass a block to configure the adapter" do + d = Duck.new + a = OtherAdapter.adapt(d) do |x| + x.value = "Donald" + end + a.value.should == "Donald" + end + + it "should be possible to pass a block to configure the adapter and get the adapted" do + d = Duck.new + a = OtherAdapter.adapt(d) do |x, o| + x.value = "Donald, the #{o.class.name}" + end + a.value.should == "Donald, the Duck" + end + end + + context "When adapting with #adapt_new it" do + it "should be possible to pass a block to configure the adapter" do + d = Duck.new + a = OtherAdapter.adapt_new(d) do |x| + x.value = "Donald" + end + a.value.should == "Donald" + end + + it "should be possible to pass a block to configure the adapter and get the adapted" do + d = Duck.new + a = OtherAdapter.adapt_new(d) do |x, o| + x.value = "Donald, the #{o.class.name}" + end + a.value.should == "Donald, the Duck" + end + end +end diff --git a/spec/unit/pops/containment_spec.rb b/spec/unit/pops/containment_spec.rb new file mode 100644 index 000000000..da5fae77a --- /dev/null +++ b/spec/unit/pops/containment_spec.rb @@ -0,0 +1,25 @@ +require 'puppet/pops' +require File.join(File.dirname(__FILE__), 'factory_rspec_helper') + +describe Puppet::Pops::Containment do + include FactoryRspecHelper + + it "Should return an Enumerable if eAllContents is called without arguments" do + (literal(1) + literal(2)).current.eAllContents.is_a?(Enumerable).should == true + end + + it "Should return all content" do + # Note the top object is not included (an ArithmeticOperation with + operator) + (literal(1) + literal(2) + literal(3)).current.eAllContents.collect {|x| x}.size.should == 4 + end + + it "Should return containing feature" do + left = literal(1) + right = literal(2) + op = left + right + + #pending "eContainingFeature does not work on _uni containments in RGen < 0.6.1" + left.current.eContainingFeature.should == :left_expr + right.current.eContainingFeature.should == :right_expr + end +end diff --git a/spec/unit/pops/factory_rspec_helper.rb b/spec/unit/pops/factory_rspec_helper.rb new file mode 100644 index 000000000..b11f6ee87 --- /dev/null +++ b/spec/unit/pops/factory_rspec_helper.rb @@ -0,0 +1,77 @@ +require 'puppet/pops' + +module FactoryRspecHelper + def literal(x) + Puppet::Pops::Model::Factory.literal(x) + end + + def block(*args) + Puppet::Pops::Model::Factory.block(*args) + end + + def var(x) + Puppet::Pops::Model::Factory.var(x) + end + + def fqn(x) + Puppet::Pops::Model::Factory.fqn(x) + end + + def string(*args) + Puppet::Pops::Model::Factory.string(*args) + end + + def text(x) + Puppet::Pops::Model::Factory.text(x) + end + + def minus(x) + Puppet::Pops::Model::Factory.minus(x) + end + + def IF(test, then_expr, else_expr=nil) + Puppet::Pops::Model::Factory.IF(test, then_expr, else_expr) + end + + def UNLESS(test, then_expr, else_expr=nil) + Puppet::Pops::Model::Factory.UNLESS(test, then_expr, else_expr) + end + + def CASE(test, *options) + Puppet::Pops::Model::Factory.CASE(test, *options) + end + + def WHEN(values, block) + Puppet::Pops::Model::Factory.WHEN(values, block) + end + + def respond_to? method + if Puppet::Pops::Model::Factory.respond_to? method + true + else + super + end + end + + def method_missing(method, *args, &block) + if Puppet::Pops::Model::Factory.respond_to? method + Puppet::Pops::Model::Factory.send(method, *args, &block) + else + super + end + end + + # i.e. Selector Entry 1 => 'hello' + def MAP(match, value) + Puppet::Pops::Model::Factory.MAP(match, value) + end + + def dump(x) + Puppet::Pops::Model::ModelTreeDumper.new.dump(x) + end + + def unindent x + (x.gsub /^#{x[/\A\s*/]}/, '').chomp + end + factory ||= Puppet::Pops::Model::Factory +end diff --git a/spec/unit/pops/factory_spec.rb b/spec/unit/pops/factory_spec.rb new file mode 100644 index 000000000..3cefbc78f --- /dev/null +++ b/spec/unit/pops/factory_spec.rb @@ -0,0 +1,329 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' +require File.join(File.dirname(__FILE__), '/factory_rspec_helper') + +# This file contains testing of the pops model factory +# + +describe Puppet::Pops::Model::Factory do + include FactoryRspecHelper + + context "When factory methods are invoked they should produce expected results" do + it "tests #var should create a VariableExpression" do + var('a').current.class.should == Puppet::Pops::Model::VariableExpression + end + + it "tests #fqn should create a QualifiedName" do + fqn('a').current.class.should == Puppet::Pops::Model::QualifiedName + end + + it "tests #QNAME should create a QualifiedName" do + QNAME('a').current.class.should == Puppet::Pops::Model::QualifiedName + end + + it "tests #QREF should create a QualifiedReference" do + QREF('a').current.class.should == Puppet::Pops::Model::QualifiedReference + end + + it "tests #block should create a BlockExpression" do + block().current.is_a?(Puppet::Pops::Model::BlockExpression).should == true + end + + it "should create a literal undef on :undef" do + literal(:undef).current.class.should == Puppet::Pops::Model::LiteralUndef + end + + it "should create a literal default on :default" do + literal(:default).current.class.should == Puppet::Pops::Model::LiteralDefault + end + end + + context "When calling block_or_expression" do + it "A single expression should produce identical output" do + block_or_expression(literal(1) + literal(2)).current.is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true + end + + it "Multiple expressions should produce a block expression" do + built = block_or_expression(literal(1) + literal(2), literal(2) + literal(3)).current + built.is_a?(Puppet::Pops::Model::BlockExpression).should == true + built.statements.size.should == 2 + end + end + + context "When processing calls with CALL_NAMED" do + it "Should be possible to state that r-value is required" do + built = CALL_NAMED("foo", true, []).current + built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true + built.rval_required.should == true + end + + it "Should produce a call expression without arguments" do + built = CALL_NAMED("foo", false, []).current + built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true + built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName).should == true + built.functor_expr.value.should == "foo" + built.rval_required.should == false + built.arguments.size.should == 0 + end + + it "Should produce a call expression with one argument" do + built = CALL_NAMED("foo", false, [literal(1) + literal(2)]).current + built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true + built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName).should == true + built.functor_expr.value.should == "foo" + built.rval_required.should == false + built.arguments.size.should == 1 + built.arguments[0].is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true + end + + it "Should produce a call expression with two arguments" do + built = CALL_NAMED("foo", false, [literal(1) + literal(2), literal(1) + literal(2)]).current + built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true + built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName).should == true + built.functor_expr.value.should == "foo" + built.rval_required.should == false + built.arguments.size.should == 2 + built.arguments[0].is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true + built.arguments[1].is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true + end + end + + context "When creating attribute operations" do + it "Should produce an attribute operation for =>" do + built = ATTRIBUTE_OP("aname", :'=>', 'x').current + built.is_a?(Puppet::Pops::Model::AttributeOperation) + built.operator.should == :'=>' + built.attribute_name.should == "aname" + built.value_expr.is_a?(Puppet::Pops::Model::LiteralString).should == true + end + + it "Should produce an attribute operation for +>" do + built = ATTRIBUTE_OP("aname", :'+>', 'x').current + built.is_a?(Puppet::Pops::Model::AttributeOperation) + built.operator.should == :'+>' + built.attribute_name.should == "aname" + built.value_expr.is_a?(Puppet::Pops::Model::LiteralString).should == true + end + end + + context "When processing RESOURCE" do + it "Should create a Resource body" do + built = RESOURCE_BODY("title", [ATTRIBUTE_OP('aname', :'=>', 'x')]).current + built.is_a?(Puppet::Pops::Model::ResourceBody).should == true + built.title.is_a?(Puppet::Pops::Model::LiteralString).should == true + built.operations.size.should == 1 + built.operations[0].class.should == Puppet::Pops::Model::AttributeOperation + built.operations[0].attribute_name.should == 'aname' + end + + it "Should create a RESOURCE without a resource body" do + bodies = [] + built = RESOURCE("rtype", bodies).current + built.class.should == Puppet::Pops::Model::ResourceExpression + built.bodies.size.should == 0 + end + + it "Should create a RESOURCE with 1 resource body" do + bodies = [] << RESOURCE_BODY('title', []) + built = RESOURCE("rtype", bodies).current + built.class.should == Puppet::Pops::Model::ResourceExpression + built.bodies.size.should == 1 + built.bodies[0].title.value.should == 'title' + end + + it "Should create a RESOURCE with 2 resource bodies" do + bodies = [] << RESOURCE_BODY('title', []) << RESOURCE_BODY('title2', []) + built = RESOURCE("rtype", bodies).current + built.class.should == Puppet::Pops::Model::ResourceExpression + built.bodies.size.should == 2 + built.bodies[0].title.value.should == 'title' + built.bodies[1].title.value.should == 'title2' + end + end + + context "When processing simple literals" do + it "Should produce a literal boolean from a boolean" do + built = literal(true).current + built.class.should == Puppet::Pops::Model::LiteralBoolean + built.value.should == true + built = literal(false).current + built.class.should == Puppet::Pops::Model::LiteralBoolean + built.value.should == false + end + end + + context "When processing COLLECT" do + it "should produce a virtual query" do + built = VIRTUAL_QUERY(fqn('a') == literal(1)).current + built.class.should == Puppet::Pops::Model::VirtualQuery + built.expr.class.should == Puppet::Pops::Model::ComparisonExpression + built.expr.operator.should == :'==' + end + + it "should produce an export query" do + built = EXPORTED_QUERY(fqn('a') == literal(1)).current + built.class.should == Puppet::Pops::Model::ExportedQuery + built.expr.class.should == Puppet::Pops::Model::ComparisonExpression + built.expr.operator.should == :'==' + end + + it "should produce a collect expression" do + q = VIRTUAL_QUERY(fqn('a') == literal(1)) + built = COLLECT(literal('t'), q, [ATTRIBUTE_OP('name', :'=>', 3)]).current + built.class.should == Puppet::Pops::Model::CollectExpression + built.operations.size.should == 1 + end + + it "should produce a collect expression without attribute operations" do + q = VIRTUAL_QUERY(fqn('a') == literal(1)) + built = COLLECT(literal('t'), q, []).current + built.class.should == Puppet::Pops::Model::CollectExpression + built.operations.size.should == 0 + end + end + + context "When processing concatenated string(iterpolation)" do + it "should handle 'just a string'" do + built = string('blah blah').current + built.class.should == Puppet::Pops::Model::ConcatenatedString + built.segments.size == 1 + built.segments[0].class.should == Puppet::Pops::Model::LiteralString + built.segments[0].value.should == "blah blah" + end + + it "should handle one expression in the middle" do + built = string('blah blah', TEXT(literal(1)+literal(2)), 'blah blah').current + built.class.should == Puppet::Pops::Model::ConcatenatedString + built.segments.size == 3 + built.segments[0].class.should == Puppet::Pops::Model::LiteralString + built.segments[0].value.should == "blah blah" + built.segments[1].class.should == Puppet::Pops::Model::TextExpression + built.segments[1].expr.class.should == Puppet::Pops::Model::ArithmeticExpression + built.segments[2].class.should == Puppet::Pops::Model::LiteralString + built.segments[2].value.should == "blah blah" + end + + it "should handle one expression at the end" do + built = string('blah blah', TEXT(literal(1)+literal(2))).current + built.class.should == Puppet::Pops::Model::ConcatenatedString + built.segments.size == 2 + built.segments[0].class.should == Puppet::Pops::Model::LiteralString + built.segments[0].value.should == "blah blah" + built.segments[1].class.should == Puppet::Pops::Model::TextExpression + built.segments[1].expr.class.should == Puppet::Pops::Model::ArithmeticExpression + end + + it "should handle only one expression" do + built = string(TEXT(literal(1)+literal(2))).current + built.class.should == Puppet::Pops::Model::ConcatenatedString + built.segments.size == 1 + built.segments[0].class.should == Puppet::Pops::Model::TextExpression + built.segments[0].expr.class.should == Puppet::Pops::Model::ArithmeticExpression + end + + it "should handle several expressions" do + built = string(TEXT(literal(1)+literal(2)), TEXT(literal(1)+literal(2))).current + built.class.should == Puppet::Pops::Model::ConcatenatedString + built.segments.size == 2 + built.segments[0].class.should == Puppet::Pops::Model::TextExpression + built.segments[0].expr.class.should == Puppet::Pops::Model::ArithmeticExpression + built.segments[1].class.should == Puppet::Pops::Model::TextExpression + built.segments[1].expr.class.should == Puppet::Pops::Model::ArithmeticExpression + end + + it "should handle no expression" do + built = string().current + built.class.should == Puppet::Pops::Model::ConcatenatedString + built.segments.size == 0 + end + end + + context "When processing instance / resource references" do + it "should produce an InstanceReference without a reference" do + built = INSTANCE(QREF('a'), []).current + built.class.should == Puppet::Pops::Model::InstanceReferences + built.names.size.should == 0 + end + + it "should produce an InstanceReference with one reference" do + built = INSTANCE(QREF('a'), [QNAME('b')]).current + built.class.should == Puppet::Pops::Model::InstanceReferences + built.names.size.should == 1 + built.names[0].value.should == 'b' + end + + it "should produce an InstanceReference with two references" do + built = INSTANCE(QREF('a'), [QNAME('b'), QNAME('c')]).current + built.class.should == Puppet::Pops::Model::InstanceReferences + built.names.size.should == 2 + built.names[0].value.should == 'b' + built.names[1].value.should == 'c' + end + end + + context "When processing UNLESS" do + it "should create an UNLESS expression with then part" do + built = UNLESS(true, literal(1), nil).current + built.class.should == Puppet::Pops::Model::UnlessExpression + built.test.class.should == Puppet::Pops::Model::LiteralBoolean + built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber + built.else_expr.class.should == Puppet::Pops::Model::Nop + end + + it "should create an UNLESS expression with then and else parts" do + built = UNLESS(true, literal(1), literal(2)).current + built.class.should == Puppet::Pops::Model::UnlessExpression + built.test.class.should == Puppet::Pops::Model::LiteralBoolean + built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber + built.else_expr.class.should == Puppet::Pops::Model::LiteralNumber + end + end + + context "When processing IF" do + it "should create an IF expression with then part" do + built = IF(true, literal(1), nil).current + built.class.should == Puppet::Pops::Model::IfExpression + built.test.class.should == Puppet::Pops::Model::LiteralBoolean + built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber + built.else_expr.class.should == Puppet::Pops::Model::Nop + end + + it "should create an IF expression with then and else parts" do + built = IF(true, literal(1), literal(2)).current + built.class.should == Puppet::Pops::Model::IfExpression + built.test.class.should == Puppet::Pops::Model::LiteralBoolean + built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber + built.else_expr.class.should == Puppet::Pops::Model::LiteralNumber + end + end + + context "When processing a Parameter" do + it "should create a Parameter" do + # PARAM(name, expr) + # PARAM(name) + # + end + end + + # LIST, HASH, KEY_ENTRY + context "When processing Definition" do + # DEFINITION(classname, arguments, statements) + # should accept empty arguments, and no statements + end + + context "When processing Hostclass" do + # HOSTCLASS(classname, arguments, parent, statements) + # parent may be passed as a nop /nil - check this works, should accept empty statements (nil) + # should accept empty arguments + + end + + context "When processing Node" do + end + + # Tested in the evaluator test already, but should be here to test factory assumptions + # + # TODO: CASE / WHEN + # TODO: MAP +end diff --git a/spec/unit/pops/issues_spec.rb b/spec/unit/pops/issues_spec.rb new file mode 100644 index 000000000..091bd9c93 --- /dev/null +++ b/spec/unit/pops/issues_spec.rb @@ -0,0 +1,26 @@ +#! /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 Ruby String may not have a name contain 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 diff --git a/spec/unit/pops/label_provider_spec.rb b/spec/unit/pops/label_provider_spec.rb new file mode 100644 index 000000000..3e822669a --- /dev/null +++ b/spec/unit/pops/label_provider_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' +require 'puppet/pops' + +describe Puppet::Pops::LabelProvider do + let(:labeler) { Puppet::Pops::LabelProvider.new } + + it "prefixes words that start with a vowel with an 'an'" do + labeler.a_an('owl').should == 'an owl' + end + + it "prefixes words that start with a consonant with an 'a'" do + labeler.a_an('bear').should == 'a bear' + end + + it "prefixes non-word characters with an 'a'" do + labeler.a_an('[] expression').should == 'a [] expression' + end + + it "ignores a single quote leading the word" do + labeler.a_an("'owl'").should == "an 'owl'" + end + + it "ignores a double quote leading the word" do + labeler.a_an('"owl"').should == 'an "owl"' + end + + it "capitalizes the indefinite article for a word when requested" do + labeler.a_an_uc('owl').should == 'An owl' + end + + it "raises an error when missing a character to work with" do + expect { + labeler.a_an('"') + }.to raise_error(Puppet::DevError, /<"> does not appear to contain a word/) + end + + it "raises an error when given an empty string" do + expect { + labeler.a_an('') + }.to raise_error(Puppet::DevError, /<> does not appear to contain a word/) + end +end diff --git a/spec/unit/pops/model/ast_transformer_spec.rb b/spec/unit/pops/model/ast_transformer_spec.rb new file mode 100644 index 000000000..f2b1c9b56 --- /dev/null +++ b/spec/unit/pops/model/ast_transformer_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') +require 'puppet/pops' + +describe Puppet::Pops::Model::AstTransformer do + include FactoryRspecHelper + + let(:filename) { "the-file.pp" } + let(:transformer) { Puppet::Pops::Model::AstTransformer.new(filename) } + + context "literal numbers" do + it "converts a decimal number to a string Name" do + ast = transform(QNAME_OR_NUMBER("10")) + + ast.should be_kind_of Puppet::Parser::AST::Name + ast.value.should == "10" + end + + it "converts an octal number to a string Name" do + ast = transform(QNAME_OR_NUMBER("020")) + + ast.should be_kind_of Puppet::Parser::AST::Name + ast.value.should == "020" + end + + it "converts a hex number to a string Name" do + ast = transform(QNAME_OR_NUMBER("0x20")) + + ast.should be_kind_of Puppet::Parser::AST::Name + ast.value.should == "0x20" + end + + it "converts an unknown radix to an error string" do + ast = transform(Puppet::Pops::Model::Factory.new(Puppet::Pops::Model::LiteralNumber, 3, 2)) + + ast.should be_kind_of Puppet::Parser::AST::Name + ast.value.should == "bad radix:3" + end + end + + it "preserves the file location" do + model = literal(1) + model.record_position(location(3, 1, 10), location(3, 2, 11)) + + ast = transform(model) + + ast.file.should == filename + ast.line.should == 3 + ast.pos.should == 1 + end + + def transform(model) + transformer.transform(model) + end + + def location(line, column, offset) + position = Puppet::Pops::Adapters::SourcePosAdapter.new + position.line = line + position.pos = column + position.offset = offset + + position + end +end diff --git a/spec/unit/pops/model/model_spec.rb b/spec/unit/pops/model/model_spec.rb new file mode 100644 index 000000000..5014a64fa --- /dev/null +++ b/spec/unit/pops/model/model_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +describe Puppet::Pops::Model do + it "should be possible to create an instance of a model object" do + nop = Puppet::Pops::Model::Nop.new + nop.class.should == Puppet::Pops::Model::Nop + end +end + +describe Puppet::Pops::Model::Factory do + Factory = Puppet::Pops::Model::Factory + Model = Puppet::Pops::Model + + it "construct an arithmetic expression" do + x = Factory.literal(10) + Factory.literal(20) + x.is_a?(Factory).should == true + current = x.current + current.is_a?(Model::ArithmeticExpression).should == true + current.operator.should == :'+' + current.left_expr.class.should == Model::LiteralNumber + current.right_expr.class.should == Model::LiteralNumber + current.left_expr.value.should == 10 + current.right_expr.value.should == 20 + end + + it "should be easy to compare using a model tree dumper" do + x = Factory.literal(10) + Factory.literal(20) + Puppet::Pops::Model::ModelTreeDumper.new.dump(x.current).should == "(+ 10 20)" + end + + it "builder should apply precedence" do + x = Factory.literal(2) * Factory.literal(10) + Factory.literal(20) + Puppet::Pops::Model::ModelTreeDumper.new.dump(x.current).should == "(+ (* 2 10) 20)" + end +end diff --git a/spec/unit/pops/parser/lexer_spec.rb b/spec/unit/pops/parser/lexer_spec.rb new file mode 100755 index 000000000..d5232e9f1 --- /dev/null +++ b/spec/unit/pops/parser/lexer_spec.rb @@ -0,0 +1,884 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'puppet/pops' + +# This is a special matcher to match easily lexer output +RSpec::Matchers.define :be_like do |*expected| + match do |actual| + diffable + expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) } + end +end +__ = nil + +module EgrammarLexerSpec + def self.tokens_scanned_from(s) + lexer = Puppet::Pops::Parser::Lexer.new + lexer.string = s + lexer.fullscan[0..-2] + end +end + +describe Puppet::Pops::Parser::Lexer do + include EgrammarLexerSpec + + describe "when reading strings" do + before { @lexer = Puppet::Pops::Parser::Lexer.new } + + it "should increment the line count for every carriage return in the string" do + @lexer.string = "'this\nis\natest'" + @lexer.fullscan[0..-2] + + line = @lexer.line + line.should == 3 + end + + it "should not increment the line count for escapes in the string" do + @lexer.string = "'this\\nis\\natest'" + @lexer.fullscan[0..-2] + + @lexer.line.should == 1 + end + + it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do + @lexer.string = "'here\nis\nthe\nstring\\\\'with\nextra\njunk" + @lexer.fullscan[0..-2] + + @lexer.line.should == 6 + end + + { + 'r' => "\r", + 'n' => "\n", + 't' => "\t", + 's' => " " + }.each do |esc, expected_result| + it "should recognize \\#{esc} sequence" do + @lexer.string = "\\#{esc}'" + @lexer.slurpstring("'")[0].should == expected_result + end + end + end +end + +describe Puppet::Pops::Parser::Lexer::Token, "when initializing" do + it "should create a regex if the first argument is a string" do + Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something} + end + + it "should set the string if the first argument is one" do + Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).string.should == "something" + end + + it "should set the regex if the first argument is one" do + Puppet::Pops::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something} + end +end + +describe Puppet::Pops::Parser::Lexer::TokenList do + before do + @list = Puppet::Pops::Parser::Lexer::TokenList.new + end + + it "should have a method for retrieving tokens by the name" do + token = @list.add_token :name, "whatever" + @list[:name].should equal(token) + end + + it "should have a method for retrieving string tokens by the string" do + token = @list.add_token :name, "whatever" + @list.lookup("whatever").should equal(token) + end + + it "should add tokens to the list when directed" do + token = @list.add_token :name, "whatever" + @list[:name].should equal(token) + end + + it "should have a method for adding multiple tokens at once" do + @list.add_tokens "whatever" => :name, "foo" => :bar + @list[:name].should_not be_nil + @list[:bar].should_not be_nil + end + + it "should fail to add tokens sharing a name with an existing token" do + @list.add_token :name, "whatever" + expect { @list.add_token :name, "whatever" }.to raise_error(ArgumentError) + end + + it "should set provided options on tokens being added" do + token = @list.add_token :name, "whatever", :skip_text => true + token.skip_text.should == true + end + + it "should define any provided blocks as a :convert method" do + token = @list.add_token(:name, "whatever") do "foo" end + token.convert.should == "foo" + end + + it "should store all string tokens in the :string_tokens list" do + one = @list.add_token(:name, "1") + @list.string_tokens.should be_include(one) + end + + it "should store all regex tokens in the :regex_tokens list" do + one = @list.add_token(:name, %r{one}) + @list.regex_tokens.should be_include(one) + end + + it "should not store string tokens in the :regex_tokens list" do + one = @list.add_token(:name, "1") + @list.regex_tokens.should_not be_include(one) + end + + it "should not store regex tokens in the :string_tokens list" do + one = @list.add_token(:name, %r{one}) + @list.string_tokens.should_not be_include(one) + end + + it "should sort the string tokens inversely by length when asked" do + one = @list.add_token(:name, "1") + two = @list.add_token(:other, "12") + @list.sort_tokens + @list.string_tokens.should == [two, one] + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS do + before do + @lexer = Puppet::Pops::Parser::Lexer.new + end + + { + :LBRACK => '[', + :RBRACK => ']', +# :LBRACE => '{', + :RBRACE => '}', + :LPAREN => '(', + :RPAREN => ')', + :EQUALS => '=', + :ISEQUAL => '==', + :GREATEREQUAL => '>=', + :GREATERTHAN => '>', + :LESSTHAN => '<', + :LESSEQUAL => '<=', + :NOTEQUAL => '!=', + :NOT => '!', + :COMMA => ',', + :DOT => '.', + :COLON => ':', + :AT => '@', + :LLCOLLECT => '<<|', + :RRCOLLECT => '|>>', + :LCOLLECT => '<|', + :RCOLLECT => '|>', + :SEMIC => ';', + :QMARK => '?', + :BACKSLASH => '\\', + :FARROW => '=>', + :PARROW => '+>', + :APPENDS => '+=', + :PLUS => '+', + :MINUS => '-', + :DIV => '/', + :TIMES => '*', + :LSHIFT => '<<', + :RSHIFT => '>>', + :MATCH => '=~', + :NOMATCH => '!~', + :IN_EDGE => '->', + :OUT_EDGE => '<-', + :IN_EDGE_SUB => '~>', + :OUT_EDGE_SUB => '<~', + :PIPE => '|', + }.each do |name, string| + it "should have a token named #{name.to_s}" do + Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil + end + + it "should match '#{string}' for the token #{name.to_s}" do + Puppet::Pops::Parser::Lexer::TOKENS[name].string.should == string + end + end + + { + "case" => :CASE, + "class" => :CLASS, + "default" => :DEFAULT, + "define" => :DEFINE, +# "import" => :IMPORT, # done as a function in egrammar + "if" => :IF, + "elsif" => :ELSIF, + "else" => :ELSE, + "inherits" => :INHERITS, + "node" => :NODE, + "and" => :AND, + "or" => :OR, + "undef" => :UNDEF, + "false" => :FALSE, + "true" => :TRUE, + "in" => :IN, + "unless" => :UNLESS, + }.each do |string, name| + it "should have a keyword named #{name.to_s}" do + Puppet::Pops::Parser::Lexer::KEYWORDS[name].should_not be_nil + end + + it "should have the keyword for #{name.to_s} set to #{string}" do + Puppet::Pops::Parser::Lexer::KEYWORDS[name].string.should == string + end + end + + # These tokens' strings don't matter, just that the tokens exist. + [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, + :LBRACE, :LAMBDA, + :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name| + it "should have a token named #{name.to_s}" do + Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil + end + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] do + before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] } + + it "should match against single upper-case alpha-numeric terms" do + @token.regex.should =~ "One" + end + + it "should match against upper-case alpha-numeric terms separated by double colons" do + @token.regex.should =~ "One::Two" + end + + it "should match against many upper-case alpha-numeric terms separated by double colons" do + @token.regex.should =~ "One::Two::Three::Four::Five" + end + + it "should match against upper-case alpha-numeric terms prefixed by double colons" do + @token.regex.should =~ "::One" + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:NAME] do + before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:NAME] } + + it "should match against lower-case alpha-numeric terms" do + @token.regex.should =~ "one-two" + end + + it "should return itself and the value if the matched term is not a keyword" do + Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil) + @token.convert(stub("lexer"), "myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:NAME], "myval"] + end + + it "should return the keyword token and the value if the matched term is a keyword" do + keyword = stub 'keyword', :name => :testing + Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) + @token.convert(stub("lexer"), "myval").should == [keyword, "myval"] + end + + it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do + keyword = stub 'keyword', :name => :TRUE + Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) + @token.convert(stub('lexer'), "true").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], true] + end + + it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do + keyword = stub 'keyword', :name => :FALSE + Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) + @token.convert(stub('lexer'), "false").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], false] + end + + it "should match against lower-case alpha-numeric terms separated by double colons" do + @token.regex.should =~ "one::two" + end + + it "should match against many lower-case alpha-numeric terms separated by double colons" do + @token.regex.should =~ "one::two::three::four::five" + end + + it "should match against lower-case alpha-numeric terms prefixed by double colons" do + @token.regex.should =~ "::one" + end + + it "should match against nested terms starting with numbers" do + @token.regex.should =~ "::1one::2two::3three" + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] do + before do + @token = Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] + @regex = @token.regex + end + + it "should match against numeric terms" do + @regex.should =~ "2982383139" + end + + it "should match against float terms" do + @regex.should =~ "29823.235" + end + + it "should match against hexadecimal terms" do + @regex.should =~ "0xBEEF0023" + end + + it "should match against float with exponent terms" do + @regex.should =~ "10e23" + end + + it "should match against float terms with negative exponents" do + @regex.should =~ "10e-23" + end + + it "should match against float terms with fractional parts and exponent" do + @regex.should =~ "1.234e23" + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] do + before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] } + + it "should match against lines starting with '#'" do + @token.regex.should =~ "# this is a comment" + end + + it "should be marked to get skipped" do + @token.skip?.should be_true + end + + it "'s block should return the comment without the #" do + @token.convert(@lexer,"# this is a comment")[1].should == "this is a comment" + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] do + before do + @token = Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] + @lexer = stub 'lexer', :line => 0 + end + + it "should match against lines enclosed with '/*' and '*/'" do + @token.regex.should =~ "/* this is a comment */" + end + + it "should match multiple lines enclosed with '/*' and '*/'" do + @token.regex.should =~ """/* + this is a comment + */""" + end + +# # TODO: REWRITE THIS TEST TO NOT BE BASED ON INTERNALS +# it "should increase the lexer current line number by the amount of lines spanned by the comment" do +# @lexer.expects(:line=).with(2) +# @token.convert(@lexer, "1\n2\n3") +# end + + it "should not greedily match comments" do + match = @token.regex.match("/* first */ word /* second */") + match[1].should == " first " + end + + it "'s block should return the comment without the comment marks" do + @lexer.stubs(:line=).with(0) + + @token.convert(@lexer,"/* this is a comment */")[1].should == "this is a comment" + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] do + before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] } + + it "should match against carriage returns" do + @token.regex.should =~ "\n" + end + + it "should be marked to initiate text skipping" do + @token.skip_text.should be_true + end +end + +shared_examples_for "handling `-` in standard variable names for egrammar" do |prefix| + # Watch out - a regex might match a *prefix* on these, not just the whole + # word, so make sure you don't have false positive or negative results based + # on that. + legal = %w{f foo f::b foo::b f::bar foo::bar 3 foo3 3foo} + illegal = %w{f- f-o -f f::-o f::o- f::o-o} + + ["", "::"].each do |global_scope| + legal.each do |name| + var = prefix + global_scope + name + it "should accept #{var.inspect} as a valid variable name" do + (subject.regex.match(var) || [])[0].should == var + end + end + + illegal.each do |name| + var = prefix + global_scope + name + it "when `variable_with_dash` is disabled it should NOT accept #{var.inspect} as a valid variable name" do + Puppet[:allow_variables_with_dashes] = false + (subject.regex.match(var) || [])[0].should_not == var + end + + it "when `variable_with_dash` is enabled it should NOT accept #{var.inspect} as a valid variable name" do + Puppet[:allow_variables_with_dashes] = true + (subject.regex.match(var) || [])[0].should_not == var + end + end + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do + its(:skip_text) { should be_false } + + it_should_behave_like "handling `-` in standard variable names for egrammar", '$' +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE] do + its(:skip_text) { should be_false } + + it_should_behave_like "handling `-` in standard variable names for egrammar", '' +end + +describe "the horrible deprecation / compatibility variables with dashes" do + ENamesWithDashes = %w{f- f-o -f f::-o f::o- f::o-o} + + { Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR_WITH_DASH] => '$', + Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE_WITH_DASH] => '' + }.each do |token, prefix| + describe token do + its(:skip_text) { should be_false } + + context "when compatibly is disabled" do + before :each do Puppet[:allow_variables_with_dashes] = false end + Puppet::Pops::Parser::Lexer::TOKENS.each do |name, value| + it "should be unacceptable after #{name}" do + token.acceptable?(:after => name).should be_false + end + end + + # Yes, this should still *match*, just not be acceptable. + ENamesWithDashes.each do |name| + ["", "::"].each do |global_scope| + var = prefix + global_scope + name + it "should match #{var.inspect}" do + subject.regex.match(var).to_a.should == [var] + end + end + end + end + + context "when compatibility is enabled" do + before :each do Puppet[:allow_variables_with_dashes] = true end + + it "should be acceptable after DQPRE" do + token.acceptable?(:after => :DQPRE).should be_true + end + + ENamesWithDashes.each do |name| + ["", "::"].each do |global_scope| + var = prefix + global_scope + name + it "should match #{var.inspect}" do + subject.regex.match(var).to_a.should == [var] + end + end + end + end + end + end + + context "deprecation warnings" do + before :each do Puppet[:allow_variables_with_dashes] = true end + + it "should match a top level variable" do + Puppet.expects(:deprecation_warning).once + + EgrammarLexerSpec.tokens_scanned_from('$foo-bar').should == [ + [:VARIABLE, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>8}] + ] + end + + it "does not warn about a variable without a dash" do + Puppet.expects(:deprecation_warning).never + + EgrammarLexerSpec.tokens_scanned_from('$c').should == [ + [:VARIABLE, {:value=>"c", :line=>1, :pos=>1, :offset=>0, :length=>2}] + ] + end + + it "does not warn about referencing a class name that contains a dash" do + Puppet.expects(:deprecation_warning).never + + EgrammarLexerSpec.tokens_scanned_from('foo-bar').should == [ + [:NAME, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>7}] + ] + end + + it "warns about reference to variable" do + Puppet.expects(:deprecation_warning).once + + EgrammarLexerSpec.tokens_scanned_from('$::foo-bar::baz-quux').should == [ + [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>1, :offset=>0, :length=>20}] + ] + end + + it "warns about reference to variable interpolated in a string" do + Puppet.expects(:deprecation_warning).once + + EgrammarLexerSpec.tokens_scanned_from('"$::foo-bar::baz-quux"').should == [ + [:DQPRE, {:value=>"", :line=>1, :pos=>1, :offset=>0, :length=>2}], # length since preamble includes start and terminator + [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>3, :offset=>2, :length=>19}], + [:DQPOST, {:value=>"", :line=>1, :pos=>22, :offset=>21, :length=>1}], + ] + end + + it "warns about reference to variable interpolated in a string as an expression" do + Puppet.expects(:deprecation_warning).once + + EgrammarLexerSpec.tokens_scanned_from('"${::foo-bar::baz-quux}"').should == [ + [:DQPRE, {:value=>"", :line=>1, :pos=>1, :offset=>0, :length=>3}], + [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>4, :offset=>3, :length=>19}], + [:DQPOST, {:value=>"", :line=>1, :pos=>23, :offset=>22, :length=>2}], + ] + end + end +end + + +describe Puppet::Pops::Parser::Lexer,"when lexing strings" do + { + %q{'single quoted string')} => [[:STRING,'single quoted string']], + %q{"double quoted string"} => [[:STRING,'double quoted string']], + %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']], + %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']], + %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']], + %q{'single quoted string with an escaped "\r\n"'} => [[:STRING,'single quoted string with an escaped "\r\n"']], + %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']], + %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']], + %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]], + %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]], + %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]], + %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']], + %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']], + %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']], + %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]], + %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]], + %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']], + %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]], + %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]], + %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]], + %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']], + %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]], + %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']], + %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]], + %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]], + %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]], + %q["$$$$"] => [[:STRING,"$$$$"]], + %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]], + %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]], + %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]], + %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]], + %q[""] => [[:STRING,""]], + %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]], + %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]], + %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]] + }.each { |src,expected_result| + it "should handle #{src} correctly" do + EgrammarLexerSpec.tokens_scanned_from(src).should be_like(*expected_result) + end + } +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do + before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] } + + it "should match against alpha words prefixed with '$'" do + @token.regex.should =~ '$this_var' + end + + it "should return the VARIABLE token and the variable name stripped of the '$'" do + @token.convert(stub("lexer"), "$myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE], "myval"] + end +end + +describe Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] do + before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] } + + it "should match against any expression enclosed in //" do + @token.regex.should =~ '/this is a regex/' + end + + it 'should not match if there is \n in the regex' do + @token.regex.should_not =~ "/this is \n a regex/" + end + + describe "when scanning" do + it "should not consider escaped slashes to be the end of a regex" do + EgrammarLexerSpec.tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}]) + end + + it "should not lex chained division as a regex" do + EgrammarLexerSpec.tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX ) + end + + it "should accept a regular expression after NODE" do + EgrammarLexerSpec.tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")]) + end + + it "should accept regular expressions in a CASE" do + s = %q{case $variable { + "something": {$othervar = 4096 / 2} + /regex/: {notice("this notably sucks")} + } + } + EgrammarLexerSpec.tokens_scanned_from(s).should be_like( + :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE + ) + end + end + + it "should return the REGEX token and a Regexp" do + @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Pops::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)] + end +end + +describe Puppet::Pops::Parser::Lexer, "when lexing comments" do + before { @lexer = Puppet::Pops::Parser::Lexer.new } + + it "should skip whitespace before lexing the next token after a non-token" do + EgrammarLexerSpec.tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"]) + end +end + +# FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now. +describe "Puppet::Pops::Parser::Lexer in the old tests" do + before { @lexer = Puppet::Pops::Parser::Lexer.new } + + it "should do simple lexing" do + { + %q{\\} => [[:BACKSLASH,"\\"]], + %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]], + %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]] + }.each { |source,expected| + EgrammarLexerSpec.tokens_scanned_from(source).should be_like(*expected) + } + end + + it "should fail usefully" do + expect { EgrammarLexerSpec.tokens_scanned_from('^') }.to raise_error(RuntimeError) + end + + it "should fail if the string is not set" do + expect { @lexer.fullscan }.to raise_error(Puppet::LexError) + end + + it "should correctly identify keywords" do + EgrammarLexerSpec.tokens_scanned_from("case").should be_like([:CASE, "case"]) + end + + it "should correctly parse class references" do + %w{Many Different Words A Word}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF,t])} + end + + # #774 + it "should correctly parse namespaced class refernces token" do + %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF, t]) } + end + + it "should correctly parse names" do + %w{this is a bunch of names}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) } + end + + it "should correctly parse names with numerals" do + %w{1name name1 11names names11}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) } + end + + it "should correctly parse empty strings" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = ""') }.to_not raise_error + end + + it "should correctly parse virtual resources" do + EgrammarLexerSpec.tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"]) + end + + it "should correctly deal with namespaces" do + @lexer.string = %{class myclass} + @lexer.fullscan + @lexer.namespace.should == "myclass" + + @lexer.namepop + @lexer.namespace.should == "" + + @lexer.string = "class base { class sub { class more" + @lexer.fullscan + @lexer.namespace.should == "base::sub::more" + + @lexer.namepop + @lexer.namespace.should == "base::sub" + end + + it "should not put class instantiation on the namespace" do + @lexer.string = "class base { class sub { class { mode" + @lexer.fullscan + @lexer.namespace.should == "base::sub" + end + + it "should correctly handle fully qualified names" do + @lexer.string = "class base { class sub::more {" + @lexer.fullscan + @lexer.namespace.should == "base::sub::more" + + @lexer.namepop + @lexer.namespace.should == "base" + end + + it "should correctly lex variables" do + ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string| + EgrammarLexerSpec.tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')]) + end + end + + it "should end variables at `-`" do + EgrammarLexerSpec.tokens_scanned_from('$hyphenated-variable'). + should be_like [:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable'] + end + + it "should not include whitespace in a variable" do + EgrammarLexerSpec.tokens_scanned_from("$foo bar").should_not be_like([:VARIABLE, "foo bar"]) + end + it "should not include excess colons in a variable" do + EgrammarLexerSpec.tokens_scanned_from("$foo::::bar").should_not be_like([:VARIABLE, "foo::::bar"]) + end +end + +describe "Puppet::Pops::Parser::Lexer in the old tests when lexing example files" do + my_fixtures('*.pp') do |file| + it "should correctly lex #{file}" do + lexer = Puppet::Pops::Parser::Lexer.new + lexer.file = file + expect { lexer.fullscan }.to_not raise_error + end + end +end + +describe "when trying to lex an non-existent file" do + include PuppetSpec::Files + + it "should return an empty list of tokens" do + lexer = Puppet::Pops::Parser::Lexer.new + lexer.file = nofile = tmpfile('lexer') + File.exists?(nofile).should == false + + lexer.fullscan.should == [[false,false]] + end +end + +describe "when string quotes are not closed" do + it "should report with message including an \" opening quote" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/after '"'/) + end + + it "should report with message including an \' opening quote" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = \'') }.to raise_error(/after "'"/) + end + + it "should report <eof> if immediately followed by eof" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/followed by '<eof>'/) + end + + it "should report max 5 chars following quote" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/followed by '12345...'/) + end + + it "should escape control chars" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = "12\n3456') }.to raise_error(/followed by '12\\n3...'/) + end + + it "should resport position of opening quote" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:8/) + expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:9/) + end +end + +describe "when lexing number, bad input should not go unpunished" do + it "should slap bad octal as such" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0778') }.to raise_error(/Not a valid octal/) + end + + it "should slap bad hex as such" do + expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xFG') }.to raise_error(/Not a valid hex/) + expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xfg') }.to raise_error(/Not a valid hex/) + end + # Note, bad decimals are probably impossible to enter, as they are not recognized as complete numbers, instead, + # the error will be something else, depending on what follows some initial digit. + # +end + +describe "when lexing interpolation detailed positioning should be correct" do + it "should correctly position a string without interpolation" do + EgrammarLexerSpec.tokens_scanned_from('"not interpolated"').should be_like( + [:STRING, {:value=>"not interpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}]) + end + + it "should correctly position a string with false start in interpolation" do + EgrammarLexerSpec.tokens_scanned_from('"not $$$ rpolated"').should be_like( + [:STRING, {:value=>"not $$$ rpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}]) + end + + it "should correctly position pre-mid-end interpolation " do + EgrammarLexerSpec.tokens_scanned_from('"pre $x mid $y end"').should be_like( + [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>6}], + [:VARIABLE, {:value=>"x", :line=>1, :offset=>6, :pos=>7, :length=>1}], + [:DQMID, {:value=>" mid ", :line=>1, :offset=>7, :pos=>8, :length=>6}], + [:VARIABLE, {:value=>"y", :line=>1, :offset=>13, :pos=>14, :length=>1}], + [:DQPOST, {:value=>" end", :line=>1, :offset=>14, :pos=>15, :length=>5}] + ) + end + + it "should correctly position pre-mid-end interpolation using ${} " do + EgrammarLexerSpec.tokens_scanned_from('"pre ${x} mid ${y} end"').should be_like( + [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], + [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}], + [:DQMID, {:value=>" mid ", :line=>1, :offset=>8, :pos=>9, :length=>8}], + [:VARIABLE, {:value=>"y", :line=>1, :offset=>16, :pos=>17, :length=>1}], + [:DQPOST, {:value=>" end", :line=>1, :offset=>17, :pos=>18, :length=>6}] + ) + end + + it "should correctly position pre-end interpolation using ${} with f call" do + EgrammarLexerSpec.tokens_scanned_from('"pre ${x()} end"').should be_like( + [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], + [:NAME, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}], + [:LPAREN, {:value=>"(", :line=>1, :offset=>8, :pos=>9, :length=>1}], + [:RPAREN, {:value=>")", :line=>1, :offset=>9, :pos=>10, :length=>1}], + [:DQPOST, {:value=>" end", :line=>1, :offset=>10, :pos=>11, :length=>6}] + ) + end + + it "should correctly position pre-end interpolation using ${} with $x" do + EgrammarLexerSpec.tokens_scanned_from('"pre ${$x} end"').should be_like( + [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], + [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>2}], + [:DQPOST, {:value=>" end", :line=>1, :offset=>9, :pos=>10, :length=>6}] + ) + end + + it "should correctly position pre-end interpolation across lines" do + EgrammarLexerSpec.tokens_scanned_from(%Q["pre ${\n$x} end"]).should be_like( + [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], + [:VARIABLE, {:value=>"x", :line=>2, :offset=>8, :pos=>1, :length=>2}], + [:DQPOST, {:value=>" end", :line=>2, :offset=>10, :pos=>3, :length=>6}] + ) + end + + it "should correctly position interpolation across lines when strings have embedded newlines" do + EgrammarLexerSpec.tokens_scanned_from(%Q["pre \n\n${$x}\n mid$y"]).should be_like( + [:DQPRE, {:value=>"pre \n\n", :line=>1, :offset=>0, :pos=>1, :length=>9}], + [:VARIABLE, {:value=>"x", :line=>3, :offset=>9, :pos=>3, :length=>2}], + [:DQMID, {:value=>"\n mid", :line=>3, :offset=>11, :pos=>5, :length=>7}], + [:VARIABLE, {:value=>"y", :line=>4, :offset=>18, :pos=>6, :length=>1}] + ) + end +end diff --git a/spec/unit/pops/parser/parse_basic_expressions_spec.rb b/spec/unit/pops/parser/parse_basic_expressions_spec.rb new file mode 100644 index 000000000..6560e62e7 --- /dev/null +++ b/spec/unit/pops/parser/parse_basic_expressions_spec.rb @@ -0,0 +1,248 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/parser_rspec_helper') + +describe "egrammar parsing basic expressions" do + include ParserRspecHelper + + context "When the parser parses arithmetic" do + context "with Integers" do + it "$a = 2 + 2" do; dump(parse("$a = 2 + 2")).should == "(= $a (+ 2 2))" ; end + it "$a = 7 - 3" do; dump(parse("$a = 7 - 3")).should == "(= $a (- 7 3))" ; end + it "$a = 6 * 3" do; dump(parse("$a = 6 * 3")).should == "(= $a (* 6 3))" ; end + it "$a = 6 / 3" do; dump(parse("$a = 6 / 3")).should == "(= $a (/ 6 3))" ; end + it "$a = 6 % 3" do; dump(parse("$a = 6 % 3")).should == "(= $a (% 6 3))" ; end + it "$a = -(6/3)" do; dump(parse("$a = -(6/3)")).should == "(= $a (- (/ 6 3)))" ; end + it "$a = -6/3" do; dump(parse("$a = -6/3")).should == "(= $a (/ (- 6) 3))" ; end + it "$a = 8 >> 1 " do; dump(parse("$a = 8 >> 1")).should == "(= $a (>> 8 1))" ; end + it "$a = 8 << 1 " do; dump(parse("$a = 8 << 1")).should == "(= $a (<< 8 1))" ; end + end + + context "with Floats" do + it "$a = 2.2 + 2.2" do; dump(parse("$a = 2.2 + 2.2")).should == "(= $a (+ 2.2 2.2))" ; end + it "$a = 7.7 - 3.3" do; dump(parse("$a = 7.7 - 3.3")).should == "(= $a (- 7.7 3.3))" ; end + it "$a = 6.1 * 3.1" do; dump(parse("$a = 6.1 - 3.1")).should == "(= $a (- 6.1 3.1))" ; end + it "$a = 6.6 / 3.3" do; dump(parse("$a = 6.6 / 3.3")).should == "(= $a (/ 6.6 3.3))" ; end + it "$a = -(6.0/3.0)" do; dump(parse("$a = -(6.0/3.0)")).should == "(= $a (- (/ 6.0 3.0)))" ; end + it "$a = -6.0/3.0" do; dump(parse("$a = -6.0/3.0")).should == "(= $a (/ (- 6.0) 3.0))" ; end + it "$a = 3.14 << 2" do; dump(parse("$a = 3.14 << 2")).should == "(= $a (<< 3.14 2))" ; end + it "$a = 3.14 >> 2" do; dump(parse("$a = 3.14 >> 2")).should == "(= $a (>> 3.14 2))" ; end + end + + context "with hex and octal Integer values" do + it "$a = 0xAB + 0xCD" do; dump(parse("$a = 0xAB + 0xCD")).should == "(= $a (+ 0xAB 0xCD))" ; end + it "$a = 0777 - 0333" do; dump(parse("$a = 0777 - 0333")).should == "(= $a (- 0777 0333))" ; end + end + + context "with strings requiring boxing to Numeric" do + # Test that numbers in string form does not turn into numbers + it "$a = '2' + '2'" do; dump(parse("$a = '2' + '2'")).should == "(= $a (+ '2' '2'))" ; end + it "$a = '2.2' + '0.2'" do; dump(parse("$a = '2.2' + '0.2'")).should == "(= $a (+ '2.2' '0.2'))" ; end + it "$a = '0xab' + '0xcd'" do; dump(parse("$a = '0xab' + '0xcd'")).should == "(= $a (+ '0xab' '0xcd'))" ; end + it "$a = '0777' + '0333'" do; dump(parse("$a = '0777' + '0333'")).should == "(= $a (+ '0777' '0333'))" ; end + end + + context "precedence should be correct" do + it "$a = 1 + 2 * 3" do; dump(parse("$a = 1 + 2 * 3")).should == "(= $a (+ 1 (* 2 3)))"; end + it "$a = 1 + 2 % 3" do; dump(parse("$a = 1 + 2 % 3")).should == "(= $a (+ 1 (% 2 3)))"; end + it "$a = 1 + 2 / 3" do; dump(parse("$a = 1 + 2 / 3")).should == "(= $a (+ 1 (/ 2 3)))"; end + it "$a = 1 + 2 << 3" do; dump(parse("$a = 1 + 2 << 3")).should == "(= $a (<< (+ 1 2) 3))"; end + it "$a = 1 + 2 >> 3" do; dump(parse("$a = 1 + 2 >> 3")).should == "(= $a (>> (+ 1 2) 3))"; end + end + + context "parentheses alter precedence" do + it "$a = (1 + 2) * 3" do; dump(parse("$a = (1 + 2) * 3")).should == "(= $a (* (+ 1 2) 3))"; end + it "$a = (1 + 2) / 3" do; dump(parse("$a = (1 + 2) / 3")).should == "(= $a (/ (+ 1 2) 3))"; end + end + end + + context "When the evaluator performs boolean operations" do + context "using operators AND OR NOT" do + it "$a = true and true" do; dump(parse("$a = true and true")).should == "(= $a (&& true true))"; end + it "$a = true or true" do; dump(parse("$a = true or true")).should == "(= $a (|| true true))" ; end + it "$a = !true" do; dump(parse("$a = !true")).should == "(= $a (! true))" ; end + end + + context "precedence should be correct" do + it "$a = false or true and true" do + dump(parse("$a = false or true and true")).should == "(= $a (|| false (&& true true)))" + end + + it "$a = (false or true) and true" do + dump(parse("$a = (false or true) and true")).should == "(= $a (&& (|| false true) true))" + end + + it "$a = !true or true and true" do + dump(parse("$a = !false or true and true")).should == "(= $a (|| (! false) (&& true true)))" + end + end + + # Possibly change to check of literal expressions + context "on values requiring boxing to Boolean" do + it "'x' == true" do + dump(parse("! 'x'")).should == "(! 'x')" + end + + it "'' == false" do + dump(parse("! ''")).should == "(! '')" + end + + it ":undef == false" do + dump(parse("! undef")).should == "(! :undef)" + end + end + end + + context "When parsing comparisons" do + context "of string values" do + it "$a = 'a' == 'a'" do; dump(parse("$a = 'a' == 'a'")).should == "(= $a (== 'a' 'a'))" ; end + it "$a = 'a' != 'a'" do; dump(parse("$a = 'a' != 'a'")).should == "(= $a (!= 'a' 'a'))" ; end + it "$a = 'a' < 'b'" do; dump(parse("$a = 'a' < 'b'")).should == "(= $a (< 'a' 'b'))" ; end + it "$a = 'a' > 'b'" do; dump(parse("$a = 'a' > 'b'")).should == "(= $a (> 'a' 'b'))" ; end + it "$a = 'a' <= 'b'" do; dump(parse("$a = 'a' <= 'b'")).should == "(= $a (<= 'a' 'b'))" ; end + it "$a = 'a' >= 'b'" do; dump(parse("$a = 'a' >= 'b'")).should == "(= $a (>= 'a' 'b'))" ; end + end + + context "of integer values" do + it "$a = 1 == 1" do; dump(parse("$a = 1 == 1")).should == "(= $a (== 1 1))" ; end + it "$a = 1 != 1" do; dump(parse("$a = 1 != 1")).should == "(= $a (!= 1 1))" ; end + it "$a = 1 < 2" do; dump(parse("$a = 1 < 2")).should == "(= $a (< 1 2))" ; end + it "$a = 1 > 2" do; dump(parse("$a = 1 > 2")).should == "(= $a (> 1 2))" ; end + it "$a = 1 <= 2" do; dump(parse("$a = 1 <= 2")).should == "(= $a (<= 1 2))" ; end + it "$a = 1 >= 2" do; dump(parse("$a = 1 >= 2")).should == "(= $a (>= 1 2))" ; end + end + + context "of regular expressions (parse errors)" do + # Not supported in concrete syntax + it "$a = /.*/ == /.*/" do + expect { parse("$a = /.*/ == /.*/") }.to raise_error(Puppet::ParseError) + end + + it "$a = /.*/ != /a.*/" do + expect { parse("$a = /.*/ != /.*/") }.to raise_error(Puppet::ParseError) + end + end + end + + context "When parsing Regular Expression matching" do + it "$a = 'a' =~ /.*/" do; dump(parse("$a = 'a' =~ /.*/")).should == "(= $a (=~ 'a' /.*/))" ; end + it "$a = 'a' =~ '.*'" do; dump(parse("$a = 'a' =~ '.*'")).should == "(= $a (=~ 'a' '.*'))" ; end + it "$a = 'a' !~ /b.*/" do; dump(parse("$a = 'a' !~ /b.*/")).should == "(= $a (!~ 'a' /b.*/))" ; end + it "$a = 'a' !~ 'b.*'" do; dump(parse("$a = 'a' !~ 'b.*'")).should == "(= $a (!~ 'a' 'b.*'))" ; end + end + + context "When parsing Lists" do + it "$a = []" do + dump(parse("$a = []")).should == "(= $a ([]))" + end + + it "$a = [1]" do + dump(parse("$a = [1]")).should == "(= $a ([] 1))" + end + + it "$a = [1,2,3]" do + dump(parse("$a = [1,2,3]")).should == "(= $a ([] 1 2 3))" + end + + it "[...[...[]]] should create nested arrays without trouble" do + dump(parse("$a = [1,[2.0, 2.1, [2.2]],[3.0, 3.1]]")).should == "(= $a ([] 1 ([] 2.0 2.1 ([] 2.2)) ([] 3.0 3.1)))" + end + + it "$a = [2 + 2]" do + dump(parse("$a = [2+2]")).should == "(= $a ([] (+ 2 2)))" + end + + it "$a [1,2,3] == [1,2,3]" do + dump(parse("$a = [1,2,3] == [1,2,3]")).should == "(= $a (== ([] 1 2 3) ([] 1 2 3)))" + end + end + + context "When parsing indexed access" do + it "$a = $b[2]" do + dump(parse("$a = $b[2]")).should == "(= $a (slice $b 2))" + end + + it "$a = [1, 2, 3][2]" do + dump(parse("$a = [1,2,3][2]")).should == "(= $a (slice ([] 1 2 3) 2))" + end + + it "$a = {'a' => 1, 'b' => 2}['b']" do + dump(parse("$a = {'a'=>1,'b' =>2}[b]")).should == "(= $a (slice ({} ('a' 1) ('b' 2)) b))" + end + end + + context "When parsing assignments" do + it "Should allow simple assignment" do + dump(parse("$a = 10")).should == "(= $a 10)" + end + + it "Should allow chained assignment" do + dump(parse("$a = $b = 10")).should == "(= $a (= $b 10))" + end + + it "Should allow chained assignment with expressions" do + dump(parse("$a = 1 + ($b = 10)")).should == "(= $a (+ 1 (= $b 10)))" + end + end + + context "When parsing Hashes" do + it "should create a Hash when evaluating a LiteralHash" do + dump(parse("$a = {'a'=>1,'b'=>2}")).should == "(= $a ({} ('a' 1) ('b' 2)))" + end + + it "$a = {...{...{}}} should create nested hashes without trouble" do + dump(parse("$a = {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}")).should == "(= $a ({} ('a' 1) ('b' ({} ('x' 2.1) ('y' 2.2)))))" + end + + it "$a = {'a'=> 2 + 2} should evaluate values in entries" do + dump(parse("$a = {'a'=>2+2}")).should == "(= $a ({} ('a' (+ 2 2))))" + end + + it "$a = {'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" do + dump(parse("$a = {'a'=>1,'b'=>2} == {'a'=>1,'b'=>2}")).should == "(= $a (== ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))" + end + + it "$a = {'a'=> 1, 'b'=>2} != {'x'=> 1, 'y'=>3}" do + dump(parse("$a = {'a'=>1,'b'=>2} != {'a'=>1,'b'=>2}")).should == "(= $a (!= ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))" + end + end + + context "When parsing the 'in' operator" do + it "with integer in a list" do + dump(parse("$a = 1 in [1,2,3]")).should == "(= $a (in 1 ([] 1 2 3)))" + end + + it "with string key in a hash" do + dump(parse("$a = 'a' in {'x'=>1, 'a'=>2, 'y'=> 3}")).should == "(= $a (in 'a' ({} ('x' 1) ('a' 2) ('y' 3))))" + end + + it "with substrings of a string" do + dump(parse("$a = 'ana' in 'bananas'")).should == "(= $a (in 'ana' 'bananas'))" + end + + it "with sublist in a list" do + dump(parse("$a = [2,3] in [1,2,3]")).should == "(= $a (in ([] 2 3) ([] 1 2 3)))" + end + end + + context "When parsing string interpolation" do + it "should interpolate a bare word as a variable name, \"${var}\"" do + dump(parse("$a = \"$var\"")).should == "(= $a (cat '' (str $var) ''))" + end + + it "should interpolate a variable in a text expression, \"${$var}\"" do + dump(parse("$a = \"${$var}\"")).should == "(= $a (cat '' (str $var) ''))" + end + + it "should interpolate a variable, \"yo${var}yo\"" do + dump(parse("$a = \"yo${var}yo\"")).should == "(= $a (cat 'yo' (str $var) 'yo'))" + end + + it "should interpolate any expression in a text expression, \"${var*2}\"" do + dump(parse("$a = \"yo${var+2}yo\"")).should == "(= $a (cat 'yo' (str (+ $var 2)) 'yo'))" + end + end +end diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb new file mode 100644 index 000000000..62012a430 --- /dev/null +++ b/spec/unit/pops/parser/parse_calls_spec.rb @@ -0,0 +1,93 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/parser_rspec_helper') + +describe "egrammar parsing function calls" do + include ParserRspecHelper + + context "When parsing calls as statements" do + context "in top level scope" do + it "foo()" do + dump(parse("foo()")).should == "(invoke foo)" + end + + it "foo bar" do + dump(parse("foo bar")).should == "(invoke foo bar)" + end + + it "foo(bar)" do + dump(parse("foo(bar)")).should == "(invoke foo bar)" + end + + it "foo(bar,)" do + dump(parse("foo(bar,)")).should == "(invoke foo bar)" + end + + it "foo(bar, fum,)" do + dump(parse("foo(bar,fum,)")).should == "(invoke foo bar fum)" + end + end + + context "in nested scopes" do + it "if true { foo() }" do + dump(parse("if true {foo()}")).should == "(if true\n (then (invoke foo)))" + end + + it "if true { foo bar}" do + dump(parse("if true {foo bar}")).should == "(if true\n (then (invoke foo bar)))" + end + end + end + + context "When parsing calls as expressions" do + it "$a = foo()" do + dump(parse("$a = foo()")).should == "(= $a (call foo))" + end + + it "$a = foo(bar)" do + dump(parse("$a = foo()")).should == "(= $a (call foo))" + end + + # # For regular grammar where a bare word can not be a "statement" + # it "$a = foo bar # illegal, must have parentheses" do + # expect { dump(parse("$a = foo bar"))}.to raise_error(Puppet::ParseError) + # end + + # For egrammar where a bare word can be a "statement" + it "$a = foo bar # illegal, must have parentheses" do + dump(parse("$a = foo bar")).should == "(block (= $a foo) bar)" + end + + context "in nested scopes" do + it "if true { $a = foo() }" do + dump(parse("if true { $a = foo()}")).should == "(if true\n (then (= $a (call foo))))" + end + + it "if true { $a= foo(bar)}" do + dump(parse("if true {$a = foo(bar)}")).should == "(if true\n (then (= $a (call foo bar))))" + end + end + end + + context "When parsing method calls" do + it "$a.foo" do + dump(parse("$a.foo")).should == "(call-method (. $a foo))" + end + + it "$a.foo {|| }" do + dump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))" + end + + it "$a.foo {|$x| }" do + dump(parse("$a.foo {|$x| }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" + end + + it "$a.foo {|$x| }" do + dump(parse("$a.foo {|$x| $b = $x}")).should == + "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))" + end + end +end diff --git a/spec/unit/pops/parser/parse_conditionals_spec.rb b/spec/unit/pops/parser/parse_conditionals_spec.rb new file mode 100644 index 000000000..b0cb36b96 --- /dev/null +++ b/spec/unit/pops/parser/parse_conditionals_spec.rb @@ -0,0 +1,159 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/parser_rspec_helper') + +describe "egrammar parsing conditionals" do + include ParserRspecHelper + + context "When parsing if statements" do + it "if true { $a = 10 }" do + dump(parse("if true { $a = 10 }")).should == "(if true\n (then (= $a 10)))" + end + + it "if true { $a = 10 } else {$a = 20}" do + dump(parse("if true { $a = 10 } else {$a = 20}")).should == + ["(if true", + " (then (= $a 10))", + " (else (= $a 20)))"].join("\n") + end + + it "if true { $a = 10 } elsif false { $a = 15} else {$a = 20}" do + dump(parse("if true { $a = 10 } elsif false { $a = 15} else {$a = 20}")).should == + ["(if true", + " (then (= $a 10))", + " (else (if false", + " (then (= $a 15))", + " (else (= $a 20)))))"].join("\n") + end + + it "if true { $a = 10 $b = 10 } else {$a = 20}" do + dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == + ["(if true", + " (then (block (= $a 10) (= $b 20)))", + " (else (= $a 20)))"].join("\n") + end + + it "allows a parenthesized conditional expression" do + dump(parse("if (true) { 10 }")).should == "(if true\n (then 10))" + end + + it "allows a parenthesized elsif conditional expression" do + dump(parse("if true { 10 } elsif (false) { 20 }")).should == + ["(if true", + " (then 10)", + " (else (if false", + " (then 20))))"].join("\n") + end + end + + context "When parsing unless statements" do + it "unless true { $a = 10 }" do + dump(parse("unless true { $a = 10 }")).should == "(unless true\n (then (= $a 10)))" + end + + it "unless true { $a = 10 } else {$a = 20}" do + dump(parse("unless true { $a = 10 } else {$a = 20}")).should == + ["(unless true", + " (then (= $a 10))", + " (else (= $a 20)))"].join("\n") + end + + it "allows a parenthesized conditional expression" do + dump(parse("unless (true) { 10 }")).should == "(unless true\n (then 10))" + end + + it "unless true { $a = 10 } elsif false { $a = 15} else {$a = 20} # is illegal" do + expect { parse("unless true { $a = 10 } elsif false { $a = 15} else {$a = 20}")}.to raise_error(Puppet::ParseError) + end + end + + context "When parsing selector expressions" do + it "$a = $b ? banana => fruit " do + dump(parse("$a = $b ? banana => fruit")).should == + "(= $a (? $b (banana => fruit)))" + end + + it "$a = $b ? { banana => fruit}" do + dump(parse("$a = $b ? { banana => fruit }")).should == + "(= $a (? $b (banana => fruit)))" + end + + it "does not fail on a trailing blank line" do + dump(parse("$a = $b ? { banana => fruit }\n\n")).should == + "(= $a (? $b (banana => fruit)))" + end + + it "$a = $b ? { banana => fruit, grape => berry }" do + dump(parse("$a = $b ? {banana => fruit, grape => berry}")).should == + "(= $a (? $b (banana => fruit) (grape => berry)))" + end + + it "$a = $b ? { banana => fruit, grape => berry, default => wat }" do + dump(parse("$a = $b ? {banana => fruit, grape => berry, default => wat}")).should == + "(= $a (? $b (banana => fruit) (grape => berry) (:default => wat)))" + end + + it "$a = $b ? { default => wat, banana => fruit, grape => berry, }" do + dump(parse("$a = $b ? {default => wat, banana => fruit, grape => berry}")).should == + "(= $a (? $b (:default => wat) (banana => fruit) (grape => berry)))" + end + end + + context "When parsing case statements" do + it "case $a { a : {}}" do + dump(parse("case $a { a : {}}")).should == + ["(case $a", + " (when (a) (then ())))" + ].join("\n") + end + + it "allows a parenthesized value expression" do + dump(parse("case ($a) { a : {}}")).should == + ["(case $a", + " (when (a) (then ())))" + ].join("\n") + end + + it "case $a { /.*/ : {}}" do + dump(parse("case $a { /.*/ : {}}")).should == + ["(case $a", + " (when (/.*/) (then ())))" + ].join("\n") + end + + it "case $a { a, b : {}}" do + dump(parse("case $a { a, b : {}}")).should == + ["(case $a", + " (when (a b) (then ())))" + ].join("\n") + end + + it "case $a { a, b : {} default : {}}" do + dump(parse("case $a { a, b : {} default : {}}")).should == + ["(case $a", + " (when (a b) (then ()))", + " (when (:default) (then ())))" + ].join("\n") + end + + it "case $a { a : {$b = 10 $c = 20}}" do + dump(parse("case $a { a : {$b = 10 $c = 20}}")).should == + ["(case $a", + " (when (a) (then (block (= $b 10) (= $c 20)))))" + ].join("\n") + end + end + + context "When parsing imports" do + it "import 'foo'" do + dump(parse("import 'foo'")).should == "(import 'foo')" + end + + it "import 'foo', 'bar'" do + dump(parse("import 'foo', 'bar'")).should == "(import 'foo' 'bar')" + end + end +end diff --git a/spec/unit/pops/parser/parse_containers_spec.rb b/spec/unit/pops/parser/parse_containers_spec.rb new file mode 100644 index 000000000..a6b9f2022 --- /dev/null +++ b/spec/unit/pops/parser/parse_containers_spec.rb @@ -0,0 +1,175 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/parser_rspec_helper') + +describe "egrammar parsing containers" do + include ParserRspecHelper + + context "When parsing file scope" do + it "$a = 10 $b = 20" do + dump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))" + end + + it "$a = 10" do + dump(parse("$a = 10")).should == "(= $a 10)" + end + end + + context "When parsing class" do + it "class foo {}" do + dump(parse("class foo {}")).should == "(class foo ())" + end + + it "class foo::bar {}" do + dump(parse("class foo::bar {}")).should == "(class foo::bar ())" + end + + it "class foo inherits bar {}" do + dump(parse("class foo inherits bar {}")).should == "(class foo (inherits bar) ())" + end + + it "class foo($a) {}" do + dump(parse("class foo($a) {}")).should == "(class foo (parameters a) ())" + end + + it "class foo($a, $b) {}" do + dump(parse("class foo($a, $b) {}")).should == "(class foo (parameters a b) ())" + end + + it "class foo($a, $b=10) {}" do + dump(parse("class foo($a, $b=10) {}")).should == "(class foo (parameters a (= b 10)) ())" + end + + it "class foo($a, $b) inherits belgo::bar {}" do + dump(parse("class foo($a, $b) inherits belgo::bar{}")).should == "(class foo (inherits belgo::bar) (parameters a b) ())" + end + + it "class foo {$a = 10 $b = 20}" do + dump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))" + end + + context "it should handle '3x weirdness'" do + it "class class {} # a class named 'class'" do + # Not as much weird as confusing that it is possible to name a class 'class'. Can have + # a very confusing effect when resolving relative names, getting the global hardwired "Class" + # instead of some foo::class etc. + # This is allowed in 3.x. + dump(parse("class class {}")).should == "(class class ())" + end + + it "class default {} # a class named 'default'" do + # The weirdness here is that a class can inherit 'default' but not declare a class called default. + # (It will work with relative names i.e. foo::default though). The whole idea with keywords as + # names is flawed to begin with - it generally just a very bad idea. + expect { dump(parse("class default {}")).should == "(class default ())" }.to raise_error(Puppet::ParseError) + end + + it "class foo::default {} # a nested name 'default'" do + dump(parse("class foo::default {}")).should == "(class foo::default ())" + end + + it "class class inherits default {} # inherits default", :broken => true do + dump(parse("class class inherits default {}")).should == "(class class (inherits default) ())" + end + + it "class class inherits default {} # inherits default" do + # TODO: See previous test marked as :broken=>true, it is actually this test (result) that is wacky, + # this because a class is named at parse time (since class evaluation is lazy, the model must have the + # full class name for nested classes - only, it gets this wrong when a class is named "class" - or at least + # I think it is wrong.) + # + dump(parse("class class inherits default {}")).should == "(class class::class (inherits default) ())" + end + + it "class foo inherits class" do + dump(parse("class foo inherits class {}")).should == "(class foo (inherits class) ())" + end + end + end + + context "When the parser parses define" do + it "define foo {}" do + dump(parse("define foo {}")).should == "(define foo ())" + end + + it "define foo::bar {}" do + dump(parse("define foo::bar {}")).should == "(define foo::bar ())" + end + + it "define foo($a) {}" do + dump(parse("define foo($a) {}")).should == "(define foo (parameters a) ())" + end + + it "define foo($a, $b) {}" do + dump(parse("define foo($a, $b) {}")).should == "(define foo (parameters a b) ())" + end + + it "define foo($a, $b=10) {}" do + dump(parse("define foo($a, $b=10) {}")).should == "(define foo (parameters a (= b 10)) ())" + end + + it "define foo {$a = 10 $b = 20}" do + dump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))" + end + + context "it should handle '3x weirdness'" do + it "define class {} # a define named 'class'" do + # This is weird because Class already exists, and instantiating this define will probably not + # work + dump(parse("define class {}")).should == "(define class ())" + end + + it "define default {} # a define named 'default'" do + # Check unwanted ability to define 'default'. + # The expression below is not allowed (which is good). + # + expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError) + end + end + end + + context "When parsing node" do + it "node foo {}" do + dump(parse("node foo {}")).should == "(node (matches foo) ())" + end + + it "node foo, x::bar, default {}" do + dump(parse("node foo, x::bar, default {}")).should == "(node (matches foo x::bar :default) ())" + end + + it "node 'foo' {}" do + dump(parse("node 'foo' {}")).should == "(node (matches 'foo') ())" + end + + it "node foo inherits x::bar {}" do + dump(parse("node foo inherits x::bar {}")).should == "(node (matches foo) (parent x::bar) ())" + end + + it "node foo inherits 'bar' {}" do + dump(parse("node foo inherits 'bar' {}")).should == "(node (matches foo) (parent 'bar') ())" + end + + it "node foo inherits default {}" do + dump(parse("node foo inherits default {}")).should == "(node (matches foo) (parent :default) ())" + end + + it "node /web.*/ {}" do + dump(parse("node /web.*/ {}")).should == "(node (matches /web.*/) ())" + end + + it "node /web.*/, /do\.wop.*/, and.so.on {}" do + dump(parse("node /web.*/, /do\.wop.*/, 'and.so.on' {}")).should == "(node (matches /web.*/ /do\.wop.*/ 'and.so.on') ())" + end + + it "node wat inherits /apache.*/ {}" do + expect { parse("node wat inherits /apache.*/ {}")}.to raise_error(Puppet::ParseError) + end + + it "node foo inherits bar {$a = 10 $b = 20}" do + dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches foo) (parent bar) (block (= $a 10) (= $b 20)))" + end + end +end diff --git a/spec/unit/pops/parser/parse_resource_spec.rb b/spec/unit/pops/parser/parse_resource_spec.rb new file mode 100644 index 000000000..7d2b54d10 --- /dev/null +++ b/spec/unit/pops/parser/parse_resource_spec.rb @@ -0,0 +1,228 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/parser_rspec_helper') + +describe "egrammar parsing resource declarations" do + include ParserRspecHelper + + context "When parsing regular resource" do + it "file { 'title': }" do + dump(parse("file { 'title': }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end + + it "file { 'title': path => '/somewhere', mode => 0777}" do + dump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')", + " (mode => 0777)))" + ].join("\n") + end + + it "file { 'title': path => '/somewhere', }" do + dump(parse("file { 'title': path => '/somewhere', }")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')))" + ].join("\n") + end + + it "file { 'title': , }" do + dump(parse("file { 'title': , }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end + + it "file { 'title': ; }" do + dump(parse("file { 'title': ; }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end + + it "file { 'title': ; 'other_title': }" do + dump(parse("file { 'title': ; 'other_title': }")).should == [ + "(resource file", + " ('title')", + " ('other_title'))" + ].join("\n") + end + + it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do + dump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ + "(resource file", + " ('title1'", + " (path => 'x'))", + " ('title2'", + " (path => 'y')))", + ].join("\n") + end + end + + context "When parsing resource defaults" do + it "File { }" do + dump(parse("File { }")).should == "(resource-defaults file)" + end + + it "File { mode => 0777 }" do + dump(parse("File { mode => 0777}")).should == [ + "(resource-defaults file", + " (mode => 0777))" + ].join("\n") + end + end + + context "When parsing resource override" do + it "File['x'] { }" do + dump(parse("File['x'] { }")).should == "(override (slice file 'x'))" + end + + it "File['x'] { x => 1 }" do + dump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))" + end + + it "File['x', 'y'] { x => 1 }" do + dump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))" + end + + it "File['x'] { x => 1, y => 2 }" do + dump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))" + end + + it "File['x'] { x +> 1 }" do + dump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))" + end + end + + context "When parsing virtual and exported resources" do + it "@@file { 'title': }" do + dump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))" + end + + it "@file { 'title': }" do + dump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))" + end + + it "@file { mode => 0777 }" do + # Defaults are not virtualizeable + expect { + dump(parse("@file { mode => 0777 }")).should == "" + }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/) + end + end + + context "When parsing class resource" do + it "class { 'cname': }" do + dump(parse("class { 'cname': }")).should == [ + "(resource class", + " ('cname'))" + ].join("\n") + end + + it "class { 'cname': x => 1, y => 2}" do + dump(parse("class { 'cname': x => 1, y => 2}")).should == [ + "(resource class", + " ('cname'", + " (x => 1)", + " (y => 2)))" + ].join("\n") + end + + it "class { 'cname1': x => 1; 'cname2': y => 2}" do + dump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [ + "(resource class", + " ('cname1'", + " (x => 1))", + " ('cname2'", + " (y => 2)))", + ].join("\n") + end + end + + context "reported issues in 3.x" do + it "should not screw up on brackets in title of resource #19632" do + dump(parse('notify { "thisisa[bug]": }')).should == [ + "(resource notify", + " ('thisisa[bug]'))", + ].join("\n") + end + end + + context "When parsing Relationships" do + it "File[a] -> File[b]" do + dump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))" + end + + it "File[a] <- File[b]" do + dump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))" + end + + it "File[a] ~> File[b]" do + dump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))" + end + + it "File[a] <~ File[b]" do + dump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))" + end + + it "Should chain relationships" do + dump(parse("a -> b -> c")).should == + "(-> (-> a b) c)" + end + + it "Should chain relationships" do + dump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should == + "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))" + end + + it "should create relationships between collects" do + dump(parse("File <| mode == 0644 |> -> File <| mode == 0755 |>")).should == + "(-> (collect file\n (<| |> (== mode 0644))) (collect file\n (<| |> (== mode 0755))))" + end + end + + context "When parsing collection" do + context "of virtual resources" do + it "File <| |>" do + dump(parse("File <| |>")).should == "(collect file\n (<| |>))" + end + end + + context "of exported resources" do + it "File <<| |>>" do + dump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))" + end + end + + context "queries are parsed with correct precedence" do + it "File <| tag == 'foo' |>" do + dump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))" + end + + it "File <| tag == 'foo' and mode != 0777 |>" do + dump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))" + end + + it "File <| tag == 'foo' or mode != 0777 |>" do + dump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))" + end + + it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do + dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should == + "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))" + end + + it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do + dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should == + "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))" + end + end + end +end diff --git a/spec/unit/pops/parser/parser_rspec_helper.rb b/spec/unit/pops/parser/parser_rspec_helper.rb new file mode 100644 index 000000000..4b2e7a00b --- /dev/null +++ b/spec/unit/pops/parser/parser_rspec_helper.rb @@ -0,0 +1,11 @@ +require 'puppet/pops' + +require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') + +module ParserRspecHelper + include FactoryRspecHelper + def parse(code) + parser = Puppet::Pops::Parser::Parser.new() + parser.parse_string(code) + end +end diff --git a/spec/unit/pops/parser/parser_spec.rb b/spec/unit/pops/parser/parser_spec.rb new file mode 100644 index 000000000..86d3d6dd0 --- /dev/null +++ b/spec/unit/pops/parser/parser_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' +require 'puppet/pops' + +describe Puppet::Pops::Parser::Parser do + it "should instantiate a parser" do + parser = Puppet::Pops::Parser::Parser.new() + parser.class.should == Puppet::Pops::Parser::Parser + end + + it "should parse a code string and return a model" do + parser = Puppet::Pops::Parser::Parser.new() + model = parser.parse_string("$a = 10").current + model.class.should == Model::AssignmentExpression + end +end diff --git a/spec/unit/pops/parser/rgen_sanitycheck_spec.rb b/spec/unit/pops/parser/rgen_sanitycheck_spec.rb new file mode 100644 index 000000000..4184cc122 --- /dev/null +++ b/spec/unit/pops/parser/rgen_sanitycheck_spec.rb @@ -0,0 +1,15 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +require 'rgen/array_extensions' + +describe "RGen working with hashes" do + it "should be possible to create an empty hash after having required the files above" do + # If this fails, it means the rgen addition to Array is not monkey patched as it + # should (it will return an array instead of fail in a method_missing), and thus + # screw up Hash's check if it can do "to_hash' or not. + # + Hash[[]] + end +end diff --git a/spec/unit/pops/transformer/transform_basic_expressions_spec.rb b/spec/unit/pops/transformer/transform_basic_expressions_spec.rb new file mode 100644 index 000000000..11a2c76f5 --- /dev/null +++ b/spec/unit/pops/transformer/transform_basic_expressions_spec.rb @@ -0,0 +1,243 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') + +describe "transformation to Puppet AST for basic expressions" do + include TransformerRspecHelper + + context "When transforming arithmetic" do + context "with Integers" do + it "$a = 2 + 2" do; astdump(parse("$a = 2 + 2")).should == "(= $a (+ 2 2))" ; end + it "$a = 7 - 3" do; astdump(parse("$a = 7 - 3")).should == "(= $a (- 7 3))" ; end + it "$a = 6 * 3" do; astdump(parse("$a = 6 * 3")).should == "(= $a (* 6 3))" ; end + it "$a = 6 / 3" do; astdump(parse("$a = 6 / 3")).should == "(= $a (/ 6 3))" ; end + it "$a = 6 % 3" do; astdump(parse("$a = 6 % 3")).should == "(= $a (% 6 3))" ; end + it "$a = -(6/3)" do; astdump(parse("$a = -(6/3)")).should == "(= $a (- (/ 6 3)))" ; end + it "$a = -6/3" do; astdump(parse("$a = -6/3")).should == "(= $a (/ (- 6) 3))" ; end + it "$a = 8 >> 1 " do; astdump(parse("$a = 8 >> 1")).should == "(= $a (>> 8 1))" ; end + it "$a = 8 << 1 " do; astdump(parse("$a = 8 << 1")).should == "(= $a (<< 8 1))" ; end + end + + context "with Floats" do + it "$a = 2.2 + 2.2" do; astdump(parse("$a = 2.2 + 2.2")).should == "(= $a (+ 2.2 2.2))" ; end + it "$a = 7.7 - 3.3" do; astdump(parse("$a = 7.7 - 3.3")).should == "(= $a (- 7.7 3.3))" ; end + it "$a = 6.1 * 3.1" do; astdump(parse("$a = 6.1 - 3.1")).should == "(= $a (- 6.1 3.1))" ; end + it "$a = 6.6 / 3.3" do; astdump(parse("$a = 6.6 / 3.3")).should == "(= $a (/ 6.6 3.3))" ; end + it "$a = -(6.0/3.0)" do; astdump(parse("$a = -(6.0/3.0)")).should == "(= $a (- (/ 6.0 3.0)))" ; end + it "$a = -6.0/3.0" do; astdump(parse("$a = -6.0/3.0")).should == "(= $a (/ (- 6.0) 3.0))" ; end + it "$a = 3.14 << 2" do; astdump(parse("$a = 3.14 << 2")).should == "(= $a (<< 3.14 2))" ; end + it "$a = 3.14 >> 2" do; astdump(parse("$a = 3.14 >> 2")).should == "(= $a (>> 3.14 2))" ; end + end + + context "with hex and octal Integer values" do + it "$a = 0xAB + 0xCD" do; astdump(parse("$a = 0xAB + 0xCD")).should == "(= $a (+ 0xAB 0xCD))" ; end + it "$a = 0777 - 0333" do; astdump(parse("$a = 0777 - 0333")).should == "(= $a (- 0777 0333))" ; end + end + + context "with strings requiring boxing to Numeric" do + # In AST, there is no difference, the ast dumper prints all numbers without quotes - they are still + # strings + it "$a = '2' + '2'" do; astdump(parse("$a = '2' + '2'")).should == "(= $a (+ 2 2))" ; end + it "$a = '2.2' + '0.2'" do; astdump(parse("$a = '2.2' + '0.2'")).should == "(= $a (+ 2.2 0.2))" ; end + it "$a = '0xab' + '0xcd'" do; astdump(parse("$a = '0xab' + '0xcd'")).should == "(= $a (+ 0xab 0xcd))" ; end + it "$a = '0777' + '0333'" do; astdump(parse("$a = '0777' + '0333'")).should == "(= $a (+ 0777 0333))" ; end + end + + context "precedence should be correct" do + it "$a = 1 + 2 * 3" do; astdump(parse("$a = 1 + 2 * 3")).should == "(= $a (+ 1 (* 2 3)))"; end + it "$a = 1 + 2 % 3" do; astdump(parse("$a = 1 + 2 % 3")).should == "(= $a (+ 1 (% 2 3)))"; end + it "$a = 1 + 2 / 3" do; astdump(parse("$a = 1 + 2 / 3")).should == "(= $a (+ 1 (/ 2 3)))"; end + it "$a = 1 + 2 << 3" do; astdump(parse("$a = 1 + 2 << 3")).should == "(= $a (<< (+ 1 2) 3))"; end + it "$a = 1 + 2 >> 3" do; astdump(parse("$a = 1 + 2 >> 3")).should == "(= $a (>> (+ 1 2) 3))"; end + end + + context "parentheses alter precedence" do + it "$a = (1 + 2) * 3" do; astdump(parse("$a = (1 + 2) * 3")).should == "(= $a (* (+ 1 2) 3))"; end + it "$a = (1 + 2) / 3" do; astdump(parse("$a = (1 + 2) / 3")).should == "(= $a (/ (+ 1 2) 3))"; end + end + end + + context "When transforming boolean operations" do + context "using operators AND OR NOT" do + it "$a = true and true" do; astdump(parse("$a = true and true")).should == "(= $a (&& true true))"; end + it "$a = true or true" do; astdump(parse("$a = true or true")).should == "(= $a (|| true true))" ; end + it "$a = !true" do; astdump(parse("$a = !true")).should == "(= $a (! true))" ; end + end + + context "precedence should be correct" do + it "$a = false or true and true" do + astdump(parse("$a = false or true and true")).should == "(= $a (|| false (&& true true)))" + end + + it "$a = (false or true) and true" do + astdump(parse("$a = (false or true) and true")).should == "(= $a (&& (|| false true) true))" + end + + it "$a = !true or true and true" do + astdump(parse("$a = !false or true and true")).should == "(= $a (|| (! false) (&& true true)))" + end + end + + # Possibly change to check of literal expressions + context "on values requiring boxing to Boolean" do + it "'x' == true" do + astdump(parse("! 'x'")).should == "(! 'x')" + end + + it "'' == false" do + astdump(parse("! ''")).should == "(! '')" + end + + it ":undef == false" do + astdump(parse("! undef")).should == "(! :undef)" + end + end + end + + context "When transforming comparisons" do + context "of string values" do + it "$a = 'a' == 'a'" do; astdump(parse("$a = 'a' == 'a'")).should == "(= $a (== 'a' 'a'))" ; end + it "$a = 'a' != 'a'" do; astdump(parse("$a = 'a' != 'a'")).should == "(= $a (!= 'a' 'a'))" ; end + it "$a = 'a' < 'b'" do; astdump(parse("$a = 'a' < 'b'")).should == "(= $a (< 'a' 'b'))" ; end + it "$a = 'a' > 'b'" do; astdump(parse("$a = 'a' > 'b'")).should == "(= $a (> 'a' 'b'))" ; end + it "$a = 'a' <= 'b'" do; astdump(parse("$a = 'a' <= 'b'")).should == "(= $a (<= 'a' 'b'))" ; end + it "$a = 'a' >= 'b'" do; astdump(parse("$a = 'a' >= 'b'")).should == "(= $a (>= 'a' 'b'))" ; end + end + + context "of integer values" do + it "$a = 1 == 1" do; astdump(parse("$a = 1 == 1")).should == "(= $a (== 1 1))" ; end + it "$a = 1 != 1" do; astdump(parse("$a = 1 != 1")).should == "(= $a (!= 1 1))" ; end + it "$a = 1 < 2" do; astdump(parse("$a = 1 < 2")).should == "(= $a (< 1 2))" ; end + it "$a = 1 > 2" do; astdump(parse("$a = 1 > 2")).should == "(= $a (> 1 2))" ; end + it "$a = 1 <= 2" do; astdump(parse("$a = 1 <= 2")).should == "(= $a (<= 1 2))" ; end + it "$a = 1 >= 2" do; astdump(parse("$a = 1 >= 2")).should == "(= $a (>= 1 2))" ; end + end + + context "of regular expressions (parse errors)" do + # Not supported in concrete syntax + it "$a = /.*/ == /.*/" do + expect { parse("$a = /.*/ == /.*/") }.to raise_error(Puppet::ParseError) + end + + it "$a = /.*/ != /a.*/" do + expect { parse("$a = /.*/ != /.*/") }.to raise_error(Puppet::ParseError) + end + end + end + + context "When transforming Regular Expression matching" do + it "$a = 'a' =~ /.*/" do; astdump(parse("$a = 'a' =~ /.*/")).should == "(= $a (=~ 'a' /.*/))" ; end + it "$a = 'a' =~ '.*'" do; astdump(parse("$a = 'a' =~ '.*'")).should == "(= $a (=~ 'a' '.*'))" ; end + it "$a = 'a' !~ /b.*/" do; astdump(parse("$a = 'a' !~ /b.*/")).should == "(= $a (!~ 'a' /b.*/))" ; end + it "$a = 'a' !~ 'b.*'" do; astdump(parse("$a = 'a' !~ 'b.*'")).should == "(= $a (!~ 'a' 'b.*'))" ; end + end + + context "When transforming Lists" do + it "$a = []" do + astdump(parse("$a = []")).should == "(= $a ([]))" + end + + it "$a = [1]" do + astdump(parse("$a = [1]")).should == "(= $a ([] 1))" + end + + it "$a = [1,2,3]" do + astdump(parse("$a = [1,2,3]")).should == "(= $a ([] 1 2 3))" + end + + it "[...[...[]]] should create nested arrays without trouble" do + astdump(parse("$a = [1,[2.0, 2.1, [2.2]],[3.0, 3.1]]")).should == "(= $a ([] 1 ([] 2.0 2.1 ([] 2.2)) ([] 3.0 3.1)))" + end + + it "$a = [2 + 2]" do + astdump(parse("$a = [2+2]")).should == "(= $a ([] (+ 2 2)))" + end + + it "$a [1,2,3] == [1,2,3]" do + astdump(parse("$a = [1,2,3] == [1,2,3]")).should == "(= $a (== ([] 1 2 3) ([] 1 2 3)))" + end + end + + context "When transforming indexed access" do + it "$a = $b[2]" do + astdump(parse("$a = $b[2]")).should == "(= $a (slice $b 2))" + end + + it "$a = [1, 2, 3][2]" do + astdump(parse("$a = [1,2,3][2]")).should == "(= $a (slice ([] 1 2 3) 2))" + end + + it "$a = {'a' => 1, 'b' => 2}['b']" do + astdump(parse("$a = {'a'=>1,'b' =>2}[b]")).should == "(= $a (slice ({} ('a' 1) ('b' 2)) b))" + end + end + + context "When transforming Hashes" do + it "should create a Hash when evaluating a LiteralHash" do + astdump(parse("$a = {'a'=>1,'b'=>2}")).should == "(= $a ({} ('a' 1) ('b' 2)))" + end + + it "$a = {...{...{}}} should create nested hashes without trouble" do + astdump(parse("$a = {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}")).should == "(= $a ({} ('a' 1) ('b' ({} ('x' 2.1) ('y' 2.2)))))" + end + + it "$a = {'a'=> 2 + 2} should evaluate values in entries" do + astdump(parse("$a = {'a'=>2+2}")).should == "(= $a ({} ('a' (+ 2 2))))" + end + + it "$a = {'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" do + astdump(parse("$a = {'a'=>1,'b'=>2} == {'a'=>1,'b'=>2}")).should == "(= $a (== ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))" + end + + it "$a = {'a'=> 1, 'b'=>2} != {'x'=> 1, 'y'=>3}" do + astdump(parse("$a = {'a'=>1,'b'=>2} != {'a'=>1,'b'=>2}")).should == "(= $a (!= ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))" + end + end + + context "When transforming the 'in' operator" do + it "with integer in a list" do + astdump(parse("$a = 1 in [1,2,3]")).should == "(= $a (in 1 ([] 1 2 3)))" + end + + it "with string key in a hash" do + astdump(parse("$a = 'a' in {'x'=>1, 'a'=>2, 'y'=> 3}")).should == "(= $a (in 'a' ({} ('a' 2) ('x' 1) ('y' 3))))" + end + + it "with substrings of a string" do + astdump(parse("$a = 'ana' in 'bananas'")).should == "(= $a (in 'ana' 'bananas'))" + end + + it "with sublist in a list" do + astdump(parse("$a = [2,3] in [1,2,3]")).should == "(= $a (in ([] 2 3) ([] 1 2 3)))" + end + end + + context "When transforming string interpolation" do + it "should interpolate a bare word as a variable name, \"${var}\"" do + astdump(parse("$a = \"$var\"")).should == "(= $a (cat '' (str $var) ''))" + end + + it "should interpolate a variable in a text expression, \"${$var}\"" do + astdump(parse("$a = \"${$var}\"")).should == "(= $a (cat '' (str $var) ''))" + end + + it "should interpolate two variables in a text expression" do + astdump(parse(%q{$a = "xxx $x and $y end"})).should == "(= $a (cat 'xxx ' (str $x) ' and ' (str $y) ' end'))" + end + + it "should interpolate one variables followed by parentheses" do + astdump(parse(%q{$a = "xxx ${x} (yay)"})).should == "(= $a (cat 'xxx ' (str $x) ' (yay)'))" + end + + it "should interpolate a variable, \"yo${var}yo\"" do + astdump(parse("$a = \"yo${var}yo\"")).should == "(= $a (cat 'yo' (str $var) 'yo'))" + end + + it "should interpolate any expression in a text expression, \"${var*2}\"" do + astdump(parse("$a = \"yo${var+2}yo\"")).should == "(= $a (cat 'yo' (str (+ $var 2)) 'yo'))" + end + end +end diff --git a/spec/unit/pops/transformer/transform_calls_spec.rb b/spec/unit/pops/transformer/transform_calls_spec.rb new file mode 100644 index 000000000..f2303db5b --- /dev/null +++ b/spec/unit/pops/transformer/transform_calls_spec.rb @@ -0,0 +1,80 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') + +describe "transformation to Puppet AST for function calls" do + include TransformerRspecHelper + + context "When transforming calls as statements" do + context "in top level scope" do + it "foo()" do + astdump(parse("foo()")).should == "(invoke foo)" + end + + it "foo bar" do + astdump(parse("foo bar")).should == "(invoke foo bar)" + end + end + + context "in nested scopes" do + it "if true { foo() }" do + astdump(parse("if true {foo()}")).should == "(if true\n (then (invoke foo)))" + end + + it "if true { foo bar}" do + astdump(parse("if true {foo bar}")).should == "(if true\n (then (invoke foo bar)))" + end + end + end + + context "When transforming calls as expressions" do + it "$a = foo()" do + astdump(parse("$a = foo()")).should == "(= $a (call foo))" + end + + it "$a = foo(bar)" do + astdump(parse("$a = foo()")).should == "(= $a (call foo))" + end + + # For egrammar where a bare word can be a "statement" + it "$a = foo bar # assignment followed by bare word is ok in egrammar" do + astdump(parse("$a = foo bar")).should == "(block (= $a foo) bar)" + end + + context "in nested scopes" do + it "if true { $a = foo() }" do + astdump(parse("if true { $a = foo()}")).should == "(if true\n (then (= $a (call foo))))" + end + + it "if true { $a= foo(bar)}" do + astdump(parse("if true {$a = foo(bar)}")).should == "(if true\n (then (= $a (call foo bar))))" + end + end + end + + context "When transforming method calls" do + it "$a.foo" do + astdump(parse("$a.foo")).should == "(call-method (. $a foo))" + end + + it "$a.foo {|| }" do + astdump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))" + end + + it "$a.foo {|| []} # check transformation to block with empty array" do + astdump(parse("$a.foo || { []}")).should == "(call-method (. $a foo) (lambda (block ([]))))" + end + + it "$a.foo {|$x| }" do + astdump(parse("$a.foo {|$x| }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" + end + + it "$a.foo {|$x| $b = $x}" do + astdump(parse("$a.foo {|$x| $b = $x}")).should == + "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))" + end + end +end diff --git a/spec/unit/pops/transformer/transform_conditionals_spec.rb b/spec/unit/pops/transformer/transform_conditionals_spec.rb new file mode 100644 index 000000000..6eefa51a3 --- /dev/null +++ b/spec/unit/pops/transformer/transform_conditionals_spec.rb @@ -0,0 +1,132 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') + +describe "transformation to Puppet AST for conditionals" do + include TransformerRspecHelper + + context "When transforming if statements" do + it "if true { $a = 10 }" do + astdump(parse("if true { $a = 10 }")).should == "(if true\n (then (= $a 10)))" + end + + it "if true { $a = 10 } else {$a = 20}" do + astdump(parse("if true { $a = 10 } else {$a = 20}")).should == + ["(if true", + " (then (= $a 10))", + " (else (= $a 20)))"].join("\n") + end + + it "if true { $a = 10 } elsif false { $a = 15} else {$a = 20}" do + astdump(parse("if true { $a = 10 } elsif false { $a = 15} else {$a = 20}")).should == + ["(if true", + " (then (= $a 10))", + " (else (if false", + " (then (= $a 15))", + " (else (= $a 20)))))"].join("\n") + end + + it "if true { $a = 10 $b = 10 } else {$a = 20}" do + astdump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == + ["(if true", + " (then (block (= $a 10) (= $b 20)))", + " (else (= $a 20)))"].join("\n") + end + end + + context "When transforming unless statements" do + # Note that Puppet 3.1 does not have an "unless x", it is encoded as "if !x" + it "unless true { $a = 10 }" do + astdump(parse("unless true { $a = 10 }")).should == "(if (! true)\n (then (= $a 10)))" + end + + it "unless true { $a = 10 } else {$a = 20}" do + astdump(parse("unless true { $a = 10 } else {$a = 20}")).should == + ["(if (! true)", + " (then (= $a 10))", + " (else (= $a 20)))"].join("\n") + end + + it "unless true { $a = 10 } elsif false { $a = 15} else {$a = 20} # is illegal" do + expect { parse("unless true { $a = 10 } elsif false { $a = 15} else {$a = 20}")}.to raise_error(Puppet::ParseError) + end + end + + context "When transforming selector expressions" do + it "$a = $b ? banana => fruit " do + astdump(parse("$a = $b ? banana => fruit")).should == + "(= $a (? $b (banana => fruit)))" + end + + it "$a = $b ? { banana => fruit}" do + astdump(parse("$a = $b ? { banana => fruit }")).should == + "(= $a (? $b (banana => fruit)))" + end + + it "$a = $b ? { banana => fruit, grape => berry }" do + astdump(parse("$a = $b ? {banana => fruit, grape => berry}")).should == + "(= $a (? $b (banana => fruit) (grape => berry)))" + end + + it "$a = $b ? { banana => fruit, grape => berry, default => wat }" do + astdump(parse("$a = $b ? {banana => fruit, grape => berry, default => wat}")).should == + "(= $a (? $b (banana => fruit) (grape => berry) (:default => wat)))" + end + + it "$a = $b ? { default => wat, banana => fruit, grape => berry, }" do + astdump(parse("$a = $b ? {default => wat, banana => fruit, grape => berry}")).should == + "(= $a (? $b (:default => wat) (banana => fruit) (grape => berry)))" + end + end + + context "When transforming case statements" do + it "case $a { a : {}}" do + astdump(parse("case $a { a : {}}")).should == + ["(case $a", + " (when (a) (then ())))" + ].join("\n") + end + + it "case $a { /.*/ : {}}" do + astdump(parse("case $a { /.*/ : {}}")).should == + ["(case $a", + " (when (/.*/) (then ())))" + ].join("\n") + end + + it "case $a { a, b : {}}" do + astdump(parse("case $a { a, b : {}}")).should == + ["(case $a", + " (when (a b) (then ())))" + ].join("\n") + end + + it "case $a { a, b : {} default : {}}" do + astdump(parse("case $a { a, b : {} default : {}}")).should == + ["(case $a", + " (when (a b) (then ()))", + " (when (:default) (then ())))" + ].join("\n") + end + + it "case $a { a : {$b = 10 $c = 20}}" do + astdump(parse("case $a { a : {$b = 10 $c = 20}}")).should == + ["(case $a", + " (when (a) (then (block (= $b 10) (= $c 20)))))" + ].join("\n") + end + end + + context "When transforming imports" do + it "import 'foo'" do + astdump(parse("import 'foo'")).should == ":nop" + end + + it "import 'foo', 'bar'" do + astdump(parse("import 'foo', 'bar'")).should == ":nop" + end + end +end diff --git a/spec/unit/pops/transformer/transform_containers_spec.rb b/spec/unit/pops/transformer/transform_containers_spec.rb new file mode 100644 index 000000000..682860fe1 --- /dev/null +++ b/spec/unit/pops/transformer/transform_containers_spec.rb @@ -0,0 +1,182 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') + +describe "transformation to Puppet AST for containers" do + include TransformerRspecHelper + + context "When transforming file scope" do + it "$a = 10 $b = 20" do + astdump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))" + end + + it "$a = 10" do + astdump(parse("$a = 10")).should == "(= $a 10)" + end + end + + context "When transforming class" do + it "class foo {}" do + astdump(parse("class foo {}")).should == "(class foo ())" + end + + it "class foo::bar {}" do + astdump(parse("class foo::bar {}")).should == "(class foo::bar ())" + end + + it "class foo inherits bar {}" do + astdump(parse("class foo inherits bar {}")).should == "(class foo (inherits bar) ())" + end + + it "class foo($a) {}" do + astdump(parse("class foo($a) {}")).should == "(class foo (parameters a) ())" + end + + it "class foo($a, $b) {}" do + astdump(parse("class foo($a, $b) {}")).should == "(class foo (parameters a b) ())" + end + + it "class foo($a, $b=10) {}" do + astdump(parse("class foo($a, $b=10) {}")).should == "(class foo (parameters a (= b 10)) ())" + end + + it "class foo($a, $b) inherits belgo::bar {}" do + astdump(parse("class foo($a, $b) inherits belgo::bar{}")).should == "(class foo (inherits belgo::bar) (parameters a b) ())" + end + + it "class foo {$a = 10 $b = 20}" do + astdump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))" + end + + context "it should handle '3x weirdness'" do + it "class class {} # a class named 'class'" do + # Not as much weird as confusing that it is possible to name a class 'class'. Can have + # a very confusing effect when resolving relative names, getting the global hardwired "Class" + # instead of some foo::class etc. + # This is allowed in 3.x. + astdump(parse("class class {}")).should == "(class class ())" + end + + it "class default {} # a class named 'default'" do + # The weirdness here is that a class can inherit 'default' but not declare a class called default. + # (It will work with relative names i.e. foo::default though). The whole idea with keywords as + # names is flawed to begin with - it generally just a very bad idea. + expect { dump(parse("class default {}")).should == "(class default ())" }.to raise_error(Puppet::ParseError) + end + + it "class foo::default {} # a nested name 'default'" do + astdump(parse("class foo::default {}")).should == "(class foo::default ())" + end + + it "class class inherits default {} # inherits default", :broken => true do + astdump(parse("class class inherits default {}")).should == "(class class (inherits default) ())" + end + + it "class class inherits default {} # inherits default" do + # TODO: See previous test marked as :broken=>true, it is actually this test (result) that is wacky, + # this because a class is named at parse time (since class evaluation is lazy, the model must have the + # full class name for nested classes - only, it gets this wrong when a class is named "class" - or at least + # I think it is wrong.) + # + astdump(parse("class class inherits default {}")).should == "(class class::class (inherits default) ())" + end + + it "class foo inherits class" do + astdump(parse("class foo inherits class {}")).should == "(class foo (inherits class) ())" + end + end + end + + context "When transforming define" do + it "define foo {}" do + astdump(parse("define foo {}")).should == "(define foo ())" + end + + it "define foo::bar {}" do + astdump(parse("define foo::bar {}")).should == "(define foo::bar ())" + end + + it "define foo($a) {}" do + astdump(parse("define foo($a) {}")).should == "(define foo (parameters a) ())" + end + + it "define foo($a, $b) {}" do + astdump(parse("define foo($a, $b) {}")).should == "(define foo (parameters a b) ())" + end + + it "define foo($a, $b=10) {}" do + astdump(parse("define foo($a, $b=10) {}")).should == "(define foo (parameters a (= b 10)) ())" + end + + it "define foo {$a = 10 $b = 20}" do + astdump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))" + end + + context "it should handle '3x weirdness'" do + it "define class {} # a define named 'class'" do + # This is weird because Class already exists, and instantiating this define will probably not + # work + astdump(parse("define class {}")).should == "(define class ())" + end + + it "define default {} # a define named 'default'" do + # Check unwanted ability to define 'default'. + # The expression below is not allowed (which is good). + # + expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError) + end + end + end + + context "When transforming node" do + it "node foo {}" do + # AST can not differentiate between bare word and string + astdump(parse("node foo {}")).should == "(node (matches 'foo') ())" + end + + it "node foo, x.bar, default {}" do + # AST can not differentiate between bare word and string + astdump(parse("node foo, x_bar, default {}")).should == "(node (matches 'foo' 'x_bar' :default) ())" + end + + it "node 'foo' {}" do + # AST can not differentiate between bare word and string + astdump(parse("node 'foo' {}")).should == "(node (matches 'foo') ())" + end + + it "node foo inherits x::bar {}" do + # AST can not differentiate between bare word and string + astdump(parse("node foo inherits x_bar {}")).should == "(node (matches 'foo') (parent x_bar) ())" + end + + it "node foo inherits 'bar' {}" do + # AST can not differentiate between bare word and string + astdump(parse("node foo inherits 'bar' {}")).should == "(node (matches 'foo') (parent 'bar') ())" + end + + it "node foo inherits default {}" do + # AST can not differentiate between bare word and string + astdump(parse("node foo inherits default {}")).should == "(node (matches 'foo') (parent :default) ())" + end + + it "node /web.*/ {}" do + astdump(parse("node /web.*/ {}")).should == "(node (matches /web.*/) ())" + end + + it "node /web.*/, /do\.wop.*/, and.so.on {}" do + astdump(parse("node /web.*/, /do\.wop.*/, 'and.so.on' {}")).should == "(node (matches /web.*/ /do\.wop.*/ 'and.so.on') ())" + end + + it "node wat inherits /apache.*/ {}" do + expect { parse("node wat inherits /apache.*/ {}")}.to raise_error(Puppet::ParseError) + end + + it "node foo inherits bar {$a = 10 $b = 20}" do + # AST can not differentiate between bare word and string + astdump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches 'foo') (parent bar) (block (= $a 10) (= $b 20)))" + end + end +end diff --git a/spec/unit/pops/transformer/transform_resource_spec.rb b/spec/unit/pops/transformer/transform_resource_spec.rb new file mode 100644 index 000000000..251ef2f75 --- /dev/null +++ b/spec/unit/pops/transformer/transform_resource_spec.rb @@ -0,0 +1,185 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') + +describe "transformation to Puppet AST for resource declarations" do + include TransformerRspecHelper + + context "When transforming regular resource" do + it "file { 'title': }" do + astdump(parse("file { 'title': }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end + + it "file { 'title': ; 'other_title': }" do + astdump(parse("file { 'title': ; 'other_title': }")).should == [ + "(resource file", + " ('title')", + " ('other_title'))" + ].join("\n") + end + + it "file { 'title': path => '/somewhere', mode => 0777}" do + astdump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')", + " (mode => 0777)))" + ].join("\n") + end + + it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do + astdump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ + "(resource file", + " ('title1'", + " (path => 'x'))", + " ('title2'", + " (path => 'y')))", + ].join("\n") + end + end + + context "When transforming resource defaults" do + it "File { }" do + astdump(parse("File { }")).should == "(resource-defaults file)" + end + + it "File { mode => 0777 }" do + astdump(parse("File { mode => 0777}")).should == [ + "(resource-defaults file", + " (mode => 0777))" + ].join("\n") + end + end + + context "When transforming resource override" do + it "File['x'] { }" do + astdump(parse("File['x'] { }")).should == "(override (slice file 'x'))" + end + + it "File['x'] { x => 1 }" do + astdump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))" + end + + it "File['x', 'y'] { x => 1 }" do + astdump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))" + end + + it "File['x'] { x => 1, y => 2 }" do + astdump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))" + end + + it "File['x'] { x +> 1 }" do + astdump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))" + end + end + + context "When transforming virtual and exported resources" do + it "@@file { 'title': }" do + astdump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))" + end + + it "@file { 'title': }" do + astdump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))" + end + end + + context "When transforming class resource" do + it "class { 'cname': }" do + astdump(parse("class { 'cname': }")).should == [ + "(resource class", + " ('cname'))" + ].join("\n") + end + + it "class { 'cname': x => 1, y => 2}" do + astdump(parse("class { 'cname': x => 1, y => 2}")).should == [ + "(resource class", + " ('cname'", + " (x => 1)", + " (y => 2)))" + ].join("\n") + end + + it "class { 'cname1': x => 1; 'cname2': y => 2}" do + astdump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [ + "(resource class", + " ('cname1'", + " (x => 1))", + " ('cname2'", + " (y => 2)))", + ].join("\n") + end + end + + context "When transforming Relationships" do + it "File[a] -> File[b]" do + astdump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))" + end + + it "File[a] <- File[b]" do + astdump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))" + end + + it "File[a] ~> File[b]" do + astdump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))" + end + + it "File[a] <~ File[b]" do + astdump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))" + end + + it "Should chain relationships" do + astdump(parse("a -> b -> c")).should == + "(-> (-> a b) c)" + end + + it "Should chain relationships" do + astdump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should == + "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))" + end + end + + context "When transforming collection" do + context "of virtual resources" do + it "File <| |>" do + astdump(parse("File <| |>")).should == "(collect file\n (<| |>))" + end + end + + context "of exported resources" do + it "File <<| |>>" do + astdump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))" + end + end + + context "queries are parsed with correct precedence" do + it "File <| tag == 'foo' |>" do + astdump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))" + end + + it "File <| tag == 'foo' and mode != 0777 |>" do + astdump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))" + end + + it "File <| tag == 'foo' or mode != 0777 |>" do + astdump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))" + end + + it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do + astdump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should == + "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))" + end + + it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do + astdump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should == + "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))" + end + end + end +end diff --git a/spec/unit/pops/transformer/transformer_rspec_helper.rb b/spec/unit/pops/transformer/transformer_rspec_helper.rb new file mode 100644 index 000000000..94c3f02c1 --- /dev/null +++ b/spec/unit/pops/transformer/transformer_rspec_helper.rb @@ -0,0 +1,27 @@ +require 'puppet/pops' +require 'puppet/parser/ast' + +require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') + +module TransformerRspecHelper + include FactoryRspecHelper + # Dumps the AST to string form + # + def astdump(ast) + ast = transform(ast) unless ast.kind_of?(Puppet::Parser::AST) + Puppet::Pops::Model::AstTreeDumper.new.dump(ast) + end + + # Transforms the Pops model to an AST model + # + def transform(model) + Puppet::Pops::Model::AstTransformer.new.transform(model) + end + + # Parses the string code to a Pops model + # + def parse(code) + parser = Puppet::Pops::Parser::Parser.new() + parser.parse_string(code) + end +end diff --git a/spec/unit/pops/visitor_spec.rb b/spec/unit/pops/visitor_spec.rb new file mode 100644 index 000000000..c5858c2c2 --- /dev/null +++ b/spec/unit/pops/visitor_spec.rb @@ -0,0 +1,94 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' + +describe Puppet::Pops::Visitor do + describe "A visitor and a visitable in a configuration with min and max args set to 0" do + class DuckProcessor + def initialize + @friend_visitor = Puppet::Pops::Visitor.new(self, "friend", 0, 0) + end + + def hi(o, *args) + @friend_visitor.visit(o, *args) + end + + def friend_Duck(o) + "Hi #{o.class}" + end + + def friend_Numeric(o) + "Howdy #{o.class}" + end + end + + class Duck + include Puppet::Pops::Visitable + end + + it "should select the expected method when there are no arguments" do + duck = Duck.new + duck_processor = DuckProcessor.new + duck_processor.hi(duck).should == "Hi Duck" + end + + it "should fail if there are too many arguments" do + duck = Duck.new + duck_processor = DuckProcessor.new + expect { duck_processor.hi(duck, "how are you?") }.to raise_error(/^Visitor Error: Too many.*/) + end + + it "should select method for superclass" do + duck_processor = DuckProcessor.new + duck_processor.hi(42).should == "Howdy Fixnum" + end + + it "should select method for superclass" do + duck_processor = DuckProcessor.new + duck_processor.hi(42.0).should == "Howdy Float" + end + + it "should fail if class not handled" do + duck_processor = DuckProcessor.new + expect { duck_processor.hi("wassup?") }.to raise_error(/Visitor Error: the configured.*/) + end + end + + describe "A visitor and a visitable in a configuration with min =1, and max args set to 2" do + class DuckProcessor2 + def initialize + @friend_visitor = Puppet::Pops::Visitor.new(self, "friend", 1, 2) + end + + def hi(o, *args) + @friend_visitor.visit(o, *args) + end + + def friend_Duck(o, drink, eat="grain") + "Hi #{o.class}, drink=#{drink}, eat=#{eat}" + end + end + + class Duck + include Puppet::Pops::Visitable + end + + it "should select the expected method when there are is one arguments" do + duck = Duck.new + duck_processor = DuckProcessor2.new + duck_processor.hi(duck, "water").should == "Hi Duck, drink=water, eat=grain" + end + + it "should fail if there are too many arguments" do + duck = Duck.new + duck_processor = DuckProcessor2.new + expect { duck_processor.hi(duck, "scotch", "soda", "peanuts") }.to raise_error(/^Visitor Error: Too many.*/) + end + + it "should fail if there are too few arguments" do + duck = Duck.new + duck_processor = DuckProcessor2.new + expect { duck_processor.hi(duck) }.to raise_error(/^Visitor Error: Too few.*/) + end + end +end diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index a59e94eb2..2c9a54e68 100755 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -139,6 +139,17 @@ describe Puppet::Property do property.stubs(:path).returns "/my/param" property.event.source_description.should == "/my/param" end + + it "should have the 'invalidate_refreshes' value set if set on a value" do + property.stubs(:event_name).returns :my_event + property.stubs(:should).returns "foo" + foo = mock() + foo.expects(:invalidate_refreshes).returns(true) + collection = mock() + collection.expects(:match?).with("foo").returns(foo) + property.class.stubs(:value_collection).returns(collection) + property.event.invalidate_refreshes.should be_true + end end describe "when shadowing metaparameters" do diff --git a/spec/unit/provider/augeas/augeas_spec.rb b/spec/unit/provider/augeas/augeas_spec.rb index 77a1621f6..514772ae4 100755 --- a/spec/unit/provider/augeas/augeas_spec.rb +++ b/spec/unit/provider/augeas/augeas_spec.rb @@ -744,6 +744,7 @@ describe provider_class do @resource[:context] = "/files/etc/test" @resource[:load_path] = my_fixture_dir + @provider.expects(:print_load_errors).with(:warning => true) aug = @provider.open_augeas aug.should_not == nil aug.match("/files/etc/fstab").should == [] @@ -754,6 +755,7 @@ describe provider_class do it "should load standard files if context isn't specific" do @resource[:context] = "/files/etc" + @provider.expects(:print_load_errors).with(:warning => false) aug = @provider.open_augeas aug.should_not == nil aug.match("/files/etc/fstab").should == ["/files/etc/fstab"] @@ -763,6 +765,7 @@ describe provider_class do it "should not optimise if the context is a complex path" do @resource[:context] = "/files/*[label()='etc']" + @provider.expects(:print_load_errors).with(:warning => false) aug = @provider.open_augeas aug.should_not == nil aug.match("/files/etc/fstab").should == ["/files/etc/fstab"] diff --git a/spec/unit/provider/cron/crontab_spec.rb b/spec/unit/provider/cron/crontab_spec.rb index 68f7b22ff..512714a0a 100755 --- a/spec/unit/provider/cron/crontab_spec.rb +++ b/spec/unit/provider/cron/crontab_spec.rb @@ -8,10 +8,15 @@ describe Puppet::Type.type(:cron).provider(:crontab) do provider end + def compare_crontab_text(have, want) + # We should have four header lines, and then the text... + have.lines.to_a[0..3].should be_all {|x| x =~ /^# / } + have.lines.to_a[4..-1].join('').should == want + end + context "with the simple samples" do FIELDS = { :crontab => %w{command minute hour month monthday weekday}.collect { |o| o.intern }, - :freebsd_special => %w{special command}.collect { |o| o.intern }, :environment => [:line], :blank => [:line], :comment => [:line], @@ -28,12 +33,6 @@ describe Puppet::Type.type(:cron).provider(:crontab) do end end - def compare_crontab_text(have, want) - # We should have four header lines, and then the text... - have.lines.to_a[0..3].should be_all {|x| x =~ /^# / } - have.lines.to_a[4..-1].join('').should == want - end - ######################################################################## # Simple input fixtures for testing. samples = YAML.load(File.read(my_fixture('single_line.yaml'))) @@ -113,4 +112,95 @@ describe Puppet::Type.type(:cron).provider(:crontab) do end end end + + context "when receiving a vixie cron header from the cron interface" do + it "should not write that header back to disk" do + vixie_header = File.read(my_fixture('vixie_header.txt')) + vixie_records = subject.parse(vixie_header) + compare_crontab_text subject.to_file(vixie_records), "" + end + end + + context "when adding a cronjob with the same command as an existing job" do + let(:record) { {:name => "existing", :user => "root", :command => "/bin/true", :record_type => :crontab} } + let(:resource) { Puppet::Type::Cron.new(:name => "test", :user => "root", :command => "/bin/true") } + let(:resources) { { "test" => resource } } + + before :each do + subject.stubs(:prefetch_all_targets).returns([record]) + end + +# this would be a more fitting test, but I haven't yet +# figured out how to get it working +# it "should include both jobs in the output" do +# subject.prefetch(resources) +# class Puppet::Provider::ParsedFile +# def self.records +# @records +# end +# end +# subject.to_file(subject.records).should match /Puppet name: test/ +# end + + it "should not base the new resource's provider on the existing record" do + subject.expects(:new).with(record).never + subject.stubs(:new) + subject.prefetch(resources) + end + end + + context "when prefetching an entry now managed for another user" do + let(:resource) do + s = stub(:resource) + s.stubs(:[]).with(:user).returns 'root' + s + end + + let(:record) { {:name => "test", :user => "nobody", :command => "/bin/true", :record_type => :crontab} } + let(:resources) { { "test" => resource } } + + before :each do + subject.stubs(:prefetch_all_targets).returns([record]) + end + + it "should try and use the match method to find a more fitting record" do + subject.expects(:match).with(record, resources) + subject.prefetch(resources) + end + + it "should not match a provider to the resource" do + resource.expects(:provider=).never + subject.prefetch(resources) + end + + it "should not find the resource when looking up the on-disk record" do + subject.prefetch(resources) + subject.resource_for_record(record, resources).should be_nil + end + end + + context "when matching resources to existing crontab entries" do + let(:first_resource) { Puppet::Type::Cron.new(:name => :one, :user => 'root', :command => '/bin/true') } + let(:second_resource) { Puppet::Type::Cron.new(:name => :two, :user => 'nobody', :command => '/bin/false') } + + let(:resources) {{:one => first_resource, :two => second_resource}} + + describe "with a record with a matching name and mismatching user (#2251)" do + # Puppet::Resource objects have #should defined on them, so in these + # examples we have to use the monkey patched `must` alias for the rspec + # `should` method. + + it "doesn't match the record to the resource" do + record = {:name => :one, :user => 'notroot', :record_type => :crontab} + subject.resource_for_record(record, resources).must be_nil + end + end + + describe "with a record with a matching name and matching user" do + it "matches the record to the resource" do + record = {:name => :two, :target => 'nobody', :command => '/bin/false'} + subject.resource_for_record(record, resources).must == second_resource + end + end + end end diff --git a/spec/unit/provider/cron/parsed_spec.rb b/spec/unit/provider/cron/parsed_spec.rb new file mode 100644 index 000000000..a96fa7ed8 --- /dev/null +++ b/spec/unit/provider/cron/parsed_spec.rb @@ -0,0 +1,325 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe Puppet::Type.type(:cron).provider(:crontab) do + + let :provider do + described_class.new(:command => '/bin/true') + end + + let :resource do + Puppet::Type.type(:cron).new( + :minute => %w{0 15 30 45}, + :hour => %w{8-18 20-22}, + :monthday => %w{31}, + :month => %w{12}, + :weekday => %w{7}, + :name => 'basic', + :command => '/bin/true', + :target => 'root', + :provider => provider + ) + end + + let :resource_special do + Puppet::Type.type(:cron).new( + :special => 'reboot', + :name => 'special', + :command => '/bin/true', + :target => 'nobody' + ) + end + + let :record_special do + { + :record_type => :crontab, + :special => 'reboot', + :command => '/bin/true', + :on_disk => true, + :target => 'nobody' + } + end + + let :record do + { + :record_type => :crontab, + :minute => %w{0 15 30 45}, + :hour => %w{8-18 20-22}, + :monthday => %w{31}, + :month => %w{12}, + :weekday => %w{7}, + :special => :absent, + :command => '/bin/true', + :on_disk => true, + :target => 'root' + } + end + + describe "when determining the correct filetype" do + it "should use the suntab filetype on Solaris" do + Facter.stubs(:value).with(:osfamily).returns 'Solaris' + described_class.filetype.should == Puppet::Util::FileType::FileTypeSuntab + end + + it "should use the aixtab filetype on AIX" do + Facter.stubs(:value).with(:osfamily).returns 'AIX' + described_class.filetype.should == Puppet::Util::FileType::FileTypeAixtab + end + + it "should use the crontab filetype on other platforms" do + Facter.stubs(:value).with(:osfamily).returns 'Not a real operating system family' + described_class.filetype.should == Puppet::Util::FileType::FileTypeCrontab + end + end + + # I'd use ENV.expects(:[]).with('USER') but this does not work because + # ENV["USER"] is evaluated at load time. + describe "when determining the default target" do + it "should use the current user #{ENV['USER']}", :if => ENV['USER'] do + described_class.default_target.should == ENV['USER'] + end + + it "should fallback to root", :unless => ENV['USER'] do + described_class.default_target.should == "root" + end + end + + describe "when parsing a record" do + it "should parse a comment" do + described_class.parse_line("# This is a test").should == { + :record_type => :comment, + :line => "# This is a test", + } + end + + it "should get the resource name of a PUPPET NAME comment" do + described_class.parse_line('# Puppet Name: My Fancy Cronjob').should == { + :record_type => :comment, + :name => 'My Fancy Cronjob', + :line => '# Puppet Name: My Fancy Cronjob', + } + end + + it "should ignore blank lines" do + described_class.parse_line('').should == {:record_type => :blank, :line => ''} + described_class.parse_line(' ').should == {:record_type => :blank, :line => ' '} + described_class.parse_line("\t").should == {:record_type => :blank, :line => "\t"} + described_class.parse_line(" \t ").should == {:record_type => :blank, :line => " \t "} + end + + it "should extract environment assignments" do + # man 5 crontab: MAILTO="" with no value can be used to surpress sending + # mails at all + described_class.parse_line('MAILTO=""').should == {:record_type => :environment, :line => 'MAILTO=""'} + described_class.parse_line('FOO=BAR').should == {:record_type => :environment, :line => 'FOO=BAR'} + described_class.parse_line('FOO_BAR=BAR').should == {:record_type => :environment, :line => 'FOO_BAR=BAR'} + end + + it "should extract a cron entry" do + described_class.parse_line('* * * * * /bin/true').should == { + :record_type => :crontab, + :hour => :absent, + :minute => :absent, + :month => :absent, + :weekday => :absent, + :monthday => :absent, + :special => :absent, + :command => '/bin/true' + } + described_class.parse_line('0,15,30,45 8-18,20-22 31 12 7 /bin/true').should == { + :record_type => :crontab, + :minute => %w{0 15 30 45}, + :hour => %w{8-18 20-22}, + :monthday => %w{31}, + :month => %w{12}, + :weekday => %w{7}, + :special => :absent, + :command => '/bin/true' + } + # A percent sign will cause the rest of the string to be passed as + # standard input and will also act as a newline character. Not sure + # if puppet should convert % to a \n as the command property so the + # test covers the current behaviour: Do not do any conversions + described_class.parse_line('0 22 * * 1-5 mail -s "It\'s 10pm" joe%Joe,%%Where are your kids?%').should == { + :record_type => :crontab, + :minute => %w{0}, + :hour => %w{22}, + :monthday => :absent, + :month => :absent, + :weekday => %w{1-5}, + :special => :absent, + :command => 'mail -s "It\'s 10pm" joe%Joe,%%Where are your kids?%' + } + end + + describe "it should support special strings" do + ['reboot','yearly','anually','monthly', 'weekly', 'daily', 'midnight', 'hourly'].each do |special| + it "should support @#{special}" do + described_class.parse_line("@#{special} /bin/true").should == { + :record_type => :crontab, + :hour => :absent, + :minute => :absent, + :month => :absent, + :weekday => :absent, + :monthday => :absent, + :special => special, + :command => '/bin/true' + } + end + end + end + end + + describe ".instances" do + before :each do + described_class.stubs(:default_target).returns 'foobar' + end + + describe "on linux" do + before do + Facter.stubs(:value).with(:osfamily).returns 'Linux' + Facter.stubs(:value).with(:operatingsystem) + end + + it "should be empty if user has no crontab" do + # `crontab...` does only capture stdout here. On vixie-cron-4.1 + # STDERR shows "no crontab for foobar" but stderr is ignored as + # well as the exitcode. + described_class.target_object('foobar').expects(:`).with('crontab -u foobar -l 2>/dev/null').returns "" + described_class.instances.should be_empty + end + + it "should be empty if user is not present" do + # `crontab...` does only capture stdout. On vixie-cron-4.1 + # STDERR shows "crontab: user `foobar' unknown" but stderr is + # ignored as well as the exitcode + described_class.target_object('foobar').expects(:`).with('crontab -u foobar -l 2>/dev/null').returns "" + described_class.instances.should be_empty + end + + it "should be able to create records from not-managed records" do + described_class.expects(:target_object).returns File.new(my_fixture('simple')) + described_class.instances.map do |p| + h = {:name => p.get(:name)} + Puppet::Type.type(:cron).validproperties.each do |property| + h[property] = p.get(property) + end + h + end.should == [ + { + :name => :absent, + :minute => ['5'], + :hour => ['0'], + :weekday => :absent, + :month => :absent, + :monthday => :absent, + :special => :absent, + :command => '$HOME/bin/daily.job >> $HOME/tmp/out 2>&1', + :ensure => :present, + :environment => :absent, + :user => :absent, + :target => 'foobar' + }, + { + :name => :absent, + :minute => ['15'], + :hour => ['14'], + :weekday => :absent, + :month => :absent, + :monthday => ['1'], + :special => :absent, + :command => '$HOME/bin/monthly', + :ensure => :present, + :environment => :absent, + :user => :absent, + :target => 'foobar' + } + ] + end + + it "should be able to parse puppet manged cronjobs" do + described_class.expects(:target_object).returns File.new(my_fixture('managed')) + described_class.instances.map do |p| + h = {:name => p.get(:name)} + Puppet::Type.type(:cron).validproperties.each do |property| + h[property] = p.get(property) + end + h + end.should == [ + { + :name => 'real_job', + :minute => :absent, + :hour => :absent, + :weekday => :absent, + :month => :absent, + :monthday => :absent, + :special => :absent, + :command => '/bin/true', + :ensure => :present, + :environment => :absent, + :user => :absent, + :target => 'foobar' + }, + { + :name => 'complex_job', + :minute => :absent, + :hour => :absent, + :weekday => :absent, + :month => :absent, + :monthday => :absent, + :special => 'reboot', + :command => '/bin/true >> /dev/null 2>&1', + :ensure => :present, + :environment => [ + 'MAILTO=foo@example.com', + 'SHELL=/bin/sh' + ], + :user => :absent, + :target => 'foobar' + } + ] + end + end + end + + describe ".match" do + describe "normal records" do + it "should match when all fields are the same" do + described_class.match(record,{resource[:name] => resource}).must == resource + end + + { + :minute => %w{0 15 31 45}, + :hour => %w{8-18}, + :monthday => %w{30 31}, + :month => %w{12 23}, + :weekday => %w{4}, + :command => '/bin/false', + :target => 'nobody' + }.each_pair do |field, new_value| + it "should not match a record when #{field} does not match" do + record[field] = new_value + described_class.match(record,{resource[:name] => resource}).must be_false + end + end + end + + describe "special records" do + it "should match when all fields are the same" do + described_class.match(record_special,{resource_special[:name] => resource_special}).must == resource_special + end + + { + :special => 'monthly', + :command => '/bin/false', + :target => 'root' + }.each_pair do |field, new_value| + it "should not match a record when #{field} does not match" do + record_special[field] = new_value + described_class.match(record_special,{resource_special[:name] => resource_special}).must be_false + end + end + end + end +end diff --git a/spec/unit/provider/exec/posix_spec.rb b/spec/unit/provider/exec/posix_spec.rb index 5bbc91d7d..2a4813f89 100755 --- a/spec/unit/provider/exec/posix_spec.rb +++ b/spec/unit/provider/exec/posix_spec.rb @@ -86,7 +86,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do end it "should not be able to execute shell builtins" do - provider.resource[:path] = [''] + provider.resource[:path] = ['/bogus/bin'] expect { provider.run("cd ..") }.to raise_error(ArgumentError, "Could not find command 'cd'") end diff --git a/spec/unit/provider/group/groupadd_spec.rb b/spec/unit/provider/group/groupadd_spec.rb index ec9777b1f..4e6141af3 100755 --- a/spec/unit/provider/group/groupadd_spec.rb +++ b/spec/unit/provider/group/groupadd_spec.rb @@ -6,22 +6,27 @@ describe Puppet::Type.type(:group).provider(:groupadd) do described_class.stubs(:command).with(:add).returns '/usr/sbin/groupadd' described_class.stubs(:command).with(:delete).returns '/usr/sbin/groupdel' described_class.stubs(:command).with(:modify).returns '/usr/sbin/groupmod' + described_class.stubs(:command).with(:localadd).returns '/usr/sbin/lgroupadd' end let(:resource) { Puppet::Type.type(:group).new(:name => 'mygroup', :provider => provider) } let(:provider) { described_class.new(:name => 'mygroup') } describe "#create" do + before do + provider.stubs(:exists?).returns(false) + end + it "should add -o when allowdupe is enabled and the group is being created" do resource[:allowdupe] = :true - provider.expects(:execute).with(['/usr/sbin/groupadd', '-o', 'mygroup']) + provider.expects(:execute).with(['/usr/sbin/groupadd', '-o', 'mygroup'], kind_of(Hash)) provider.create end describe "on system that feature system_groups", :if => described_class.system_groups? do it "should add -r when system is enabled and the group is being created" do resource[:system] = :true - provider.expects(:execute).with(['/usr/sbin/groupadd', '-r', 'mygroup']) + provider.expects(:execute).with(['/usr/sbin/groupadd', '-r', 'mygroup'], kind_of(Hash)) provider.create end end @@ -29,10 +34,35 @@ describe Puppet::Type.type(:group).provider(:groupadd) do describe "on system that do not feature system_groups", :unless => described_class.system_groups? do it "should not add -r when system is enabled and the group is being created" do resource[:system] = :true - provider.expects(:execute).with(['/usr/sbin/groupadd', 'mygroup']) + provider.expects(:execute).with(['/usr/sbin/groupadd', 'mygroup'], kind_of(Hash)) + provider.create + end + end + + describe "on systems with the libuser and forcelocal=true" do + before do + described_class.has_feature(:libuser) + resource[:forcelocal] = :true + end + + it "should use lgroupadd instead of groupadd" do + provider.expects(:execute).with(includes('/usr/sbin/lgroupadd'), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) + provider.create + end + + it "should NOT pass -o to lgroupadd" do + resource[:allowdupe] = :true + provider.expects(:execute).with(Not(includes('-o')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end + + it "should raise an exception for duplicate GID if allowdupe is not set and duplicate GIDs exist" do + resource[:gid] = 505 + provider.stubs(:findgroup).returns(true) + lambda { provider.create }.should raise_error(Puppet::Error, "GID 505 already exists, use allowdupe to force group creation") + end end + end describe "#gid=" do diff --git a/spec/unit/provider/group/pw_spec.rb b/spec/unit/provider/group/pw_spec.rb index e3c0c14fb..5d7d470e0 100755 --- a/spec/unit/provider/group/pw_spec.rb +++ b/spec/unit/provider/group/pw_spec.rb @@ -21,31 +21,31 @@ describe provider_class do it "should run pw with no additional flags when no properties are given" do provider.addcmd.must == [provider_class.command(:pw), "groupadd", "testgroup"] - provider.expects(:execute).with([provider_class.command(:pw), "groupadd", "testgroup"]) + provider.expects(:execute).with([provider_class.command(:pw), "groupadd", "testgroup"], kind_of(Hash)) provider.create end it "should use -o when allowdupe is enabled" do resource[:allowdupe] = true - provider.expects(:execute).with(includes("-o")) + provider.expects(:execute).with(includes("-o"), kind_of(Hash)) provider.create end it "should use -g with the correct argument when the gid property is set" do resource[:gid] = 12345 - provider.expects(:execute).with(all_of(includes("-g"), includes(12345))) + provider.expects(:execute).with(all_of(includes("-g"), includes(12345)), kind_of(Hash)) provider.create end it "should use -M with the correct argument when the members property is set" do resource[:members] = "user1" - provider.expects(:execute).with(all_of(includes("-M"), includes("user1"))) + provider.expects(:execute).with(all_of(includes("-M"), includes("user1")), kind_of(Hash)) provider.create end it "should use -M with all the given users when the members property is set to an array" do resource[:members] = ["user1", "user2"] - provider.expects(:execute).with(all_of(includes("-M"), includes("user1,user2"))) + provider.expects(:execute).with(all_of(includes("-M"), includes("user1,user2")), kind_of(Hash)) provider.create end end diff --git a/spec/unit/provider/nameservice_spec.rb b/spec/unit/provider/nameservice_spec.rb new file mode 100755 index 000000000..c79f7bdfe --- /dev/null +++ b/spec/unit/provider/nameservice_spec.rb @@ -0,0 +1,304 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'puppet/provider/nameservice' +require 'etc' + +describe Puppet::Provider::NameService do + + before :each do + described_class.initvars + described_class.resource_type = faketype + end + + # These are values getpwent might give you + let :users do + [ + Struct::Passwd.new('root', 'x', 0, 0), + Struct::Passwd.new('foo', 'x', 1000, 2000), + nil + ] + end + + # These are values getgrent might give you + let :groups do + [ + Struct::Group.new('root', 'x', 0, %w{root}), + Struct::Group.new('bin', 'x', 1, %w{root bin daemon}), + nil + ] + end + + # A fake struct besides Struct::Group and Struct::Passwd + let :fakestruct do + Struct.new(:foo, :bar) + end + + # A fake value get<foo>ent might return + let :fakeetcobject do + fakestruct.new('fooval', 'barval') + end + + # The provider sometimes relies on @resource for valid properties so let's + # create a fake type with properties that match our fake struct. + let :faketype do + Puppet::Type.newtype(:nameservice_dummytype) do + newparam(:name) + ensurable + newproperty(:foo) + newproperty(:bar) + end + end + + let :provider do + described_class.new(:name => 'bob', :foo => 'fooval', :bar => 'barval') + end + + let :resource do + resource = faketype.new(:name => 'bob', :ensure => :present) + resource.provider = provider + resource + end + + describe "#options" do + it "should add options for a valid property" do + described_class.options :foo, :key1 => 'val1', :key2 => 'val2' + described_class.options :bar, :key3 => 'val3' + described_class.option(:foo, :key1).should == 'val1' + described_class.option(:foo, :key2).should == 'val2' + described_class.option(:bar, :key3).should == 'val3' + end + + it "should raise an error for an invalid property" do + expect { described_class.options :baz, :key1 => 'val1' }.to raise_error( + Puppet::Error, 'baz is not a valid attribute for nameservice_dummytype') + end + end + + describe "#option" do + it "should return the correct value" do + described_class.options :foo, :key1 => 'val1', :key2 => 'val2' + described_class.option(:foo, :key2).should == 'val2' + end + + it "should symbolize the name first" do + described_class.options :foo, :key1 => 'val1', :key2 => 'val2' + described_class.option('foo', :key2).should == 'val2' + end + + it "should return nil if no option has been specified earlier" do + described_class.option(:foo, :key2).should be_nil + end + + it "should return nil if no option for that property has been specified earlier" do + described_class.options :bar, :key2 => 'val2' + described_class.option(:foo, :key2).should be_nil + end + + it "should return nil if no matching key can be found for that property" do + described_class.options :foo, :key3 => 'val2' + described_class.option(:foo, :key2).should be_nil + end + end + + describe "#section" do + it "should raise an error if resource_type has not been set" do + described_class.expects(:resource_type).returns nil + expect { described_class.section }.to raise_error Puppet::Error, 'Cannot determine Etc section without a resource type' + end + + # the return values are hard coded so I am using types that actually make + # use of the nameservice provider + it "should return pw for users" do + described_class.resource_type = Puppet::Type.type(:user) + described_class.section.should == 'pw' + end + + it "should return gr for groups" do + described_class.resource_type = Puppet::Type.type(:group) + described_class.section.should == 'gr' + end + end + + describe "#listbyname" do + it "should return a list of users if resource_type is user" do + described_class.resource_type = Puppet::Type.type(:user) + Etc.expects(:setpwent) + Etc.stubs(:getpwent).returns *users + Etc.expects(:endpwent) + described_class.listbyname.should == %w{root foo} + end + + it "should return a list of groups if resource_type is group", :unless => Puppet.features.microsoft_windows? do + described_class.resource_type = Puppet::Type.type(:group) + Etc.expects(:setgrent) + Etc.stubs(:getgrent).returns *groups + Etc.expects(:endgrent) + described_class.listbyname.should == %w{root bin} + end + + it "should yield if a block given" do + yield_results = [] + described_class.resource_type = Puppet::Type.type(:user) + Etc.expects(:setpwent) + Etc.stubs(:getpwent).returns *users + Etc.expects(:endpwent) + described_class.listbyname {|x| yield_results << x } + yield_results.should == %w{root foo} + end + end + + describe "instances" do + it "should return a list of objects based on listbyname" do + described_class.expects(:listbyname).multiple_yields 'root', 'foo', 'nobody' + described_class.instances.map(&:name).should == %w{root foo nobody} + end + end + + describe "validate" do + it "should pass if no check is registered at all" do + expect { described_class.validate(:foo, 300) }.to_not raise_error + expect { described_class.validate('foo', 300) }.to_not raise_error + end + + it "should pass if no check for that property is registered" do + described_class.verify(:bar, 'Must be 100') { |val| val == 100 } + expect { described_class.validate(:foo, 300) }.to_not raise_error + expect { described_class.validate('foo', 300) }.to_not raise_error + end + + it "should pass if the value is valid" do + described_class.verify(:foo, 'Must be 100') { |val| val == 100 } + expect { described_class.validate(:foo, 100) }.to_not raise_error + expect { described_class.validate('foo', 100) }.to_not raise_error + end + + it "should raise an error if the value is invalid" do + described_class.verify(:foo, 'Must be 100') { |val| val == 100 } + expect { described_class.validate(:foo, 200) }.to raise_error(ArgumentError, 'Invalid value 200: Must be 100') + expect { described_class.validate('foo', 200) }.to raise_error(ArgumentError, 'Invalid value 200: Must be 100') + end + end + + describe "getinfo" do + before :each do + # with section=foo we'll call Etc.getfoonam instead of getpwnam or getgrnam + described_class.stubs(:section).returns 'foo' + resource # initialize the resource so our provider has a @resource instance variable + end + + it "should return a hash if we can retrieve something" do + Etc.expects(:send).with(:getfoonam, 'bob').returns fakeetcobject + provider.expects(:info2hash).with(fakeetcobject).returns(:foo => 'fooval', :bar => 'barval') + provider.getinfo(true).should == {:foo => 'fooval', :bar => 'barval'} + end + + it "should return nil if we cannot retrieve anything" do + Etc.expects(:send).with(:getfoonam, 'bob').raises(ArgumentError, "can't find bob") + provider.expects(:info2hash).never + provider.getinfo(true).should be_nil + end + end + + describe "info2hash" do + it "should return a hash with all properties" do + # we have to have an implementation of posixmethod which has to + # convert a propertyname (e.g. comment) into a fieldname of our + # Struct (e.g. gecos). I do not want to test posixmethod here so + # let's fake an implementation which does not do any translation. We + # expect two method invocations because info2hash calls the method + # twice if the Struct responds to the propertyname (our fake Struct + # provides values for :foo and :bar) TODO: Fix that + provider.expects(:posixmethod).with(:foo).returns(:foo).twice + provider.expects(:posixmethod).with(:bar).returns(:bar).twice + provider.expects(:posixmethod).with(:ensure).returns :ensure + provider.info2hash(fakeetcobject).should == { :foo => 'fooval', :bar => 'barval' } + end + end + + describe "munge" do + it "should return the input value if no munge method has be defined" do + provider.munge(:foo, 100).should == 100 + end + + it "should return the munged value otherwise" do + described_class.options(:foo, :munge => proc { |x| x*2 }) + provider.munge(:foo, 100).should == 200 + end + end + + describe "unmunge" do + it "should return the input value if no unmunge method has been defined" do + provider.unmunge(:foo, 200).should == 200 + end + + it "should return the unmunged value otherwise" do + described_class.options(:foo, :unmunge => proc { |x| x/2 }) + provider.unmunge(:foo, 200).should == 100 + end + end + + + describe "exists?" do + it "should return true if we can retrieve anything" do + provider.expects(:getinfo).with(true).returns(:foo => 'fooval', :bar => 'barval') + provider.should be_exists + end + it "should return false if we cannot retrieve anything" do + provider.expects(:getinfo).with(true).returns nil + provider.should_not be_exists + end + end + + describe "get" do + before(:each) {described_class.resource_type = faketype } + + it "should return the correct getinfo value" do + provider.expects(:getinfo).with(false).returns(:foo => 'fooval', :bar => 'barval') + provider.get(:bar).should == 'barval' + end + + it "should unmunge the value first" do + described_class.options(:bar, :munge => proc { |x| x*2}, :unmunge => proc {|x| x/2}) + provider.expects(:getinfo).with(false).returns(:foo => 200, :bar => 500) + provider.get(:bar).should == 250 + end + + it "should return nil if getinfo cannot retrieve the value" do + provider.expects(:getinfo).with(false).returns(:foo => 'fooval', :bar => 'barval') + provider.get(:no_such_key).should be_nil + end + + end + + describe "set" do + before :each do + resource # initialize resource so our provider has a @resource object + described_class.verify(:foo, 'Must be 100') { |val| val == 100 } + end + + it "should raise an error on invalid values" do + expect { provider.set(:foo, 200) }.to raise_error(ArgumentError, 'Invalid value 200: Must be 100') + end + + it "should execute the modify command on valid values" do + provider.expects(:modifycmd).with(:foo, 100).returns ['/bin/modify', '-f', '100' ] + provider.expects(:execute).with ['/bin/modify', '-f', '100'] + provider.set(:foo, 100) + end + + it "should munge the value first" do + described_class.options(:foo, :munge => proc { |x| x*2}, :unmunge => proc {|x| x/2}) + provider.expects(:modifycmd).with(:foo, 200).returns ['/bin/modify', '-f', '200' ] + provider.expects(:execute).with ['/bin/modify', '-f', '200'] + provider.set(:foo, 100) + end + + it "should fail if the modify command fails" do + provider.expects(:modifycmd).with(:foo, 100).returns ['/bin/modify', '-f', '100' ] + provider.expects(:execute).with(['/bin/modify', '-f', '100']).raises(Puppet::ExecutionFailure, "Execution of '/bin/modify' returned 1: some_failure") + expect { provider.set(:foo, 100) }.to raise_error Puppet::Error, /Could not set foo/ + end + end + +end diff --git a/spec/unit/provider/package/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb index ddba34325..ceae70cfb 100755 --- a/spec/unit/provider/package/aix_spec.rb +++ b/spec/unit/provider/package/aix_spec.rb @@ -6,18 +6,9 @@ provider_class = Puppet::Type.type(:package).provider(:aix) describe provider_class do before(:each) do # Create a mock resource - @resource = stub 'resource' + @resource = Puppet::Type.type(:package).new(:name => 'mypackage', :ensure => :installed, :source => 'mysource', :provider => :aix) - # A catch all; no parameters set - @resource.stubs(:[]).returns(nil) - - # But set name and source - @resource.stubs(:[]).with(:name).returns "mypackage" - @resource.stubs(:[]).with(:source).returns "mysource" - @resource.stubs(:[]).with(:ensure).returns :installed - - @provider = provider_class.new - @provider.resource = @resource + @provider = @resource.provider end [:install, :uninstall, :latest, :query, :update].each do |method| @@ -28,6 +19,7 @@ describe provider_class do it "should uninstall a package" do @provider.expects(:installp).with('-gu', 'mypackage') + @provider.class.expects(:pkglist).with(:pkgname => 'mypackage').returns(nil) @provider.uninstall end @@ -43,6 +35,56 @@ describe provider_class do @provider.expects(:installp).with('-acgwXY', '-d', 'mysource', 'mypackage 1.2.3.4') @provider.install end + + it "should fail if the specified version is superseded" do + @resource[:ensure] = '1.2.3.3' + @provider.stubs(:installp).returns <<-OUTPUT ++-----------------------------------------------------------------------------+ + Pre-installation Verification... ++-----------------------------------------------------------------------------+ +Verifying selections...done +Verifying requisites...done +Results... + +WARNINGS +-------- + Problems described in this section are not likely to be the source of any + immediate or serious failures, but further actions may be necessary or + desired. + + Already Installed + ----------------- + The number of selected filesets that are either already installed + or effectively installed through superseding filesets is 1. See + the summaries at the end of this installation for details. + + NOTE: Base level filesets may be reinstalled using the "Force" + option (-F flag), or they may be removed, using the deinstall or + "Remove Software Products" facility (-u flag), and then reinstalled. + + << End of Warning Section >> + ++-----------------------------------------------------------------------------+ + BUILDDATE Verification ... ++-----------------------------------------------------------------------------+ +Verifying build dates...done +FILESET STATISTICS +------------------ + 1 Selected to be installed, of which: + 1 Already installed (directly or via superseding filesets) + ---- + 0 Total to be installed + + +Pre-installation Failure/Warning Summary +---------------------------------------- +Name Level Pre-installation Failure/Warning +------------------------------------------------------------------------------- +mypackage 1.2.3.3 Already superseded by 1.2.3.4 + OUTPUT + + expect { @provider.install }.to raise_error(Puppet::Error, "aix package provider is unable to downgrade packages") + end end describe "when finding the latest version" do diff --git a/spec/unit/provider/package/aptrpm_spec.rb b/spec/unit/provider/package/aptrpm_spec.rb index 90885303d..bc07c60c9 100755 --- a/spec/unit/provider/package/aptrpm_spec.rb +++ b/spec/unit/provider/package/aptrpm_spec.rb @@ -10,6 +10,12 @@ describe Puppet::Type.type(:package).provider(:aptrpm) do it { should be_versionable } context "when retrieving ensure" do + before(:each) do + Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") + pkg.provider.stubs(:which).with("rpm").returns("/bin/rpm") + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:combine => true, :custom_environment => {}, :failonfail => true}).returns("4.10.1\n").at_most_once + end + def rpm pkg.provider.expects(:rpm). with('-q', 'faff', '--nosignature', '--nodigest', '--qf', diff --git a/spec/unit/provider/package/gem_spec.rb b/spec/unit/provider/package/gem_spec.rb index 6f79b806f..02cb2cabd 100755 --- a/spec/unit/provider/package/gem_spec.rb +++ b/spec/unit/provider/package/gem_spec.rb @@ -29,23 +29,18 @@ describe provider_class do provider.install end - it "should specify that dependencies should be included" do - provider.expects(:execute).with { |args| args[2] == "--include-dependencies" }.returns "" - provider.install - end - it "should specify that documentation should not be included" do - provider.expects(:execute).with { |args| args[3] == "--no-rdoc" }.returns "" + provider.expects(:execute).with { |args| args[2] == "--no-rdoc" }.returns "" provider.install end it "should specify that RI should not be included" do - provider.expects(:execute).with { |args| args[4] == "--no-ri" }.returns "" + provider.expects(:execute).with { |args| args[3] == "--no-ri" }.returns "" provider.install end it "should specify the package name" do - provider.expects(:execute).with { |args| args[5] == "myresource" }.returns "" + provider.expects(:execute).with { |args| args[4] == "myresource" }.returns "" provider.install end @@ -53,14 +48,14 @@ describe provider_class do describe "as a normal file" do it "should use the file name instead of the gem name" do resource[:source] = "/my/file" - provider.expects(:execute).with { |args| args[3] == "/my/file" }.returns "" + provider.expects(:execute).with { |args| args[2] == "/my/file" }.returns "" provider.install end end describe "as a file url" do it "should use the file name instead of the gem name" do resource[:source] = "file:///my/file" - provider.expects(:execute).with { |args| args[3] == "/my/file" }.returns "" + provider.expects(:execute).with { |args| args[2] == "/my/file" }.returns "" provider.install end end @@ -73,7 +68,7 @@ describe provider_class do describe "as a non-file and non-puppet url" do it "should treat the source as a gem repository" do resource[:source] = "http://host/my/file" - provider.expects(:execute).with { |args| args[3..5] == ["--source", "http://host/my/file", "myresource"] }.returns "" + provider.expects(:execute).with { |args| args[2..4] == ["--source", "http://host/my/file", "myresource"] }.returns "" provider.install end end diff --git a/spec/unit/provider/package/nim_spec.rb b/spec/unit/provider/package/nim_spec.rb index 31fc53090..95754fcfc 100755 --- a/spec/unit/provider/package/nim_spec.rb +++ b/spec/unit/provider/package/nim_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' provider_class = Puppet::Type.type(:package).provider(:nim) describe provider_class do + before(:each) do # Create a mock resource @resource = stub 'resource' @@ -12,7 +13,7 @@ describe provider_class do @resource.stubs(:[]).returns(nil) # But set name and source - @resource.stubs(:[]).with(:name).returns "mypackage" + @resource.stubs(:[]).with(:name).returns "mypackage.foo" @resource.stubs(:[]).with(:source).returns "mysource" @resource.stubs(:[]).with(:ensure).returns :installed @@ -25,17 +26,225 @@ describe provider_class do @provider.should respond_to(:install) end - describe "when installing" do + let(:bff_showres_output) { + <<END +mypackage.foo ALL @@I:mypackage.foo _all_filesets + @ 1.2.3.1 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.1 + + 1.2.3.4 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.4 + + 1.2.3.8 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.8 + +END + } + + let(:rpm_showres_output) { + <<END +mypackage.foo ALL @@R:mypackage.foo _all_filesets + @@R:mypackage.foo-1.2.3-1 1.2.3-1 + @@R:mypackage.foo-1.2.3-4 1.2.3-4 + @@R:mypackage.foo-1.2.3-8 1.2.3-8 + +END + } + + context "when installing" do it "should install a package" do + @resource.stubs(:should).with(:ensure).returns(:installed) - @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets='mypackage'") + Puppet::Util.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo'").returns(bff_showres_output) + @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo 1.2.3.8") @provider.install end - it "should install a versioned package" do - @resource.stubs(:should).with(:ensure).returns("1.2.3.4") - @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets='mypackage 1.2.3.4'") - @provider.install + context "when installing versioned packages" do + + it "should fail if the package is not available on the lpp source" do + nimclient_showres_output = "" + + @resource.stubs(:should).with(:ensure).returns("1.2.3.4") + Puppet::Util.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\.4'").returns(nimclient_showres_output) + expect { + @provider.install + }.to raise_error(Puppet::Error, "Unable to find package 'mypackage.foo' with version '1.2.3.4' on lpp_source 'mysource'") + end + + it "should succeed if a BFF/installp package is available on the lpp source" do + nimclient_sequence = sequence('nimclient') + + @resource.stubs(:should).with(:ensure).returns("1.2.3.4") + Puppet::Util.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\.4'").returns(bff_showres_output).in_sequence(nimclient_sequence) + @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo 1.2.3.4").in_sequence(nimclient_sequence) + @provider.install + end + + it "should fail if the specified version of a BFF package is superseded" do + nimclient_sequence = sequence('nimclient') + + install_output = <<OUTPUT ++-----------------------------------------------------------------------------+ + Pre-installation Verification... ++-----------------------------------------------------------------------------+ +Verifying selections...done +Verifying requisites...done +Results... + +WARNINGS +-------- + Problems described in this section are not likely to be the source of any + immediate or serious failures, but further actions may be necessary or + desired. + + Already Installed + ----------------- + The number of selected filesets that are either already installed + or effectively installed through superseding filesets is 1. See + the summaries at the end of this installation for details. + + NOTE: Base level filesets may be reinstalled using the "Force" + option (-F flag), or they may be removed, using the deinstall or + "Remove Software Products" facility (-u flag), and then reinstalled. + + << End of Warning Section >> + ++-----------------------------------------------------------------------------+ + BUILDDATE Verification ... ++-----------------------------------------------------------------------------+ +Verifying build dates...done +FILESET STATISTICS +------------------ + 1 Selected to be installed, of which: + 1 Already installed (directly or via superseding filesets) + ---- + 0 Total to be installed + + +Pre-installation Failure/Warning Summary +---------------------------------------- +Name Level Pre-installation Failure/Warning +------------------------------------------------------------------------------- +mypackage.foo 1.2.3.1 Already superseded by 1.2.3.4 +OUTPUT + + @resource.stubs(:should).with(:ensure).returns("1.2.3.1") + Puppet::Util.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\.1'").returns(bff_showres_output).in_sequence(nimclient_sequence) + @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo 1.2.3.1").in_sequence(nimclient_sequence).returns(install_output) + + expect { @provider.install }.to raise_error(Puppet::Error, "NIM package provider is unable to downgrade packages") + end + + + it "should succeed if an RPM package is available on the lpp source" do + nimclient_sequence = sequence('nimclient') + + @resource.stubs(:should).with(:ensure).returns("1.2.3-4") + Puppet::Util.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\-4'").returns(rpm_showres_output).in_sequence(nimclient_sequence) + @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo-1.2.3-4").in_sequence(nimclient_sequence) + @provider.install + end + end + + it "should fail if the specified version of a RPM package is superseded" do + nimclient_sequence = sequence('nimclient') + + install_output = <<OUTPUT + + +Validating RPM package selections ... + +Please wait... ++-----------------------------------------------------------------------------+ + RPM Error Summary: ++-----------------------------------------------------------------------------+ +The following RPM packages were requested for installation +but they are already installed or superseded by a package installed +at a higher level: +mypackage.foo-1.2.3-1 is superseded by mypackage.foo-1.2.3-4 + + +OUTPUT + + @resource.stubs(:should).with(:ensure).returns("1.2.3-1") + Puppet::Util.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\-1'").returns(rpm_showres_output).in_sequence(nimclient_sequence) + @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo-1.2.3-1").in_sequence(nimclient_sequence).returns(install_output) + + expect { @provider.install }.to raise_error(Puppet::Error, "NIM package provider is unable to downgrade packages") + end + + + + end + + context "when uninstalling" do + it "should call installp to uninstall a bff package" do + @provider.expects(:lslpp).with("-qLc", "mypackage.foo").returns("#bos.atm:bos.atm.atmle:7.1.2.0: : :C: :ATM LAN Emulation Client Support : : : : : : :0:0:/:1241") + @provider.expects(:installp).with("-gu", "mypackage.foo") + @provider.class.expects(:pkglist).with(:pkgname => 'mypackage.foo').returns(nil) + @provider.uninstall + end + + it "should call rpm to uninstall an rpm package" do + @provider.expects(:lslpp).with("-qLc", "mypackage.foo").returns("cdrecord:cdrecord-1.9-6:1.9-6: : :C:R:A command line CD/DVD recording program.: :/bin/rpm -e cdrecord: : : : :0: :/opt/freeware:Wed Jun 29 09:41:32 PDT 2005") + @provider.expects(:rpm).with("-e", "mypackage.foo") + @provider.class.expects(:pkglist).with(:pkgname => 'mypackage.foo').returns(nil) + @provider.uninstall + end + + end + + + context "when parsing nimclient showres output" do + describe "#parse_showres_output" do + it "should be able to parse installp/BFF package listings" do + packages = subject.send(:parse_showres_output, bff_showres_output) + Set.new(packages.keys).should == Set.new(['mypackage.foo']) + versions = packages['mypackage.foo'] + ['1.2.3.1', '1.2.3.4', '1.2.3.8'].each do |version| + versions.has_key?(version).should == true + versions[version].should == :installp + end + end + + it "should be able to parse RPM package listings" do + packages = subject.send(:parse_showres_output, rpm_showres_output) + Set.new(packages.keys).should == Set.new(['mypackage.foo']) + versions = packages['mypackage.foo'] + ['1.2.3-1', '1.2.3-4', '1.2.3-8'].each do |version| + versions.has_key?(version).should == true + versions[version].should == :rpm + end + end + end + + describe "#determine_latest_version" do + context "when there are multiple versions" do + it "should return the latest version" do + subject.send(:determine_latest_version, rpm_showres_output, 'mypackage.foo').should == [:rpm, '1.2.3-8'] + end + end + + context "when there is only one version" do + it "should return the type specifier and `nil` for the version number" do + nimclient_showres_output = <<END +mypackage.foo ALL @@R:mypackage.foo _all_filesets + @@R:mypackage.foo-1.2.3-4 1.2.3-4 + +END + subject.send(:determine_latest_version, nimclient_showres_output, 'mypackage.foo').should == [:rpm, nil] + end + end + + end + + describe "#determine_package_type" do + it "should return :rpm for rpm packages" do + subject.send(:determine_package_type, rpm_showres_output, 'mypackage.foo', '1.2.3-4').should == :rpm + end + + it "should return :installp for installp/bff packages" do + subject.send(:determine_package_type, bff_showres_output, 'mypackage.foo', '1.2.3.4').should == :installp + end end end + + + end diff --git a/spec/unit/provider/package/opkg_spec.rb b/spec/unit/provider/package/opkg_spec.rb new file mode 100755 index 000000000..dfe6fed15 --- /dev/null +++ b/spec/unit/provider/package/opkg_spec.rb @@ -0,0 +1,180 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +describe Puppet::Type.type(:package).provider(:opkg) do + + let(:resource) do + Puppet::Type.type(:package).new(:name => 'package') + end + + let(:provider) { described_class.new(resource) } + + before do + Puppet::Util::Execution.stubs(:execute).never + Puppet::Util.stubs(:which).with("opkg").returns("/bin/opkg") + Dir.stubs(:entries).with('/var/opkg-lists/').returns ['.', '..', 'packages'] + end + + describe "when installing" do + before do + provider.stubs(:query).returns({ :ensure => '1.0' }) + end + + context "when the package list is absent" do + before do + Dir.stubs(:entries).with('/var/opkg-lists/').returns ['.', '..'] #empty, no package list + end + + it "fetches the package list when installing" do + provider.expects(:opkg).with('update') + provider.expects(:opkg).with("--force-overwrite", "install", resource[:name]) + + provider.install + end + end + + context "when the package list is present" do + before do + Dir.stubs(:entries).with('/var/opkg-lists/').returns ['.', '..', 'lists'] # With a pre-downloaded package list + end + + it "fetches the package list when installing" do + provider.expects(:opkg).with('update').never + provider.expects(:opkg).with("--force-overwrite", "install", resource[:name]) + + provider.install + end + end + + it "should call opkg install" do + Puppet::Util::Execution.expects(:execute).with(["/bin/opkg", "--force-overwrite", "install", resource[:name]], {:failonfail => true, :combine => true, :custom_environment => {}}) + provider.install + end + + context "when :source is specified" do + context "works on valid urls" do + %w{ + /some/package/file + http://some.package.in/the/air + ftp://some.package.in/the/air + }.each do |source| + it "should install #{source} directly" do + resource[:source] = source + Puppet::Util::Execution.expects(:execute).with(["/bin/opkg", "--force-overwrite", "install", resource[:source]], {:failonfail => true, :combine => true, :custom_environment => {}}) + provider.install + end + end + end + + context "as a file:// URL" do + before do + @package_file = "file:///some/package/file" + @actual_file_path = "/some/package/file" + resource[:source] = @package_file + end + + it "should install from the path segment of the URL" do + Puppet::Util::Execution.expects(:execute).returns("") + provider.install + end + end + + context "with invalid URL for opkg" do + before do + # Emulate the `opkg` command returning a non-zero exit value + Puppet::Util::Execution.stubs(:execute).raises Puppet::ExecutionFailure + end + + context "puppet://server/whatever" do + before do + resource[:source] = "puppet://server/whatever" + end + + it "should fail" do + expect { provider.install }.to raise_error, Puppet::ExecutionFailure + end + end + + context "as a malformed URL" do + before do + resource[:source] = "blah://" + end + + it "should fail" do + expect { provider.install }.to raise_error, Puppet::ExecutionFailure + end + end + end + end # end when source is specified + end # end when installing + + describe "when updating" do + it "should call install" do + provider.expects(:install).returns("install return value") + provider.update.should == "install return value" + end + end + + describe "when uninstalling" do + it "should run opkg remove bla" do + Puppet::Util::Execution.expects(:execute).with(["/bin/opkg", "remove", resource[:name]], {:failonfail => true, :combine => true, :custom_environment => {}}) + provider.uninstall + end + end + + describe "when querying" do + + describe "self.instances" do + let (:packages) do + <<-OPKG_OUTPUT +dropbear - 2011.54-2 +kernel - 3.3.8-1-ba5cdb2523b4fc7722698b4a7ece6702 +uhttpd - 2012-10-30-e57bf6d8bfa465a50eea2c30269acdfe751a46fd +OPKG_OUTPUT + end + it "returns an array of packages" do + Puppet::Util.stubs(:which).with("opkg").returns("/bin/opkg") + described_class.stubs(:which).with("opkg").returns("/bin/opkg") + described_class.expects(:execpipe).with("/bin/opkg list-installed").yields(packages) + + installed_packages = described_class.instances + installed_packages.length.should == 3 + + installed_packages[0].properties.should == + { + :provider => :opkg, + :name => "dropbear", + :ensure => "2011.54-2" + } + installed_packages[1].properties.should == + { + :provider => :opkg, + :name => "kernel", + :ensure => "3.3.8-1-ba5cdb2523b4fc7722698b4a7ece6702" + } + installed_packages[2].properties.should == + { + :provider => :opkg, + :name => "uhttpd", + :ensure => "2012-10-30-e57bf6d8bfa465a50eea2c30269acdfe751a46fd" + } + end + end + + it "should return a nil if the package isn't found" do + Puppet::Util::Execution.expects(:execute).returns("") + provider.query.should be_nil + end + + it "should return a hash indicating that the package is missing on error" do + Puppet::Util::Execution.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!")) + provider.query.should == { + :ensure => :purged, + :status => 'missing', + :name => resource[:name], + :error => 'ok', + } + end + end #end when querying + +end # end describe provider diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb index 3420d6ca6..964175c42 100755 --- a/spec/unit/provider/package/rpm_spec.rb +++ b/spec/unit/provider/package/rpm_spec.rb @@ -11,14 +11,61 @@ describe provider_class do cracklib-dicts 0 2.8.9 3.3 x86_64 basesystem 0 8.0 5.1.1.el5.centos noarch chkconfig 0 1.3.30.2 2.el5 x86_64 + myresource 0 1.2.3.4 5.el4 noarch RPM_OUTPUT end + let(:resource) do + Puppet::Type.type(:package).new( + :name => 'myresource', + :ensure => :installed + ) + end + + let(:provider) do + provider = provider_class.new + provider.resource = resource + provider + end + + let(:rpm_version) { "RPM version 5.0.0\n" } + + before(:each) do + Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") + subject.stubs(:which).with("rpm").returns("/bin/rpm") + subject.instance_variable_set("@current_version", nil) + Puppet::Type::Package::ProviderRpm.expects(:execute).with(["/bin/rpm", "--version"]).returns(rpm_version).at_most_once + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:failonfail => true, :combine => true, :custom_environment => {}}).returns(rpm_version).at_most_once + end + describe "self.instances" do + describe "with a modern version of RPM" do + it "should include all the modern flags" do + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n'").yields(packages) + + installed_packages = subject.instances + end + end + + describe "with a version of RPM < 4.1" do + let(:rpm_version) { "RPM version 4.0.2\n" } + it "should exclude the --nosignature flag" do + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n'").yields(packages) + + installed_packages = subject.instances + end + end + + describe "with a version of RPM < 4.0.2" do + let(:rpm_version) { "RPM version 3.0.5\n" } + it "should exclude the --nodigest flag" do + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n'").yields(packages) + + installed_packages = subject.instances + end + end + it "returns an array of packages" do - Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:failonfail => true, :combine => true, :custom_environment => {}}).returns("RPM version 5.x") - Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") - subject.stubs(:which).with("rpm").returns("/bin/rpm") Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n'").yields(packages) installed_packages = subject.instances @@ -55,4 +102,103 @@ describe provider_class do } end end + + describe "#install" do + let(:resource) do + Puppet::Type.type(:package).new( + :name => 'myresource', + :ensure => :installed, + :source => '/path/to/package' + ) + end + + describe "when not already installed" do + it "should only include the '-i' flag" do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-i", '/path/to/package'], {:failonfail => true, :combine => true, :custom_environment => {}}) + provider.install + end + end + + describe "when an older version is installed" do + before(:each) do + # Force the provider to think a version of the package is already installed + # This is real hacky. I'm sorry. --jeffweiss 25 Jan 2013 + provider.instance_variable_get('@property_hash')[:ensure] = '1.2.3.3' + end + + it "should include the '-U --oldpackage' flags" do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-U", "--oldpackage"], '/path/to/package'], {:failonfail => true, :combine => true, :custom_environment => {}}) + provider.install + end + end + end + + describe "#uninstall" do + let(:resource) do + Puppet::Type.type(:package).new( + :name => 'myresource', + :ensure => :installed + ) + end + + describe "on a modern RPM" do + before(:each) do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", "myresource", '--nosignature', '--nodigest', "--qf", "%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n"], {:failonfail => true, :combine => true, :custom_environment => {}}).returns("myresource 0 1.2.3.4 5.el4 noarch\n") + end + + let(:rpm_version) { "RPM version 4.10.0\n" } + + it "should include the architecture in the package name" do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-e", 'myresource-1.2.3.4-5.el4.noarch'], {:failonfail => true, :combine => true, :custom_environment => {}}).returns('').at_most_once + provider.uninstall + end + end + + describe "on an ancient RPM" do + before(:each) do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", "myresource", '', '', "--qf", "%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n"], {:failonfail => true, :combine => true, :custom_environment => {}}).returns("myresource 0 1.2.3.4 5.el4 noarch\n") + end + + let(:rpm_version) { "RPM version 3.0.6\n" } + + it "should exclude the architecture from the package name" do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-e", 'myresource-1.2.3.4-5.el4'], {:failonfail => true, :combine => true, :custom_environment => {}}).returns('').at_most_once + provider.uninstall + end + end + + end + + describe ".nodigest" do + { '4.0' => nil, + '4.0.1' => nil, + '4.0.2' => '--nodigest', + '4.0.3' => '--nodigest', + '4.1' => '--nodigest', + '5' => '--nodigest', + }.each do |version, expected| + describe "when current version is #{version}" do + it "should return #{expected.inspect}" do + subject.stubs(:current_version).returns(version) + subject.nodigest.should == expected + end + end + end + end + + describe ".nosignature" do + { '4.0.3' => nil, + '4.1' => '--nosignature', + '4.1.1' => '--nosignature', + '4.2' => '--nosignature', + '5' => '--nosignature', + }.each do |version, expected| + describe "when current version is #{version}" do + it "should return #{expected.inspect}" do + subject.stubs(:current_version).returns(version) + subject.nosignature.should == expected + end + end + end + end end diff --git a/spec/unit/provider/package/sun_spec.rb b/spec/unit/provider/package/sun_spec.rb index dac438f92..c6cb7304f 100755 --- a/spec/unit/provider/package/sun_spec.rb +++ b/spec/unit/provider/package/sun_spec.rb @@ -5,14 +5,6 @@ describe Puppet::Type.type(:package).provider(:sun) do let(:resource) { Puppet::Type.type(:package).new(:name => 'dummy', :ensure => :installed, :provider => :sun) } let(:provider) { resource.provider } - before(:each) do - # Stub some provider methods to avoid needing the actual software - # installed, so we can test on whatever platform we want. - provider.class.stubs(:command).with(:pkginfo).returns('/usr/bin/pkginfo') - provider.class.stubs(:command).with(:pkgadd).returns('/usr/sbin/pkgadd') - provider.class.stubs(:command).with(:pkgrm).returns('/usr/sbin/pkgrm') - end - describe 'provider features' do it { should be_installable } it { should be_uninstallable } @@ -35,7 +27,7 @@ describe Puppet::Type.type(:package).provider(:sun) do end it "should install a package if it is not present on update" do - Puppet::Util::Execution.expects(:execute).with(['/usr/bin/pkginfo', '-l', 'dummy'], {:failonfail => false, :combine => false}).returns File.read(my_fixture('dummy.server')) + provider.expects(:pkginfo).with('-l', 'dummy').returns File.read(my_fixture('dummy.server')) provider.expects(:pkgrm).with(['-n', 'dummy']) provider.expects(:install) provider.update @@ -74,7 +66,7 @@ describe Puppet::Type.type(:package).provider(:sun) do context '#query' do it "should find the package on query" do - Puppet::Util::Execution.expects(:execute).with(['/usr/bin/pkginfo', '-l', 'dummy'], {:failonfail => false, :combine => false}).returns File.read(my_fixture('dummy.server')) + provider.expects(:pkginfo).with('-l', 'dummy').returns File.read(my_fixture('dummy.server')) provider.query.should == { :name => 'SUNWdummy', :category=>"system", @@ -87,19 +79,19 @@ describe Puppet::Type.type(:package).provider(:sun) do end it "shouldn't find the package on query if it is not present" do - Puppet::Util::Execution.expects(:execute).with(['/usr/bin/pkginfo', '-l', 'dummy'], {:failonfail => false, :combine => false}).returns 'ERROR: information for "dummy" not found.' + provider.expects(:pkginfo).with('-l', 'dummy').raises Puppet::ExecutionFailure, "Execution of 'pkginfo -l dummy' returned 3: ERROR: information for \"dummy\" not found." provider.query.should == {:ensure => :absent} end it "unknown message should raise error." do - Puppet::Util::Execution.expects(:execute).with(['/usr/bin/pkginfo', '-l', 'dummy'], {:failonfail => false, :combine => false}).returns 'RANDOM' - lambda { provider.query }.should raise_error(Puppet::Error) + provider.expects(:pkginfo).with('-l', 'dummy').returns 'RANDOM' + expect { provider.query }.to raise_error Puppet::Error end end context '#instance' do it "should list instances when there are packages in the system" do - Puppet::Util::Execution.expects(:execute).with(['/usr/bin/pkginfo', '-l']).returns File.read(my_fixture('simple')) + described_class.expects(:pkginfo).with('-l').returns File.read(my_fixture('simple')) instances = provider.class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } instances.size.should == 2 instances[0].should == { @@ -113,7 +105,7 @@ describe Puppet::Type.type(:package).provider(:sun) do end it "should return empty if there were no packages" do - Puppet::Util::Execution.expects(:execute).with(['/usr/bin/pkginfo', '-l']).returns '' + described_class.expects(:pkginfo).with('-l').returns '' instances = provider.class.instances instances.size.should == 0 end diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index 99785c35c..b541c0e9e 100755 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -25,6 +25,12 @@ describe provider do end describe 'when installing' do + before(:each) do + Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") + provider.stubs(:which).with("rpm").returns("/bin/rpm") + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:combine => true, :custom_environment => {}, :failonfail => true}).returns("4.10.1\n").at_most_once + end + it 'should call yum install for :installed' do @resource.stubs(:should).with(:ensure).returns :installed @provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, 'mypackage') @@ -56,11 +62,6 @@ describe provider do @provider.expects(:yum).with('-y', :erase, 'mypackage') @provider.purge end - - it 'should use rpm to uninstall' do - @provider.expects(:rpm).with('-e', 'mypackage-1-1.i386') - @provider.uninstall - end end it 'should be versionable' do diff --git a/spec/unit/provider/parsedfile_spec.rb b/spec/unit/provider/parsedfile_spec.rb index ade55ec9f..826239c70 100755 --- a/spec/unit/provider/parsedfile_spec.rb +++ b/spec/unit/provider/parsedfile_spec.rb @@ -5,55 +5,97 @@ require 'puppet/provider/parsedfile' # Most of the tests for this are still in test/ral/provider/parsedfile.rb. describe Puppet::Provider::ParsedFile do - before do - @class = Class.new(Puppet::Provider::ParsedFile) - end + + # The ParsedFile provider class is meant to be used as an abstract base class + # but also stores a lot of state within the singleton class. To avoid + # sharing data between classes we construct an anonymous class that inherits + # the ParsedFile provider instead of directly working with the ParsedFile + # provider itself. + subject { Puppet::Type.newtype(:parsedfile_type).provide(:parsedfile_provider, :parent => described_class) } describe "when looking up records loaded from disk" do it "should return nil if no records have been loaded" do - @class.record?("foo").should be_nil + subject.record?("foo").should be_nil end end describe "when generating a list of instances" do it "should return an instance for each record parsed from all of the registered targets" do - @class.expects(:targets).returns %w{/one /two} - @class.stubs(:skip_record?).returns false + subject.expects(:targets).returns %w{/one /two} + subject.stubs(:skip_record?).returns false one = [:uno1, :uno2] two = [:dos1, :dos2] - @class.expects(:prefetch_target).with("/one").returns one - @class.expects(:prefetch_target).with("/two").returns two + subject.expects(:prefetch_target).with("/one").returns one + subject.expects(:prefetch_target).with("/two").returns two results = [] (one + two).each do |inst| results << inst.to_s + "_instance" - @class.expects(:new).with(inst).returns(results[-1]) + subject.expects(:new).with(inst).returns(results[-1]) end - @class.instances.should == results + subject.instances.should == results + end + + it "should ignore target when retrieve fails" do + subject.expects(:targets).returns %w{/one /two /three} + subject.stubs(:skip_record?).returns false + subject.expects(:retrieve).with("/one").returns [ + {:name => 'target1_record1'}, + {:name => 'target1_record2'} + ] + subject.expects(:retrieve).with("/two").raises Puppet::Util::FileType::FileReadError, "some error" + subject.expects(:retrieve).with("/three").returns [ + {:name => 'target3_record1'}, + {:name => 'target3_record2'} + ] + Puppet.expects(:err).with('Could not prefetch parsedfile_type provider \'parsedfile_provider\' target \'/two\': some error. Treating as empty') + subject.expects(:new).with(:name => 'target1_record1', :on_disk => true, :target => '/one', :ensure => :present).returns 'r1' + subject.expects(:new).with(:name => 'target1_record2', :on_disk => true, :target => '/one', :ensure => :present).returns 'r2' + subject.expects(:new).with(:name => 'target3_record1', :on_disk => true, :target => '/three', :ensure => :present).returns 'r3' + subject.expects(:new).with(:name => 'target3_record2', :on_disk => true, :target => '/three', :ensure => :present).returns 'r4' + + subject.instances.should == %w{r1 r2 r3 r4} end it "should skip specified records" do - @class.expects(:targets).returns %w{/one} - @class.expects(:skip_record?).with(:uno).returns false - @class.expects(:skip_record?).with(:dos).returns true + subject.expects(:targets).returns %w{/one} + subject.expects(:skip_record?).with(:uno).returns false + subject.expects(:skip_record?).with(:dos).returns true one = [:uno, :dos] - @class.expects(:prefetch_target).returns one + subject.expects(:prefetch_target).returns one + + subject.expects(:new).with(:uno).returns "eh" + subject.expects(:new).with(:dos).never + + subject.instances + end + end - @class.expects(:new).with(:uno).returns "eh" - @class.expects(:new).with(:dos).never + describe "when matching resources to existing records" do + let(:first_resource) { stub(:one, :name => :one) } + let(:second_resource) { stub(:two, :name => :two) } - @class.instances + let(:resources) {{:one => first_resource, :two => second_resource}} + + it "returns a resource if the record name matches the resource name" do + record = {:name => :one} + subject.resource_for_record(record, resources).should be first_resource + end + + it "doesn't return a resource if the record name doesn't match any resource names" do + record = {:name => :three} + subject.resource_for_record(record, resources).should be_nil end end describe "when flushing a file's records to disk" do before do # This way we start with some @records, like we would in real life. - @class.stubs(:retrieve).returns [] - @class.default_target = "/foo/bar" - @class.initvars - @class.prefetch + subject.stubs(:retrieve).returns [] + subject.default_target = "/foo/bar" + subject.initvars + subject.prefetch @filetype = Puppet::Util::FileType.filetype(:flat).new("/my/file") Puppet::Util::FileType.filetype(:flat).stubs(:new).with("/my/file").returns @filetype @@ -64,7 +106,7 @@ describe Puppet::Provider::ParsedFile do it "should back up the file being written if the filetype can be backed up" do @filetype.expects(:backup) - @class.flush_target("/my/file") + subject.flush_target("/my/file") end it "should not try to back up the file if the filetype cannot be backed up" do @@ -73,22 +115,74 @@ describe Puppet::Provider::ParsedFile do @filetype.stubs(:write) - @class.flush_target("/my/file") + subject.flush_target("/my/file") end it "should not back up the file more than once between calls to 'prefetch'" do @filetype.expects(:backup).once - @class.flush_target("/my/file") - @class.flush_target("/my/file") + subject.flush_target("/my/file") + subject.flush_target("/my/file") end it "should back the file up again once the file has been reread" do @filetype.expects(:backup).times(2) - @class.flush_target("/my/file") - @class.prefetch - @class.flush_target("/my/file") + subject.flush_target("/my/file") + subject.prefetch + subject.flush_target("/my/file") + end + end +end + +describe "A very basic provider based on ParsedFile" do + + let(:input_text) { File.read(my_fixture('simple.txt')) } + let(:target) { File.expand_path("/tmp/test") } + + subject do + example_provider_class = Class.new(Puppet::Provider::ParsedFile) + example_provider_class.default_target = target + # Setup some record rules + example_provider_class.instance_eval do + text_line :text, :match => %r{.} + end + example_provider_class.initvars + example_provider_class.prefetch + # evade a race between multiple invocations of the header method + example_provider_class.stubs(:header). + returns("# HEADER As added by puppet.\n") + example_provider_class + end + + context "writing file contents back to disk" do + it "should not change anything except from adding a header" do + input_records = subject.parse(input_text) + subject.to_file(input_records). + should match subject.header + input_text + end + end + + context "rewriting a file containing a native header" do + let(:regex) { %r/^# HEADER.*third party\.\n/ } + let(:input_records) { subject.parse(input_text) } + + before :each do + subject.stubs(:native_header_regex).returns(regex) + end + + it "should move the native header to the top" do + subject.to_file(input_records).should_not match /\A#{subject.header}/ + end + + context "and dropping native headers found in input" do + before :each do + subject.stubs(:drop_native_header).returns(true) + end + + it "should not include the native header in the output" do + subject.to_file(input_records).should_not match regex + end end end end diff --git a/spec/unit/provider/service/freebsd_spec.rb b/spec/unit/provider/service/freebsd_spec.rb index 0f523e84f..81e3fc1af 100755 --- a/spec/unit/provider/service/freebsd_spec.rb +++ b/spec/unit/provider/service/freebsd_spec.rb @@ -54,4 +54,22 @@ OUTPUT @provider.rcvar_value.should == "YES" end + + it "should find the right rcvar_name" do + @provider.stubs(:rcvar).returns(['# ntpd', 'ntpd_enable="YES"']) + + @provider.rcvar_name.should == "ntpd" + end + + it "should enable only the selected service" do + File.stubs(:exists?).with('/etc/rc.conf').returns(true) + File.stubs(:read).with('/etc/rc.conf').returns("openntpd_enable=\"NO\"\nntpd_enable=\"NO\"\n") + fh = stub 'fh' + File.stubs(:open).with('/etc/rc.conf', File::WRONLY).yields(fh) + fh.expects(:<<).with("openntpd_enable=\"NO\"\nntpd_enable=\"YES\"\n") + File.stubs(:exists?).with('/etc/rc.conf.local').returns(false) + File.stubs(:exists?).with('/etc/rc.conf.d/ntpd').returns(false) + + @provider.rc_replace('ntpd', 'ntpd', 'YES') + end end diff --git a/spec/unit/provider/service/init_spec.rb b/spec/unit/provider/service/init_spec.rb index 0b4527026..6da19ef40 100755 --- a/spec/unit/provider/service/init_spec.rb +++ b/spec/unit/provider/service/init_spec.rb @@ -5,141 +5,155 @@ require 'spec_helper' -provider_class = Puppet::Type.type(:service).provider(:init) +describe Puppet::Type.type(:service).provider(:init) do -describe provider_class do + let :provider do + provider = described_class.new(:name => 'myservice') + provider.resource = resource + provider + end - before :each do - @class = Puppet::Type.type(:service).provider(:init) - @resource = stub 'resource' - @resource.stubs(:[]).returns(nil) - @resource.stubs(:[]).with(:name).returns "myservice" - @resource.stubs(:[]).with(:path).returns ["/service/path","/alt/service/path"] - File.stubs(:directory?).returns(true) + let :resource do + Puppet::Type.type(:service).new( + :name => 'myservice', + :ensure => :running, + :path => paths + ) + end - @provider = provider_class.new - @provider.resource = @resource + let :paths do + ["/service/path","/alt/service/path"] + end + + let :excludes do + # Taken from redhat, gentoo, and debian + %w{functions.sh reboot.sh shutdown.sh functions halt killall single linuxconf reboot boot wait-for-state rcS module-init-tools} end describe "when getting all service instances" do before :each do + described_class.stubs(:defpath).returns('tmp') + @services = ['one', 'two', 'three', 'four'] - Dir.stubs(:entries).returns @services - FileTest.stubs(:directory?).returns(true) + Dir.stubs(:entries).with('tmp').returns @services + FileTest.expects(:directory?).with('tmp').returns(true) FileTest.stubs(:executable?).returns(true) - @class.stubs(:defpath).returns('tmp') end it "should return instances for all services" do - @services.each do |inst| - @class.expects(:new).with{|hash| hash[:name] == inst}.returns("#{inst}_instance") - end - results = @services.collect {|x| "#{x}_instance"} - - @class.instances.should == results + described_class.instances.map(&:name).should == @services end it "should omit an array of services from exclude list" do exclude = ['two', 'four'] - (@services - exclude).each do |inst| - @class.expects(:new).with{|hash| hash[:name] == inst}.returns("#{inst}_instance") - end - results = (@services-exclude).collect {|x| "#{x}_instance"} - - @class.get_services(@class.defpath, exclude).should == results + described_class.get_services(described_class.defpath, exclude).map(&:name).should == (@services - exclude) end it "should omit a single service from the exclude list" do exclude = 'two' - (@services - [exclude]).each do |inst| - @class.expects(:new).with{|hash| hash[:name] == inst}.returns("#{inst}_instance") - end - results = @services.reject{|x| x == exclude }.collect {|x| "#{x}_instance"} - - @class.get_services(@class.defpath, exclude).should == results + described_class.get_services(described_class.defpath, exclude).map(&:name).should == @services - [exclude] end it "should use defpath" do - @services.each do |inst| - @class.expects(:new).with{|hash| hash[:path] == @class.defpath}.returns("#{inst}_instance") - end - results = @services.sort.collect {|x| "#{x}_instance"} - - @class.instances.sort.should == results + described_class.instances.should be_all { |provider| provider.get(:path) == described_class.defpath } end it "should set hasstatus to true for providers" do - @services.each do |inst| - @class.expects(:new).with{|hash| hash[:name] == inst && hash[:hasstatus] == true}.returns("#{inst}_instance") - end - results = @services.collect {|x| "#{x}_instance"} - - @class.instances.should == results + described_class.instances.should be_all { |provider| provider.get(:hasstatus) == true } end it "should discard upstart jobs" do not_init_service, *valid_services = @services - valid_services.each do |inst| - @class.expects(:new).with{|hash| hash[:name] == inst && hash[:hasstatus] == true}.returns("#{inst}_instance") - end - File.stubs(:symlink?).returns(false) + File.stubs(:symlink?).returns false File.stubs(:symlink?).with("tmp/#{not_init_service}").returns(true) File.stubs(:readlink).with("tmp/#{not_init_service}").returns("/lib/init/upstart-job") - results = valid_services.collect {|x| "#{x}_instance"} - @class.instances.should == results + described_class.instances.map(&:name).should == valid_services + end + + it "should discard non-initscript scripts" do + valid_services = @services + all_services = valid_services + excludes + Dir.expects(:entries).with('tmp').returns all_services + described_class.instances.map(&:name).should =~ valid_services end end - describe "when searching for the init script" do + describe "when checking valid paths" do it "should discard paths that do not exist" do - File.stubs(:exist?).returns(false) - File.stubs(:directory?).returns(false) - @provider.paths.should be_empty + File.expects(:directory?).with(paths[0]).returns false + File.expects(:exist?).with(paths[0]).returns false + File.expects(:directory?).with(paths[1]).returns true + + provider.paths.should == [paths[1]] end it "should discard paths that are not directories" do - File.stubs(:exist?).returns(true) - File.stubs(:directory?).returns(false) - @provider.paths.should be_empty + paths.each do |path| + File.expects(:exist?).with(path).returns true + File.expects(:directory?).with(path).returns false + end + provider.paths.should be_empty end + end - it "should be able to find the init script in the service path" do - File.stubs(:stat).raises(Errno::ENOENT.new('No such file or directory')) - File.expects(:stat).with("/service/path/myservice").returns true - @provider.initscript.should == "/service/path/myservice" + describe "when searching for the init script" do + before :each do + paths.each {|path| File.expects(:directory?).with(path).returns true } end + it "should be able to find the init script in the service path" do - File.stubs(:stat).raises(Errno::ENOENT.new('No such file or directory')) - File.expects(:stat).with("/alt/service/path/myservice").returns true - @provider.initscript.should == "/alt/service/path/myservice" + File.expects(:stat).with("#{paths[0]}/myservice").returns true + File.expects(:stat).with("#{paths[1]}/myservice").never # first one wins + provider.initscript.should == "/service/path/myservice" end + + it "should be able to find the init script in an alternate service path" do + File.expects(:stat).with("#{paths[0]}/myservice").raises Errno::ENOENT, "No such file or directory - #{paths[0]}/myservice" + File.expects(:stat).with("#{paths[1]}/myservice").returns true + provider.initscript.should == "/alt/service/path/myservice" + end + + it "should be able to find the init script if it ends with .sh" do + File.expects(:stat).with("#{paths[0]}/myservice").raises Errno::ENOENT, "No such file or directory - #{paths[0]}/myservice" + File.expects(:stat).with("#{paths[1]}/myservice").raises Errno::ENOENT, "No such file or directory - #{paths[1]}/myservice" + File.expects(:stat).with("#{paths[0]}/myservice.sh").returns true + provider.initscript.should == "/service/path/myservice.sh" + end + it "should fail if the service isn't there" do - lambda { @provider.initscript }.should raise_error(Puppet::Error, "Could not find init script for 'myservice'") + paths.each do |path| + File.expects(:stat).with("#{path}/myservice").raises Errno::ENOENT, "No such file or directory - #{path}/myservice" + File.expects(:stat).with("#{path}/myservice.sh").raises Errno::ENOENT, "No such file or directory - #{path}/myservice.sh" + end + expect { provider.initscript }.to raise_error(Puppet::Error, "Could not find init script for 'myservice'") end end describe "if the init script is present" do before :each do + File.stubs(:directory?).with("/service/path").returns true + File.stubs(:directory?).with("/alt/service/path").returns true File.stubs(:stat).with("/service/path/myservice").returns true end [:start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do - @provider.should respond_to(method) + provider.should respond_to(method) end describe "when running #{method}" do it "should use any provided explicit command" do - @resource.stubs(:[]).with(method).returns "/user/specified/command" - @provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } - @provider.send(method) + resource[method] = "/user/specified/command" + provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } + provider.send(method) end it "should pass #{method} to the init script when no explicit command is provided" do - @resource.stubs(:[]).with("has#{method}".intern).returns :true - @provider.expects(:execute).with { |command, *args| command == ["/service/path/myservice",method]} - @provider.send(method) + resource[:hasrestart] = :true + resource[:hasstatus] = :true + provider.expects(:execute).with { |command, *args| command == ["/service/path/myservice",method]} + provider.send(method) end end end @@ -147,45 +161,52 @@ describe provider_class do describe "when checking status" do describe "when hasstatus is :true" do before :each do - @resource.stubs(:[]).with(:hasstatus).returns :true + resource[:hasstatus] = :true end it "should execute the command" do - @provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") - @provider.status + provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") + provider.status end it "should consider the process running if the command returns 0" do - @provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") + provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) - @provider.status.should == :running + provider.status.should == :running end [-10,-1,1,10].each { |ec| it "should consider the process stopped if the command returns something non-0" do - @provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") + provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(ec) - @provider.status.should == :stopped + provider.status.should == :stopped end } end describe "when hasstatus is not :true" do + before :each do + resource[:hasstatus] = :false + end + it "should consider the service :running if it has a pid" do - @provider.expects(:getpid).returns "1234" - @provider.status.should == :running + provider.expects(:getpid).returns "1234" + provider.status.should == :running end it "should consider the service :stopped if it doesn't have a pid" do - @provider.expects(:getpid).returns nil - @provider.status.should == :stopped + provider.expects(:getpid).returns nil + provider.status.should == :stopped end end end describe "when restarting and hasrestart is not :true" do + before :each do + resource[:hasrestart] = :false + end + it "should stop and restart the process" do - @provider.expects(:texecute).with(:stop, ['/service/path/myservice', :stop ], true).returns("") - @provider.expects(:texecute).with(:start,['/service/path/myservice', :start], true).returns("") + provider.expects(:texecute).with(:stop, ['/service/path/myservice', :stop ], true).returns("") + provider.expects(:texecute).with(:start,['/service/path/myservice', :start], true).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) - @provider.restart + provider.restart end end - end end diff --git a/spec/unit/provider/service/launchd_spec.rb b/spec/unit/provider/service/launchd_spec.rb index dd22c81a0..fc82fdd00 100755 --- a/spec/unit/provider/service/launchd_spec.rb +++ b/spec/unit/provider/service/launchd_spec.rb @@ -7,8 +7,8 @@ describe Puppet::Type.type(:service).provider(:launchd) do let (:joblabel) { "com.foo.food" } let (:provider) { subject.class } let (:launchd_overrides) { '/var/db/launchd.db/com.apple.launchd/overrides.plist' } - let(:resource) { Puppet::Type.type(:service).new(:name => joblabel) } - subject { Puppet::Type.type(:service).provider(:launchd).new(resource) } + let(:resource) { Puppet::Type.type(:service).new(:name => joblabel, :provider => :launchd) } + subject { resource.provider } describe "the type interface" do %w{ start stop enabled? enable disable status}.each do |method| diff --git a/spec/unit/provider/service/openwrt_spec.rb b/spec/unit/provider/service/openwrt_spec.rb new file mode 100755 index 000000000..4cfb06f40 --- /dev/null +++ b/spec/unit/provider/service/openwrt_spec.rb @@ -0,0 +1,109 @@ +#! /usr/bin/env ruby +# +# Unit testing for the OpenWrt service Provider +# +require 'spec_helper' + +describe Puppet::Type.type(:service).provider(:openwrt), :as_platform => :posix do + + let(:resource) do + resource = stub 'resource' + resource.stubs(:[]).returns(nil) + resource.stubs(:[]).with(:name).returns "myservice" + resource.stubs(:[]).with(:path).returns ["/etc/init.d"] + + resource + end + + let(:provider) do + provider = described_class.new + provider.stubs(:get).with(:hasstatus).returns false + + provider + end + + + before :each do + resource.stubs(:provider).returns provider + provider.resource = resource + + FileTest.stubs(:file?).with('/etc/rc.common').returns true + FileTest.stubs(:executable?).with('/etc/rc.common').returns true + + # All OpenWrt tests operate on the init script directly. It must exist. + File.stubs(:directory?).with('/etc/init.d').returns true + + File.stubs(:stat).with('/etc/init.d/myservice') + FileTest.stubs(:file?).with('/etc/init.d/myservice').returns true + FileTest.stubs(:executable?).with('/etc/init.d/myservice').returns true + end + + operatingsystem = 'openwrt' + it "should be the default provider on #{operatingsystem}" do + Facter.expects(:value).with(:operatingsystem).returns(operatingsystem) + described_class.default?.should be_true + end + + # test self.instances + describe "when getting all service instances" do + + let(:services) {['dnsmasq', 'dropbear', 'firewall', 'led', 'puppet', 'uhttpd' ]} + + before :each do + Dir.stubs(:entries).returns services + FileTest.stubs(:directory?).returns(true) + FileTest.stubs(:executable?).returns(true) + end + it "should return instances for all services" do + services.each do |inst| + described_class.expects(:new).with{|hash| hash[:name] == inst && hash[:path] == '/etc/init.d'}.returns("#{inst}_instance") + end + results = services.collect {|x| "#{x}_instance"} + described_class.instances.should == results + end + end + + it "should have an enabled? method" do + provider.should respond_to(:enabled?) + end + + it "should have an enable method" do + provider.should respond_to(:enable) + end + + it "should have a disable method" do + provider.should respond_to(:disable) + end + + [:start, :stop, :restart].each do |method| + it "should have a #{method} method" do + provider.should respond_to(method) + end + describe "when running #{method}" do + + it "should use any provided explicit command" do + resource.stubs(:[]).with(method).returns "/user/specified/command" + provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } + provider.send(method) + end + + it "should execute the init script with #{method} when no explicit command is provided" do + resource.stubs(:[]).with("has#{method}".intern).returns :true + provider.expects(:execute).with { |command, *args| command == ['/etc/init.d/myservice', method ]} + provider.send(method) + end + end + end + + describe "when checking status" do + it "should consider the service :running if it has a pid" do + provider.expects(:getpid).returns "1234" + provider.status.should == :running + end + it "should consider the service :stopped if it doesn't have a pid" do + provider.expects(:getpid).returns nil + provider.status.should == :stopped + end + end + +end diff --git a/spec/unit/provider/service/src_spec.rb b/spec/unit/provider/service/src_spec.rb index 8d857a060..1f0b928fd 100755 --- a/spec/unit/provider/service/src_spec.rb +++ b/spec/unit/provider/service/src_spec.rb @@ -21,77 +21,153 @@ describe provider_class do @provider.stubs(:command).with(:startsrc).returns "/usr/bin/startsrc" @provider.stubs(:command).with(:lssrc).returns "/usr/bin/lssrc" @provider.stubs(:command).with(:refresh).returns "/usr/bin/refresh" + @provider.stubs(:command).with(:lsitab).returns "/usr/sbin/lsitab" + @provider.stubs(:command).with(:mkitab).returns "/usr/sbin/mkitab" + @provider.stubs(:command).with(:rmitab).returns "/usr/sbin/rmitab" + @provider.stubs(:command).with(:chitab).returns "/usr/sbin/chitab" @provider.stubs(:stopsrc) @provider.stubs(:startsrc) @provider.stubs(:lssrc) @provider.stubs(:refresh) + @provider.stubs(:lsitab) + @provider.stubs(:mkitab) + @provider.stubs(:rmitab) + @provider.stubs(:chitab) end - [:start, :stop, :status, :restart].each do |method| - it "should have a #{method} method" do - @provider.should respond_to(method) + describe ".instances" do + it "should has a .instances method" do + provider_class.should respond_to :instances end + + it "should get a list of running services" do + sample_output = <<_EOF_ +#subsysname:synonym:cmdargs:path:uid:auditid:standin:standout:standerr:action:multi:contact:svrkey:svrmtype:priority:signorm:sigforce:display:waittime:grpname: +myservice.1:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: +myservice.2:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: +myservice.3:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: +myservice.4:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: +_EOF_ + provider_class.stubs(:lssrc).returns sample_output + provider_class.instances.map(&:name).should == [ + 'myservice.1', + 'myservice.2', + 'myservice.3', + 'myservice.4' + ] + end + end - it "should execute the startsrc command" do - @provider.expects(:execute).with(['/usr/bin/startsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) - @provider.start + describe "when starting a service" do + it "should execute the startsrc command" do + @provider.expects(:execute).with(['/usr/bin/startsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) + @provider.start + end end - it "should execute the stopsrc command" do - @provider.expects(:execute).with(['/usr/bin/stopsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) - @provider.stop + describe "when stopping a service" do + it "should execute the stopsrc command" do + @provider.expects(:execute).with(['/usr/bin/stopsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) + @provider.stop + end end - it "should execute status and return running if the subsystem is active" do - sample_output = <<_EOF_ -Subsystem Group PID Status -myservice tcpip 1234 active -_EOF_ + describe "should have a set of methods" do + [:enabled?, :enable, :disable, :start, :stop, :status, :restart].each do |method| + it "should have a #{method} method" do + @provider.should respond_to(method) + end + end + end - @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output - @provider.status.should == :running + describe "when enabling" do + it "should execute the mkitab command" do + @provider.expects(:mkitab).with("myservice:2:once:/usr/bin/startsrc -s myservice").once + @provider.enable + end end - it "should execute status and return stopped if the subsystem is inoperative" do - sample_output = <<_EOF_ -Subsystem Group PID Status -myservice tcpip inoperative -_EOF_ + describe "when disabling" do + it "should execute the rmitab command" do + @provider.expects(:rmitab).with("myservice") + @provider.disable + end + end + + describe "when checking if it is enabled" do + it "should execute the lsitab command" do + @provider.expects(:execute).with(['/usr/sbin/lsitab', 'myservice'], {:combine => true, :failonfail => false}) + @provider.enabled? + end - @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output - @provider.status.should == :stopped + it "should return false when lsitab returns non-zero" do + @provider.stubs(:execute) + $CHILD_STATUS.stubs(:exitstatus).returns(1) + @provider.enabled?.should == :false + end + + it "should return true when lsitab returns zero" do + @provider.stubs(:execute) + $CHILD_STATUS.stubs(:exitstatus).returns(0) + @provider.enabled?.should == :true + end end - it "should execute status and return nil if the status is not known" do - sample_output = <<_EOF_ -Subsystem Group PID Status -myservice tcpip randomdata + + describe "when checking a subsystem's status" do + it "should execute status and return running if the subsystem is active" do + sample_output = <<_EOF_ + Subsystem Group PID Status + myservice tcpip 1234 active +_EOF_ + + @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output + @provider.status.should == :running + end + + it "should execute status and return stopped if the subsystem is inoperative" do + sample_output = <<_EOF_ + Subsystem Group PID Status + myservice tcpip inoperative _EOF_ - @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output - @provider.status.should == nil + @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output + @provider.status.should == :stopped + end + + it "should execute status and return nil if the status is not known" do + sample_output = <<_EOF_ + Subsystem Group PID Status + myservice tcpip randomdata +_EOF_ + + @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output + @provider.status.should == nil + end end - it "should execute restart which runs refresh" do - sample_output = <<_EOF_ + describe "when restarting a service" do + it "should execute restart which runs refresh" do + sample_output = <<_EOF_ #subsysname:synonym:cmdargs:path:uid:auditid:standin:standout:standerr:action:multi:contact:svrkey:svrmtype:priority:signorm:sigforce:display:waittime:grpname: myservice:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: _EOF_ - @provider.expects(:execute).with(['/usr/bin/lssrc', '-Ss', "myservice"]).returns sample_output - @provider.expects(:execute).with(['/usr/bin/refresh', '-s', "myservice"]) - @provider.restart - end + @provider.expects(:execute).with(['/usr/bin/lssrc', '-Ss', "myservice"]).returns sample_output + @provider.expects(:execute).with(['/usr/bin/refresh', '-s', "myservice"]) + @provider.restart + end - it "should execute restart which runs stopsrc then startsrc" do - sample_output = <<_EOF_ + it "should execute restart which runs stopsrc then startsrc" do + sample_output = <<_EOF_ #subsysname:synonym:cmdargs:path:uid:auditid:standin:standout:standerr:action:multi:contact:svrkey:svrmtype:priority:signorm:sigforce:display:waittime:grpname: myservice::--no-daemonize:/usr/sbin/puppetd:0:0:/dev/null:/var/log/puppet.log:/var/log/puppet.log:-O:-Q:-S:0:0:20:15:9:-d:20::" _EOF_ - @provider.expects(:execute).with(['/usr/bin/lssrc', '-Ss', "myservice"]).returns sample_output - @provider.expects(:execute).with(['/usr/bin/stopsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) - @provider.expects(:execute).with(['/usr/bin/startsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) - @provider.restart + @provider.expects(:execute).with(['/usr/bin/lssrc', '-Ss', "myservice"]).returns sample_output + @provider.expects(:execute).with(['/usr/bin/stopsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) + @provider.expects(:execute).with(['/usr/bin/startsrc', '-s', "myservice"], {:override_locale => false, :squelch => true, :failonfail => true}) + @provider.restart + end end end diff --git a/spec/unit/provider/service/systemd_spec.rb b/spec/unit/provider/service/systemd_spec.rb index 5041c56a9..6ed8658f9 100755 --- a/spec/unit/provider/service/systemd_spec.rb +++ b/spec/unit/provider/service/systemd_spec.rb @@ -1,34 +1,152 @@ #! /usr/bin/env ruby # -# Unit testing for the RedHat service Provider +# Unit testing for the systemd service Provider # require 'spec_helper' -provider_class = Puppet::Type.type(:service).provider(:systemd) - -describe provider_class do +describe Puppet::Type.type(:service).provider(:systemd) do before :each do - @class = Puppet::Type.type(:service).provider(:redhat) - @resource = stub 'resource' - @resource.stubs(:[]).returns(nil) - @resource.stubs(:[]).with(:name).returns "myservice.service" - @provider = provider_class.new - @resource.stubs(:provider).returns @provider - @provider.resource = @resource + Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class + described_class.stubs(:which).with('systemctl').returns '/bin/systemctl' + end + + + let :provider do + described_class.new(:name => 'sshd.service') end + + osfamily = [ 'archlinux' ] + + osfamily.each do |osfamily| + it "should be the default provider on #{osfamily}" do + Facter.expects(:value).with(:osfamily).returns(osfamily) + described_class.default?.should be_true + end + end [:enabled?, :enable, :disable, :start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do - @provider.should respond_to(method) + provider.should respond_to(method) + end + end + + describe ".instances" do + it "should have an instances method" do + described_class.should respond_to :instances + end + + it "should get a list of services from systemctl list-units" do + pending('A service that has been killed (ACTIVE=failed) is not recognized') + described_class.expects(:systemctl).with('list-units', '--full', '--all', '--no-pager').returns File.read(my_fixture('list_units')) + described_class.instances.map(&:name).should =~ %w{ + auditd.service + crond.service + dbus.service + display-manager.service + ebtables.service + fedora-readonly.service + initrd-switch-root.service + ip6tables.service + puppet.service + sshd.service + } + end + end + + describe "#start" do + it "should use the supplied start command if specified" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :start => '/bin/foo')) + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => true) + provider.start + end + + it "should start the service with systemctl start otherwise" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:execute).with(['/bin/systemctl','start','sshd.service'], :failonfail => true, :override_locale => false, :squelch => true) + provider.start + end + end + + describe "#stop" do + it "should use the supplied stop command if specified" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :stop => '/bin/foo')) + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => true) + provider.stop + end + + it "should stop the service with systemctl stop otherwise" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:execute).with(['/bin/systemctl','stop','sshd.service'], :failonfail => true, :override_locale => false, :squelch => true) + provider.stop end end + describe "#enabled?" do + it "should return :true if the service is enabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('is-enabled', 'sshd.service').returns 'enabled' + provider.enabled?.should == :true + end + + it "should return :false if the service is disabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('is-enabled', 'sshd.service').raises Puppet::ExecutionFailure, "Execution of '/bin/systemctl is-enabled sshd.service' returned 1: disabled" + provider.enabled?.should == :false + end + end + + describe "#enable" do + it "should run systemctl enable to enable a service" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('enable', 'sshd.service') + provider.enable + end + end + + describe "#disable" do + it "should run systemctl disable to disable a service" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with(:disable, 'sshd.service') + provider.disable + end + end + + # Note: systemd provider does not care about hasstatus or a custom status + # command. I just assume that it does not make sense for systemd. + describe "#status" do + it "should return running if active" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('is-active', 'sshd.service').returns 'active' + provider.status.should == :running + end + + it "should return stopped if inactive" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('is-active', 'sshd.service').raises Puppet::ExecutionFailure, "Execution of '/bin/systemctl is-active sshd.service' returned 3: inactive" + provider.status.should == :stopped + end + end + + # Note: systemd provider does not care about hasrestart. I just assume it + # does not make sense for systemd + describe "#restart" do + it "should use the supplied restart command if specified" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) + provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => true) + provider.restart + end + + it "should restart the service with systemctl restart" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => true) + provider.restart + end + end - it 'should return resources from self.instances' do - provider_class.expects(:systemctl).with('list-units', '--full', '--all', '--no-pager').returns( - "my_service loaded active running\nmy_other_service loaded active running" - ) - provider_class.instances.map {|provider| provider.name}.should =~ ["my_service","my_other_service"] + it "(#16451) has command systemctl without being fully qualified" do + described_class.instance_variable_get(:@commands). + should include(:systemctl => 'systemctl') end end diff --git a/spec/unit/provider/service/upstart_spec.rb b/spec/unit/provider/service/upstart_spec.rb index f2e5b4033..e24857901 100755 --- a/spec/unit/provider/service/upstart_spec.rb +++ b/spec/unit/provider/service/upstart_spec.rb @@ -42,7 +42,7 @@ describe Puppet::Type.type(:service).provider(:upstart) do end it "should not find excluded services" do - processes = "wait-for-state stop/waiting" + processes = "wait-for-state stop/waiting\nportmap-wait start/running" provider_class.stubs(:execpipe).yields(processes) provider_class.instances.should be_empty end diff --git a/spec/unit/provider/user/aix_spec.rb b/spec/unit/provider/user/aix_spec.rb new file mode 100644 index 000000000..ecc4b3b40 --- /dev/null +++ b/spec/unit/provider/user/aix_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +provider_class = Puppet::Type.type(:user).provider(:aix) + +describe provider_class do + + let(:lsuser_all_example) do + <<-OUTPUT +root id=0 pgrp=system groups=system,bin,sys,security,cron,audit,lp home=/root shell=/usr/bin/bash auditclasses=general login=true su=true rlogin=true daemon=true admin=true sugroups=ALL admgroups=lolt,allstaff tpath=nosak ttys=ALL expires=0 auth1=SYSTEM auth2=NONE umask=22 registry=files SYSTEM=compat logintimes= loginretries=0 pwdwarntime=0 account_locked=false minage=0 maxage=0 maxexpired=-1 minalpha=0 minother=0 mindiff=0 maxrepeats=8 minlen=0 histexpire=0 histsize=0 pwdchecks= dictionlist= default_roles= fsize=2097151 cpu=-1 data=262144 stack=65536 core=2097151 rss=65536 nofiles=2000 time_last_login=1358465855 time_last_unsuccessful_login=1358378454 tty_last_login=ssh tty_last_unsuccessful_login=ssh host_last_login=rpm-builder.puppetlabs.lan host_last_unsuccessful_login=192.168.100.78 unsuccessful_login_count=0 roles= +guest id=100 pgrp=usr groups=usr home=/home/guest login=true su=true rlogin=true daemon=true admin=false sugroups=ALL admgroups= tpath=nosak ttys=ALL expires=0 auth1=SYSTEM auth2=NONE umask=22 registry=files SYSTEM=compat logintimes= loginretries=0 pwdwarntime=0 account_locked=false minage=0 maxage=0 maxexpired=-1 minalpha=0 minother=0 mindiff=0 maxrepeats=8 minlen=0 histexpire=0 histsize=0 pwdchecks= dictionlist= default_roles= fsize=2097151 cpu=-1 data=262144 stack=65536 core=2097151 rss=65536 nofiles=2000 roles= + OUTPUT + end + + let(:lsgroup_all_example) do + <<-OUTPUT +root id=0 pgrp=system groups=system,bin,sys,security,cron,audit,lp home=/root shell=/usr/bin/bash +guest id=100 pgrp=usr groups=usr home=/home/guest + OUTPUT + end + + before do + @resource = stub('resource') + @provider = provider_class.new(@resource) + end + + it "should be able to return a group name based on a group ID" do + @provider.stubs(:lsgroupscmd) + + @provider.stubs(:execute).returns(lsgroup_all_example) + + @provider.groupname_by_id(100).should == 'guest' + end + + it "should be able to list all users" do + provider_class.stubs(:command) + + provider_class.stubs(:execute).returns(lsuser_all_example) + + provider_class.list_all.should == ['root', 'guest'] + end + +end diff --git a/spec/unit/provider/user/directoryservice_spec.rb b/spec/unit/provider/user/directoryservice_spec.rb index 7be639970..6534147b7 100755 --- a/spec/unit/provider/user/directoryservice_spec.rb +++ b/spec/unit/provider/user/directoryservice_spec.rb @@ -1,4 +1,5 @@ #! /usr/bin/env ruby -S rspec +# encoding: ASCII-8BIT require 'spec_helper' require 'facter/util/plist' diff --git a/spec/unit/provider/user/pw_spec.rb b/spec/unit/provider/user/pw_spec.rb index 19956328f..e0074f474 100755 --- a/spec/unit/provider/user/pw_spec.rb +++ b/spec/unit/provider/user/pw_spec.rb @@ -17,55 +17,61 @@ describe provider_class do it "should run pw with no additional flags when no properties are given" do provider.addcmd.must == [provider_class.command(:pw), "useradd", "testuser"] - provider.expects(:execute).with([provider_class.command(:pw), "useradd", "testuser"]) + provider.expects(:execute).with([provider_class.command(:pw), "useradd", "testuser"], kind_of(Hash)) provider.create end it "should use -o when allowdupe is enabled" do resource[:allowdupe] = true - provider.expects(:execute).with(includes("-o")) + provider.expects(:execute).with(includes("-o"), kind_of(Hash)) provider.create end it "should use -c with the correct argument when the comment property is set" do resource[:comment] = "Testuser Name" - provider.expects(:execute).with(all_of(includes("-c"), includes("Testuser Name"))) + provider.expects(:execute).with(all_of(includes("-c"), includes("Testuser Name")), kind_of(Hash)) provider.create end it "should use -e with the correct argument when the expiry property is set" do resource[:expiry] = "2010-02-19" - provider.expects(:execute).with(all_of(includes("-e"), includes("19-02-2010"))) + provider.expects(:execute).with(all_of(includes("-e"), includes("19-02-2010")), kind_of(Hash)) + provider.create + end + + it "should use -e 00-00-0000 if the expiry property has to be removed" do + resource[:expiry] = :absent + provider.expects(:execute).with(all_of(includes("-e"), includes("00-00-0000")), kind_of(Hash)) provider.create end it "should use -g with the correct argument when the gid property is set" do resource[:gid] = 12345 - provider.expects(:execute).with(all_of(includes("-g"), includes(12345))) + provider.expects(:execute).with(all_of(includes("-g"), includes(12345)), kind_of(Hash)) provider.create end it "should use -G with the correct argument when the groups property is set" do resource[:groups] = "group1" - provider.expects(:execute).with(all_of(includes("-G"), includes("group1"))) + provider.expects(:execute).with(all_of(includes("-G"), includes("group1")), kind_of(Hash)) provider.create end it "should use -G with all the given groups when the groups property is set to an array" do resource[:groups] = ["group1", "group2"] - provider.expects(:execute).with(all_of(includes("-G"), includes("group1,group2"))) + provider.expects(:execute).with(all_of(includes("-G"), includes("group1,group2")), kind_of(Hash)) provider.create end it "should use -d with the correct argument when the home property is set" do resource[:home] = "/home/testuser" - provider.expects(:execute).with(all_of(includes("-d"), includes("/home/testuser"))) + provider.expects(:execute).with(all_of(includes("-d"), includes("/home/testuser")), kind_of(Hash)) provider.create end it "should use -m when the managehome property is enabled" do resource[:managehome] = true - provider.expects(:execute).with(includes("-m")) + provider.expects(:execute).with(includes("-m"), kind_of(Hash)) provider.create end @@ -78,13 +84,13 @@ describe provider_class do it "should use -s with the correct argument when the shell property is set" do resource[:shell] = "/bin/sh" - provider.expects(:execute).with(all_of(includes("-s"), includes("/bin/sh"))) + provider.expects(:execute).with(all_of(includes("-s"), includes("/bin/sh")), kind_of(Hash)) provider.create end it "should use -u with the correct argument when the uid property is set" do resource[:uid] = 12345 - provider.expects(:execute).with(all_of(includes("-u"), includes(12345))) + provider.expects(:execute).with(all_of(includes("-u"), includes(12345)), kind_of(Hash)) provider.create end @@ -93,7 +99,7 @@ describe provider_class do resource[:password] = "*" provider.addcmd.should_not include("-p") provider.expects(:password=) - provider.expects(:execute).with(Not(includes("-p"))) + provider.expects(:execute).with(Not(includes("-p")), kind_of(Hash)) provider.create end end @@ -150,6 +156,12 @@ describe provider_class do provider.expiry = "2011-02-19" end + it "should use -e with the correct argument when the expiry property is removed" do + resource[:expiry] = :absent + provider.expects(:execute).with(all_of(includes("-e"), includes("00-00-0000"))) + provider.expiry = :absent + end + it "should use -g with the correct argument when the gid property is changed" do resource[:gid] = 12345 provider.expects(:execute).with(all_of(includes("-g"), includes(54321))) diff --git a/spec/unit/provider/user/user_role_add_spec.rb b/spec/unit/provider/user/user_role_add_spec.rb index dc8be01e8..340a5b537 100755 --- a/spec/unit/provider/user/user_role_add_spec.rb +++ b/spec/unit/provider/user/user_role_add_spec.rb @@ -116,7 +116,7 @@ describe Puppet::Type.type(:user).provider(:user_role_add), :unless => Puppet.fe describe "with :allow_duplicates" do before do - resource.expects(:allowdupe?).returns true + resource.stubs(:allowdupe?).returns true provider.stubs(:is_role?).returns(false) provider.stubs(:execute) resource.stubs(:system?).returns false diff --git a/spec/unit/provider/user/useradd_spec.rb b/spec/unit/provider/user/useradd_spec.rb index d11d4f767..98183758b 100755 --- a/spec/unit/provider/user/useradd_spec.rb +++ b/spec/unit/provider/user/useradd_spec.rb @@ -6,6 +6,7 @@ describe Puppet::Type.type(:user).provider(:useradd) do before :each do described_class.stubs(:command).with(:password).returns '/usr/bin/chage' described_class.stubs(:command).with(:add).returns '/usr/sbin/useradd' + described_class.stubs(:command).with(:localadd).returns '/usr/sbin/luseradd' described_class.stubs(:command).with(:modify).returns '/usr/sbin/usermod' described_class.stubs(:command).with(:delete).returns '/usr/sbin/userdel' end @@ -21,11 +22,38 @@ describe Puppet::Type.type(:user).provider(:useradd) do let(:provider) { described_class.new(:name => 'myuser') } + + let(:shadow_entry) { + return unless Puppet.features.libshadow? + Struct::PasswdEntry.new( + 'myuser', # login name + '$6$FvW8Ib8h$qQMI/CR9m.QzIicZKutLpBgCBBdrch1IX0rTnxuI32K1pD9.RXZrmeKQlaC.RzODNuoUtPPIyQDufunvLOQWF0', # encrypted password + 15573, # date of last password change + 10, # minimum password age + 20, # maximum password age + 7, # password warning period + -1, # password inactivity period + 15706, # account expiration date + -1 # reserved field + ) + } + describe "#create" do + before do + provider.stubs(:exists?).returns(false) + end + + it "should add -g when no gid is specified and group already exists" do + Puppet::Util.stubs(:gid).returns(true) + resource[:ensure] = :present + provider.expects(:execute).with(includes('-g'), kind_of(Hash)) + provider.create + end + it "should add -o when allowdupe is enabled and the user is being created" do resource[:allowdupe] = true - provider.expects(:execute).with(includes('-o')) + provider.expects(:execute).with(includes('-o'), kind_of(Hash)) provider.create end @@ -33,7 +61,7 @@ describe Puppet::Type.type(:user).provider(:useradd) do it "should add -r when system is enabled" do resource[:system] = :true provider.should be_system_users - provider.expects(:execute).with(includes('-r')) + provider.expects(:execute).with(includes('-r'), kind_of(Hash)) provider.create end end @@ -42,7 +70,7 @@ describe Puppet::Type.type(:user).provider(:useradd) do it "should not add -r when system is enabled" do resource[:system] = :true provider.should_not be_system_users - provider.expects(:execute).with(['/usr/sbin/useradd', 'myuser']) + provider.expects(:execute).with(['/usr/sbin/useradd', 'myuser'], kind_of(Hash)) provider.create end end @@ -51,10 +79,54 @@ describe Puppet::Type.type(:user).provider(:useradd) do described_class.has_feature :manages_password_age resource[:password_min_age] = 5 resource[:password_max_age] = 10 - provider.expects(:execute).with(includes('/usr/sbin/useradd')) + provider.expects(:execute).with(includes('/usr/sbin/useradd'), kind_of(Hash)) provider.expects(:execute).with(['/usr/bin/chage', '-m', 5, '-M', 10, 'myuser']) provider.create end + + describe "on systems with the libuser and forcelocal=true" do + before do + described_class.has_feature :libuser + resource[:forcelocal] = true + end + it "should use luseradd instead of useradd" do + provider.expects(:execute).with(includes('/usr/sbin/luseradd'), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) + provider.create + end + + it "should NOT use -o when allowdupe=true" do + resource[:allowdupe] = :true + provider.expects(:execute).with(Not(includes('-o')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) + provider.create + end + + it "should raise an exception for duplicate UIDs" do + resource[:uid] = 505 + provider.stubs(:finduser).returns(true) + lambda { provider.create }.should raise_error(Puppet::Error, "UID 505 already exists, use allowdupe to force user creation") + end + + it "should not use -G for luseradd and should call usermod with -G after luseradd when groups property is set" do + resource[:groups] = ['group1', 'group2'] + provider.expects(:execute).with(Not(includes("-G")), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) + provider.expects(:execute).with(includes('/usr/sbin/usermod')) + provider.create + end + + it "should not use -m when managehome set" do + resource[:managehome] = :true + provider.expects(:execute).with(Not(includes('-m')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) + provider.create + end + + it "should not use -e with luseradd, should call usermod with -e after luseradd when expiry is set" do + resource[:expiry] = '2038-01-24' + provider.expects(:execute).with(all_of(includes('/usr/sbin/luseradd'), Not(includes('-e'))), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) + provider.expects(:execute).with(all_of(includes('/usr/sbin/usermod'), includes('-e'))) + provider.create + end + end + end describe "#uid=" do @@ -65,11 +137,29 @@ describe Puppet::Type.type(:user).provider(:useradd) do end end - describe "#check_allow_dup" do - it "should check allow dup" do - resource.expects(:allowdupe?) - provider.check_allow_dup + describe "#expiry=" do + it "should pass expiry to usermod as MM/DD/YY when on Solaris" do + Facter.expects(:value).with(:operatingsystem).returns 'Solaris' + resource[:expiry] = '2012-10-31' + provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '10/31/2012', 'myuser']) + provider.expiry = '2012-10-31' + end + + it "should pass expiry to usermod as YYYY-MM-DD when not on Solaris" do + Facter.expects(:value).with(:operatingsystem).returns 'not_solaris' + resource[:expiry] = '2012-10-31' + provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '2012-10-31', 'myuser']) + provider.expiry = '2012-10-31' + end + + it "should use -e with an empty string when the expiry property is removed" do + resource[:expiry] = :absent + provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '', 'myuser']) + provider.expiry = :absent end + end + + describe "#check_allow_dup" do it "should return an array with a flag if dup is allowed" do resource[:allowdupe] = :true @@ -111,7 +201,7 @@ describe Puppet::Type.type(:user).provider(:useradd) do describe "#check_manage_home" do it "should return an array with -m flag if home is managed" do resource[:managehome] = :true - provider.expects(:execute).with(includes('-m')) + provider.expects(:execute).with(includes('-m'), kind_of(Hash)) provider.create end @@ -126,14 +216,14 @@ describe Puppet::Type.type(:user).provider(:useradd) do it "should use -M flag if home is not managed and on Redhat" do Facter.stubs(:value).with(:osfamily).returns("RedHat") resource[:managehome] = :false - provider.expects(:execute).with(includes('-M')) + provider.expects(:execute).with(includes('-M'), kind_of(Hash)) provider.create end it "should not use -M flag if home is not managed and not on Redhat" do Facter.stubs(:value).with(:osfamily).returns("not RedHat") resource[:managehome] = :false - provider.expects(:execute).with(Not(includes('-M'))) + provider.expects(:execute).with(Not(includes('-M')), kind_of(Hash)) provider.create end end @@ -143,6 +233,7 @@ describe Puppet::Type.type(:user).provider(:useradd) do resource[:allowdupe] = :true resource[:managehome] = :true resource[:system] = :true + resource[:groups] = [ 'somegroup' ] end it "should call command with :add" do @@ -190,19 +281,86 @@ describe Puppet::Type.type(:user).provider(:useradd) do end end - it "should return an array with full command" do + it "should return an array with the full command and expiry as MM/DD/YY when on Solaris" do + Facter.stubs(:value).with(:operatingsystem).returns 'Solaris' described_class.expects(:system_users?).returns true - provider.stubs(:add_properties).returns(["-G", "somegroup"]) resource[:expiry] = "2012-08-18" + provider.addcmd.must == ['/usr/sbin/useradd', '-e', '08/18/2012', '-G', 'somegroup', '-o', '-m', '-r', 'myuser'] + end - provider.addcmd.must == ["/usr/sbin/useradd", "-G", "somegroup", "-o", "-m", '-e 2012-08-18', "-r", "myuser"] + it "should return an array with the full command and expiry as YYYY-MM-DD when not on Solaris" do + Facter.stubs(:value).with(:operatingsystem).returns 'not_solaris' + described_class.expects(:system_users?).returns true + resource[:expiry] = "2012-08-18" + provider.addcmd.must == ['/usr/sbin/useradd', '-e', '2012-08-18', '-G', 'somegroup', '-o', '-m', '-r', 'myuser'] end it "should return an array without -e if expiry is undefined full command" do described_class.expects(:system_users?).returns true - provider.stubs(:add_properties).returns(["-G", "somegroup"]) provider.addcmd.must == ["/usr/sbin/useradd", "-G", "somegroup", "-o", "-m", "-r", "myuser"] end + + it "should pass -e \"\" if the expiry has to be removed" do + described_class.expects(:system_users?).returns true + resource[:expiry] = :absent + + provider.addcmd.must == ['/usr/sbin/useradd', '-e', '', '-G', 'somegroup', '-o', '-m', '-r', 'myuser'] + end + end + + { + :password_min_age => 10, + :password_max_age => 20, + :password => '$6$FvW8Ib8h$qQMI/CR9m.QzIicZKutLpBgCBBdrch1IX0rTnxuI32K1pD9.RXZrmeKQlaC.RzODNuoUtPPIyQDufunvLOQWF0' + }.each_pair do |property, expected_value| + describe "##{property}" do + before :each do + resource # just to link the resource to the provider + end + + it "should return absent if libshadow feature is not present" do + Puppet.features.stubs(:libshadow?).returns false + # Shadow::Passwd.expects(:getspnam).never # if we really don't have libshadow we dont have Shadow::Passwd either + provider.send(property).should == :absent + end + + it "should return absent if user cannot be found", :if => Puppet.features.libshadow? do + Shadow::Passwd.expects(:getspnam).with('myuser').returns nil + provider.send(property).should == :absent + end + + it "should return the correct value if libshadow is present", :if => Puppet.features.libshadow? do + Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry + provider.send(property).should == expected_value + end + end + end + + describe '#expiry' do + before :each do + resource # just to link the resource to the provider + end + + it "should return absent if libshadow feature is not present" do + Puppet.features.stubs(:libshadow?).returns false + provider.expiry.should == :absent + end + + it "should return absent if user cannot be found", :if => Puppet.features.libshadow? do + Shadow::Passwd.expects(:getspnam).with('myuser').returns nil + provider.expiry.should == :absent + end + + it "should return absent if expiry is -1", :if => Puppet.features.libshadow? do + shadow_entry.sp_expire = -1 + Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry + provider.expiry.should == :absent + end + + it "should convert to YYYY-MM-DD", :if => Puppet.features.libshadow? do + Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry + provider.expiry.should == '2013-01-01' + end end describe "#passcmd" do diff --git a/spec/unit/resource/type_collection_spec.rb b/spec/unit/resource/type_collection_spec.rb index 72981fe4a..da8a3d024 100755 --- a/spec/unit/resource/type_collection_spec.rb +++ b/spec/unit/resource/type_collection_spec.rb @@ -167,11 +167,13 @@ describe Puppet::Resource::TypeCollection do @code.find_hostclass("foo", "bar").should == :foobar end - it "should not try to autoload names that we couldn't autoload in a previous step" do + it "should not try to autoload names that we couldn't autoload in a previous step if ignoremissingtypes is enabled" do + Puppet[:ignoremissingtypes] = true @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass").returns(nil) @code.loader.expects(:try_load_fqname).with(:hostclass, "klass").returns(nil) @code.find_hostclass("Ns", "Klass").should be_nil + Puppet.expects(:warning).at_least_once.with {|msg| msg =~ /Not attempting to load hostclass/} @code.find_hostclass("Ns", "Klass").should be_nil end end diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index e80e903d8..f891d5cd0 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -715,13 +715,13 @@ describe Puppet::Resource::Type do dest.doc.should == "foonessyayness" end - it "should turn its code into an ASTArray if necessary" do + it "should turn its code into a BlockExpression if necessary" do dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => code("foo")) source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar")) dest.merge(source) - dest.code.should be_instance_of(Puppet::Parser::AST::ASTArray) + dest.code.should be_instance_of(Puppet::Parser::AST::BlockExpression) end it "should set the other class's code as its code if it has none" do @@ -742,7 +742,7 @@ describe Puppet::Resource::Type do dest.merge(source) - dest.code.children.collect { |l| l.value }.should == %w{dest source} + dest.code.children.collect { |l| l[0].value }.should == %w{dest source} end end end diff --git a/spec/unit/scheduler/job_spec.rb b/spec/unit/scheduler/job_spec.rb new file mode 100644 index 000000000..c1d002cfc --- /dev/null +++ b/spec/unit/scheduler/job_spec.rb @@ -0,0 +1,79 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/scheduler' + +describe Puppet::Scheduler::Job do + let(:run_interval) { 10 } + let(:job) { described_class.new(run_interval) } + + it "has a minimum run interval of 0" do + Puppet::Scheduler::Job.new(-1).run_interval.should == 0 + end + + describe "when not run yet" do + it "is ready" do + job.ready?(2).should be + end + + it "gives the time to next run as 0" do + job.interval_to_next_from(2).should == 0 + end + end + + describe "when run at least once" do + let(:last_run) { 50 } + + before(:each) do + job.run(last_run) + end + + it "is ready when the time is greater than the last run plus the interval" do + job.ready?(last_run + run_interval + 1).should be + end + + it "is ready when the time is equal to the last run plus the interval" do + job.ready?(last_run + run_interval).should be + end + + it "is not ready when the time is less than the last run plus the interval" do + job.ready?(last_run + run_interval - 1).should_not be + end + + context "when calculating the next run" do + it "returns the run interval if now == last run" do + job.interval_to_next_from(last_run).should == run_interval + end + + it "when time is between the last and next runs gives the remaining portion of the run_interval" do + time_since_last_run = 2 + now = last_run + time_since_last_run + job.interval_to_next_from(now).should == run_interval - time_since_last_run + end + + it "when time is later than last+interval returns 0" do + time_since_last_run = run_interval + 5 + now = last_run + time_since_last_run + job.interval_to_next_from(now).should == 0 + end + end + end + + it "starts enabled" do + job.enabled?.should be + end + + it "can be disabled" do + job.disable + job.enabled?.should_not be + end + + it "has the job instance as a parameter" do + passed_job = nil + job = Puppet::Scheduler::Job.new(run_interval) do |j| + passed_job = j + end + job.run(5) + + passed_job.should eql(job) + end +end diff --git a/spec/unit/scheduler/scheduler_spec.rb b/spec/unit/scheduler/scheduler_spec.rb new file mode 100644 index 000000000..2a2971676 --- /dev/null +++ b/spec/unit/scheduler/scheduler_spec.rb @@ -0,0 +1,129 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/scheduler' + +describe Puppet::Scheduler::Scheduler do + let(:now) { 183550 } + let(:timer) { MockTimer.new(now) } + + class MockTimer + attr_reader :wait_for_calls + + def initialize(start=1729) + @now = start + @wait_for_calls = [] + end + + def wait_for(seconds) + @wait_for_calls << seconds + @now += seconds + end + + def now + @now + end + end + + def one_time_job(interval) + Puppet::Scheduler::Job.new(interval) { |j| j.disable } + end + + def disabled_job(interval) + job = Puppet::Scheduler::Job.new(interval) { |j| j.disable } + job.disable + job + end + + it "uses the minimum interval" do + later_job = one_time_job(7) + earlier_job = one_time_job(2) + scheduler = Puppet::Scheduler::Scheduler.new([later_job, earlier_job], timer) + later_job.last_run = now + earlier_job.last_run = now + + scheduler.run_loop + + timer.wait_for_calls.should == [2, 5] + end + + it "ignores disabled jobs when calculating intervals" do + enabled = one_time_job(7) + enabled.last_run = now + disabled = disabled_job(2) + scheduler = Puppet::Scheduler::Scheduler.new([enabled, disabled], timer) + + scheduler.run_loop + + timer.wait_for_calls.should == [7] + end + + it "asks the timer to wait for the job interval" do + job = one_time_job(5) + job.last_run = now + scheduler = Puppet::Scheduler::Scheduler.new([job], timer) + + scheduler.run_loop + + timer.wait_for_calls.should == [5] + end + + it "does not run when there are no jobs" do + timer = mock 'no run timer' + scheduler = Puppet::Scheduler::Scheduler.new([], timer) + + timer.stubs(:now).returns(now) + timer.expects(:wait_for).never + + scheduler.run_loop + end + + it "does not run when there are only disabled jobs" do + timer = mock 'no run timer' + disabled_job = Puppet::Scheduler::Job.new(0) + scheduler = Puppet::Scheduler::Scheduler.new([disabled_job], timer) + + disabled_job.disable + timer.stubs(:now).returns(now) + timer.expects(:wait_for).never + + scheduler.run_loop + end + + it "stops running when there are no more enabled jobs" do + timer = mock 'run once timer' + disabling_job = Puppet::Scheduler::Job.new(0) do |j| + j.disable + end + scheduler = Puppet::Scheduler::Scheduler.new([disabling_job], timer) + + timer.stubs(:now).returns(now) + timer.expects(:wait_for).once + + scheduler.run_loop + end + + it "marks the start of the run loop" do + disabled_job = Puppet::Scheduler::Job.new(0) + + disabled_job.disable + + scheduler = Puppet::Scheduler::Scheduler.new([disabled_job], timer) + scheduler.run_loop + + disabled_job.start_time.should == now + end + + it "calculates the next interval from the start of a job" do + countdown = 2 + slow_job = Puppet::Scheduler::Job.new(10) do |job| + timer.wait_for(3) + countdown -= 1 + job.disable if countdown == 0 + end + + scheduler = Puppet::Scheduler::Scheduler.new([slow_job], timer) + scheduler.run_loop + + timer.wait_for_calls.should == [0, 3, 7, 3] + end +end diff --git a/spec/unit/scheduler/splay_job_spec.rb b/spec/unit/scheduler/splay_job_spec.rb new file mode 100644 index 000000000..0f53f5175 --- /dev/null +++ b/spec/unit/scheduler/splay_job_spec.rb @@ -0,0 +1,35 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/scheduler' + +describe Puppet::Scheduler::SplayJob do + let(:run_interval) { 10 } + let(:last_run) { 50 } + let(:splay_limit) { 5 } + let(:start_time) { 23 } + let(:job) { described_class.new(run_interval, splay_limit) } + + it "does not apply a splay after the first run" do + job.run(last_run) + job.interval_to_next_from(last_run).should == run_interval + end + + it "calculates the first run splayed from the start time" do + job.start_time = start_time + + job.interval_to_next_from(start_time).should == job.splay + end + + it "interval to the next run decreases as time advances" do + time_passed = 3 + job.start_time = start_time + + job.interval_to_next_from(start_time + time_passed).should == job.splay - time_passed + end + + it "is not immediately ready if splayed" do + job.start_time = start_time + job.expects(:splay).returns(6) + job.ready?(start_time).should_not be + end +end diff --git a/spec/unit/settings/file_setting_spec.rb b/spec/unit/settings/file_setting_spec.rb index 33915ccd4..9782b247d 100755 --- a/spec/unit/settings/file_setting_spec.rb +++ b/spec/unit/settings/file_setting_spec.rb @@ -289,5 +289,11 @@ describe Puppet::Settings::FileSetting do @file.to_resource[:links].should == :follow end end -end + describe "#munge" do + it 'does not expand the path of the special value :memory: so we can set dblocation to an in-memory database' do + filesetting = FileSetting.new(:settings => mock("settings"), :desc => "eh") + filesetting.munge(':memory:').should == ':memory:' + end + end +end diff --git a/spec/unit/ssl/base_spec.rb b/spec/unit/ssl/base_spec.rb index 56e77409e..3ca5a7492 100755 --- a/spec/unit/ssl/base_spec.rb +++ b/spec/unit/ssl/base_spec.rb @@ -38,16 +38,10 @@ describe Puppet::SSL::Certificate do end describe "when determining a name from a certificate subject" do - it "should convert it to a string" do + it "should extract only the CN and not any other components" do subject = stub 'sub' - subject.expects(:to_s).returns('foo') - - @class.name_from_subject(subject).should == 'foo' - end - - it "should strip the prefix" do - subject = '/CN=foo' - @class.name_from_subject(subject).should == 'foo' + Puppet::Util::SSL.expects(:cn_from_subject).with(subject).returns 'host.domain.com' + @class.name_from_subject(subject).should == 'host.domain.com' end end diff --git a/spec/unit/ssl/certificate_authority_spec.rb b/spec/unit/ssl/certificate_authority_spec.rb index 3315243b1..9d007a312 100755 --- a/spec/unit/ssl/certificate_authority_spec.rb +++ b/spec/unit/ssl/certificate_authority_spec.rb @@ -1,4 +1,5 @@ #! /usr/bin/env ruby +# encoding: ASCII-8BIT require 'spec_helper' require 'puppet/ssl/certificate_authority' diff --git a/spec/unit/ssl/certificate_request_spec.rb b/spec/unit/ssl/certificate_request_spec.rb index 7dc41b9be..1dc449d53 100755 --- a/spec/unit/ssl/certificate_request_spec.rb +++ b/spec/unit/ssl/certificate_request_spec.rb @@ -31,7 +31,9 @@ describe Puppet::SSL::CertificateRequest do describe "when converting from a string" do it "should create a CSR instance with its name set to the CSR subject and its content set to the extracted CSR" do - csr = stub 'csr', :subject => "/CN=Foo.madstop.com", :is_a? => true + csr = stub 'csr', + :subject => OpenSSL::X509::Name.parse("/CN=Foo.madstop.com"), + :is_a? => true OpenSSL::X509::Request.expects(:new).with("my csr").returns(csr) mycsr = stub 'sslcsr' diff --git a/spec/unit/ssl/certificate_spec.rb b/spec/unit/ssl/certificate_spec.rb index b71a03766..70d35d4c7 100755 --- a/spec/unit/ssl/certificate_spec.rb +++ b/spec/unit/ssl/certificate_spec.rb @@ -26,7 +26,9 @@ describe Puppet::SSL::Certificate do describe "when converting from a string" do it "should create a certificate instance with its name set to the certificate subject and its content set to the extracted certificate" do - cert = stub 'certificate', :subject => "/CN=Foo.madstop.com", :is_a? => true + cert = stub 'certificate', + :subject => OpenSSL::X509::Name.parse("/CN=Foo.madstop.com"), + :is_a? => true OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(cert) mycert = stub 'sslcert' diff --git a/spec/unit/ssl/configuration_spec.rb b/spec/unit/ssl/configuration_spec.rb index 30eeab9bc..f4ea2890d 100644 --- a/spec/unit/ssl/configuration_spec.rb +++ b/spec/unit/ssl/configuration_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby # +require 'spec_helper' require 'puppet/ssl/configuration' describe Puppet::SSL::Configuration do @@ -40,6 +41,12 @@ describe Puppet::SSL::Configuration do it "#ca_auth_file == ssl_server_ca_auth" do subject.ca_auth_file.should == ssl_server_ca_auth end + it "#ca_auth_certificates returns an Array<OpenSSL::X509::Certificate>" do + subject.stubs(:read_file).returns(master_ca_pem + root_ca_pem) + + certs = subject.ca_auth_certificates + certs.each { |cert| cert.should be_a_kind_of OpenSSL::X509::Certificate } + end end context "Partially configured" do @@ -57,4 +64,71 @@ describe Puppet::SSL::Configuration do end end end + + # This is the Intermediate CA specifically designated for issuing master + # certificates. It is signed by the Root CA. + def master_ca_pem + @master_ca_pem ||= <<-AUTH_BUNDLE +-----BEGIN CERTIFICATE----- +MIICljCCAf+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 +IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs +ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH4xJDAi +BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ +ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa +MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwXDANBgkqhkiG9w0BAQEFAANLADBI +AkEAvo/az3oR69SP92jGnUHMJLEyyD1Ui1BZ/rUABJcQTRQqn3RqtlfYePWZnUaZ +srKbXRS4q0w5Vqf1kx5w3q5tIwIDAQABo4GcMIGZMHkGA1UdIwRyMHCAFDBN1mqO +Nc4gUraE4zRtw6ueFDDaoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQL +DBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IJ +ALf2Pk2HvtBzMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4GBACRfa1YPS7RQUuhYovGgV0VYqxuATC7WwdIRihVh5FceSXKgSIbz +BKmOBAy/KixEhpnHTbkpaJ0d9ITkvjMTmj3M5YMahKaQA5niVPckQPecMMd6jg9U +l1k75xLLIcrlsDYo3999KOSSchH2K7bLT7TuQ2okdP6FHWmeWmudewlu +-----END CERTIFICATE----- + AUTH_BUNDLE + end + + # This is the Root CA + def root_ca_pem + @root_ca_pem ||= <<-LOCALCACERT +-----BEGIN CERTIFICATE----- +MIICYDCCAcmgAwIBAgIJALf2Pk2HvtBzMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV +BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK +DBBFeGFtcGxlIE9yZywgTExDMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0 +OFowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv +bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAMGSpafR4lboYOPfPJC1wVHHl0gD49ZVRjOlJ9jidEUjBdFXK6SA +S1tecDv2G4tM1ANmfMKjZl0m+KaZ8O2oq0g6kxkq1Mg0eSNvlnEyehjmTLRzHC2i +a0biH2wMtCLzfAoXDKy4GPlciBPE9mup5I8Kien5s91t92tc7K8AJ8oBAgMBAAGj +UDBOMB0GA1UdDgQWBBQwTdZqjjXOIFK2hOM0bcOrnhQw2jAfBgNVHSMEGDAWgBQw +TdZqjjXOIFK2hOM0bcOrnhQw2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBACs8EZRrzgzAlcKC1Tz8GYlNHQg0XhpbEDm+p2mOV//PuDD190O+UBpWxo9Q +rrkkx8En0wXQZJf6iH3hwewwHLOq5yXZKbJN+SmvJvRNL95Yhyy08Y9N65tJveE7 +rPsNU/Tx19jHC87oXlmAePLI4IaUHXrWb7CRbY9TEcPdmj1R +-----END CERTIFICATE----- + LOCALCACERT + end + + # This is the intermediate CA designated to issue Agent SSL certs. It is + # signed by the Root CA. + def agent_ca_pem + @agent_ca_pem ||= <<-AGENT_CA +-----BEGIN CERTIFICATE----- +MIIClTCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 +IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs +ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH0xIzAh +BgNVBAMTGkludGVybWVkaWF0ZSBDQSAoYWdlbnQtY2EpMR8wHQYJKoZIhvcNAQkB +FhB0ZXN0QGV4YW1wbGUub3JnMRkwFwYDVQQKExBFeGFtcGxlIE9yZywgTExDMRow +GAYDVQQLExFTZXJ2ZXIgT3BlcmF0aW9uczBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC +QQDkEj/Msmi4hJImxP5+ocixMTHuYC1M1E2p4QcuzOkZYrfHf+5hJMcahfYhLiXU +jHBredOXhgSisHh6CLSb/rKzAgMBAAGjgZwwgZkweQYDVR0jBHIwcIAUME3Wao41 +ziBStoTjNG3Dq54UMNqhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0ExGjAYBgNVBAsM +EVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9yZywgTExDggkA +t/Y+TYe+0HMwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcN +AQEFBQADgYEAujSj9rxIxJHEuuYXb15L30yxs9Tdvy4OCLiKdjvs9Z7gG8Pbutls +ooCwyYAkmzKVs/8cYjZJnvJrPEW1gFwqX7Xknp85Cfrl+/pQEPYq5sZVa5BIm9tI +0EvlDax/Hd28jI6Bgq5fsTECNl9GDGknCy7vwRZem0h+hI56lzR3pYE= +-----END CERTIFICATE----- +AGENT_CA + end end diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index bdf184461..8240991c7 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -710,17 +710,38 @@ describe Puppet::SSL::Host do before do @crl = stub 'crl', :content => "real_crl" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl - Puppet[:certificate_revocation] = true end - it "should add the CRL" do - @store.expects(:add_crl).with "real_crl" - @host.ssl_store + describe "and 'certificate_revocation' is true" do + before do + Puppet[:certificate_revocation] = true + end + + it "should add the CRL" do + @store.expects(:add_crl).with "real_crl" + @host.ssl_store + end + + it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do + @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + @host.ssl_store + end end - it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do - @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK - @host.ssl_store + describe "and 'certificate_revocation' is false" do + before do + Puppet[:certificate_revocation] = false + end + + it "should not add the CRL" do + @store.expects(:add_crl).never + @host.ssl_store + end + + it "should not set the flags" do + @store.expects(:flags=).never + @host.ssl_store + end end end end diff --git a/spec/unit/ssl/validator_spec.rb b/spec/unit/ssl/validator_spec.rb new file mode 100644 index 000000000..fe0904cb8 --- /dev/null +++ b/spec/unit/ssl/validator_spec.rb @@ -0,0 +1,311 @@ +require 'spec_helper' +require 'puppet/ssl/validator' +require 'puppet/ssl/configuration' + +describe Puppet::SSL::Validator do + let(:ssl_context) do + mock('OpenSSL::X509::StoreContext') + end + + let(:ssl_configuration) do + Puppet::SSL::Configuration.new( + Puppet[:localcacert], + :ca_chain_file => Puppet[:ssl_client_ca_chain], + :ca_auth_file => Puppet[:ssl_client_ca_auth]) + end + + subject do + described_class.new(:ssl_configuration => ssl_configuration) + end + + before :each do + ssl_configuration.stubs(:read_file). + with(Puppet[:localcacert]). + returns(root_ca) + end + + describe '#call' do + before :each do + ssl_context.stubs(:current_cert).returns(*cert_chain_in_callback_order) + ssl_context.stubs(:chain).returns(cert_chain) + end + + context 'When pre-verification is not OK' do + context 'and the ssl_context is in an error state' do + before :each do + ssl_context.stubs(:error_string).returns("Something went wrong.") + end + + it 'makes the error available via #verify_errors' do + subject.call(false, ssl_context) + msg_suffix = OpenSSL::X509::Certificate.new(root_ca).subject + subject.verify_errors.should == ["Something went wrong. for #{msg_suffix}"] + end + end + end + + context 'When pre-verification is OK' do + context 'and the ssl_context is in an error state' do + before :each do + ssl_context.stubs(:error_string).returns("Something went wrong.") + end + it 'does not make the error available via #verify_errors' do + subject.call(true, ssl_context) + subject.verify_errors.should == [] + end + end + context 'and the chain is valid' do + it 'is true for each CA certificate in the chain' do + (cert_chain.length - 1).times do + subject.call(true, ssl_context).should be_true + end + end + it 'is true for the SSL certificate ending the chain' do + (cert_chain.length - 1).times do + subject.call(true, ssl_context) + end + subject.call(true, ssl_context).should be_true + end + end + context 'and the chain is invalid' do + before :each do + ssl_configuration.stubs(:read_file). + with(Puppet[:localcacert]). + returns(agent_ca) + end + it 'is true for each CA certificate in the chain' do + (cert_chain.length - 1).times do + subject.call(true, ssl_context).should be_true + end + end + it 'is false for the SSL certificate ending the chain' do + (cert_chain.length - 1).times do + subject.call(true, ssl_context) + end + subject.call(true, ssl_context).should be_false + end + end + context 'an error is raised inside of #call' do + before :each do + ssl_context.expects(:current_cert).raises(StandardError, "BOOM!") + end + it 'is false' do + subject.call(true, ssl_context).should be_false + end + it 'makes the error available through #verify_errors' do + subject.call(true, ssl_context) + subject.verify_errors.should == ["BOOM!"] + end + end + end + end + + describe '#register_verify_callback' do + it 'registers itself using #verify_callback' do + connection = mock('Net::HTTP') + connection.expects(:verify_callback=).with(subject) + subject.register_verify_callback(connection) + end + end + + describe '#valid_peer?' do + before :each do + peer_certs = cert_chain_in_callback_order.map do |c| + Puppet::SSL::Certificate.from_instance(c) + end + subject.instance_variable_set(:@peer_certs, peer_certs) + end + + context 'when the peer presents a valid chain' do + before :each do + subject.stubs(:has_authz_peer_cert).returns(true) + end + it 'is true' do + subject.valid_peer?.should be_true + end + end + context 'when the peer presents an invalid chain' do + before :each do + subject.stubs(:has_authz_peer_cert).returns(false) + end + it 'is false' do + subject.valid_peer?.should be_false + end + it 'makes a helpful error message available via #verify_errors' do + subject.valid_peer? + subject.verify_errors.should == [expected_authz_error_msg] + end + end + end + + describe '#has_authz_peer_cert' do + context 'when the Root CA is listed as authorized' do + it 'returns true when the SSL cert is issued by the Master CA' do + subject.has_authz_peer_cert(cert_chain, [root_ca_cert]).should be_true + end + it 'returns true when the SSL cert is issued by the Agent CA' do + subject.has_authz_peer_cert(cert_chain_agent_ca, [root_ca_cert]).should be_true + end + end + context 'when the Master CA is listed as authorized' do + it 'returns false when the SSL cert is issued by the Master CA' do + subject.has_authz_peer_cert(cert_chain, [master_ca_cert]).should be_true + end + it 'returns true when the SSL cert is issued by the Agent CA' do + subject.has_authz_peer_cert(cert_chain_agent_ca, [master_ca_cert]).should be_false + end + end + context 'when the Agent CA is listed as authorized' do + it 'returns true when the SSL cert is issued by the Master CA' do + subject.has_authz_peer_cert(cert_chain, [agent_ca_cert]).should be_false + end + it 'returns true when the SSL cert is issued by the Agent CA' do + subject.has_authz_peer_cert(cert_chain_agent_ca, [agent_ca_cert]).should be_true + end + end + end + + def root_ca + <<-ROOT_CA +-----BEGIN CERTIFICATE----- +MIICYDCCAcmgAwIBAgIJALf2Pk2HvtBzMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV +BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK +DBBFeGFtcGxlIE9yZywgTExDMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0 +OFowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv +bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAMGSpafR4lboYOPfPJC1wVHHl0gD49ZVRjOlJ9jidEUjBdFXK6SA +S1tecDv2G4tM1ANmfMKjZl0m+KaZ8O2oq0g6kxkq1Mg0eSNvlnEyehjmTLRzHC2i +a0biH2wMtCLzfAoXDKy4GPlciBPE9mup5I8Kien5s91t92tc7K8AJ8oBAgMBAAGj +UDBOMB0GA1UdDgQWBBQwTdZqjjXOIFK2hOM0bcOrnhQw2jAfBgNVHSMEGDAWgBQw +TdZqjjXOIFK2hOM0bcOrnhQw2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBACs8EZRrzgzAlcKC1Tz8GYlNHQg0XhpbEDm+p2mOV//PuDD190O+UBpWxo9Q +rrkkx8En0wXQZJf6iH3hwewwHLOq5yXZKbJN+SmvJvRNL95Yhyy08Y9N65tJveE7 +rPsNU/Tx19jHC87oXlmAePLI4IaUHXrWb7CRbY9TEcPdmj1R +-----END CERTIFICATE----- + ROOT_CA + end + + def master_ca + <<-MASTER_CA +-----BEGIN CERTIFICATE----- +MIICljCCAf+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 +IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs +ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH4xJDAi +BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ +ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa +MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwXDANBgkqhkiG9w0BAQEFAANLADBI +AkEAvo/az3oR69SP92jGnUHMJLEyyD1Ui1BZ/rUABJcQTRQqn3RqtlfYePWZnUaZ +srKbXRS4q0w5Vqf1kx5w3q5tIwIDAQABo4GcMIGZMHkGA1UdIwRyMHCAFDBN1mqO +Nc4gUraE4zRtw6ueFDDaoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQL +DBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IJ +ALf2Pk2HvtBzMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4GBACRfa1YPS7RQUuhYovGgV0VYqxuATC7WwdIRihVh5FceSXKgSIbz +BKmOBAy/KixEhpnHTbkpaJ0d9ITkvjMTmj3M5YMahKaQA5niVPckQPecMMd6jg9U +l1k75xLLIcrlsDYo3999KOSSchH2K7bLT7TuQ2okdP6FHWmeWmudewlu +-----END CERTIFICATE----- + MASTER_CA + end + + def agent_ca + <<-AGENT_CA +-----BEGIN CERTIFICATE----- +MIIClTCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 +IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs +ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH0xIzAh +BgNVBAMTGkludGVybWVkaWF0ZSBDQSAoYWdlbnQtY2EpMR8wHQYJKoZIhvcNAQkB +FhB0ZXN0QGV4YW1wbGUub3JnMRkwFwYDVQQKExBFeGFtcGxlIE9yZywgTExDMRow +GAYDVQQLExFTZXJ2ZXIgT3BlcmF0aW9uczBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC +QQDkEj/Msmi4hJImxP5+ocixMTHuYC1M1E2p4QcuzOkZYrfHf+5hJMcahfYhLiXU +jHBredOXhgSisHh6CLSb/rKzAgMBAAGjgZwwgZkweQYDVR0jBHIwcIAUME3Wao41 +ziBStoTjNG3Dq54UMNqhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0ExGjAYBgNVBAsM +EVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9yZywgTExDggkA +t/Y+TYe+0HMwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcN +AQEFBQADgYEAujSj9rxIxJHEuuYXb15L30yxs9Tdvy4OCLiKdjvs9Z7gG8Pbutls +ooCwyYAkmzKVs/8cYjZJnvJrPEW1gFwqX7Xknp85Cfrl+/pQEPYq5sZVa5BIm9tI +0EvlDax/Hd28jI6Bgq5fsTECNl9GDGknCy7vwRZem0h+hI56lzR3pYE= +-----END CERTIFICATE----- + AGENT_CA + end + + # Signed by the master CA (Good) + def master_issued_by_master_ca +<<-GOOD_SSL_CERT +-----BEGIN CERTIFICATE----- +MIICZzCCAhGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl +cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh +bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl +cnZlciBPcGVyYXRpb25zMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0OFow +HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUA +A0sAMEgCQQDACW8fryVZH0dC7vYUASonVBKYcILnKN2O9QX7RenZGN1TWek9LQxr +yQFDyp7WJ8jUw6nENGniLU8J+QSSxryjAgMBAAGjgdkwgdYwWwYDVR0jBFQwUqFN +pEswSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv +bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEOCAQIwDAYDVR0TAQH/BAIwADAL +BgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD0GA1Ud +EQQ2MDSCE21hc3RlcjEuZXhhbXBsZS5vcmeCB21hc3RlcjGCBnB1cHBldIIMcHVw +cGV0bWFzdGVyMA0GCSqGSIb3DQEBBQUAA0EAo8PvgLrah6jQVs6YCBxOTn13PDip +fVbcRsFd0dtIr00N61bCqr6Fa0aRwy424gh6bVJTNmk2zoaH7r025dZRhw== +-----END CERTIFICATE----- +GOOD_SSL_CERT + end + + # Signed by the agent CA, not the master CA (Rogue) + def master_issued_by_agent_ca +<<-BAD_SSL_CERT +-----BEGIN CERTIFICATE----- +MIICZjCCAhCgAwIBAgIBBDANBgkqhkiG9w0BAQUFADB9MSMwIQYDVQQDExpJbnRl +cm1lZGlhdGUgQ0EgKGFnZW50LWNhKTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt +cGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEaMBgGA1UECxMRU2Vy +dmVyIE9wZXJhdGlvbnMwHhcNMTMwMzMwMDU1MDQ4WhcNMzMwMzI1MDU1MDQ4WjAe +MRwwGgYDVQQDDBNtYXN0ZXIxLmV4YW1wbGUub3JnMFwwDQYJKoZIhvcNAQEBBQAD +SwAwSAJBAPnCDnryLLXWepGLqsdBWlytfeakE/yijM8GlE/yT0SbpJInIhJR1N1A +0RskriHrxTU5qQEhd0RIja7K5o4NYksCAwEAAaOB2TCB1jBbBgNVHSMEVDBSoU2k +SzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9u +czEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBATAMBgNVHRMBAf8EAjAAMAsG +A1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0R +BDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVwcGV0ggxwdXBw +ZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADQQA841IzHLlnn4RIJ0/BOZ/16iWC1dNr +jV9bELC5OxeMNSsVXbFNeTHwbHEYjDg5dQ6eUkxPdBSMWBeQwe2Mw+xG +-----END CERTIFICATE----- +BAD_SSL_CERT + end + + def cert_chain + [ master_issued_by_master_ca, master_ca, root_ca ].map do |pem| + OpenSSL::X509::Certificate.new(pem) + end + end + + def cert_chain_agent_ca + [ master_issued_by_agent_ca, agent_ca, root_ca ].map do |pem| + OpenSSL::X509::Certificate.new(pem) + end + end + + def cert_chain_in_callback_order + cert_chain.reverse + end + + let :authz_error_prefix do + "The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file. " + end + + let :expected_authz_error_msg do + authz_ca_certs = ssl_configuration.ca_auth_certificates + msg = authz_error_prefix + msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " + msg << "Peer Chain: #{cert_chain.collect {|c| c.subject}.join(' => ')}" + msg + end + + let :root_ca_cert do + OpenSSL::X509::Certificate.new(root_ca) + end + + let :master_ca_cert do + OpenSSL::X509::Certificate.new(master_ca) + end + + let :agent_ca_cert do + OpenSSL::X509::Certificate.new(agent_ca) + end +end diff --git a/spec/unit/transaction/event_manager_spec.rb b/spec/unit/transaction/event_manager_spec.rb index 11476ab34..610389c4c 100755 --- a/spec/unit/transaction/event_manager_spec.rb +++ b/spec/unit/transaction/event_manager_spec.rb @@ -102,6 +102,16 @@ describe Puppet::Transaction::EventManager do @manager.queue_events(@resource, [@event]) end + + it "should dequeue events for the changed resource if an event with invalidate_refreshes is processed" do + @event2 = Puppet::Transaction::Event.new(:name => :foo, :resource => @resource, :invalidate_refreshes => true) + + @graph.stubs(:matching_edges).returns [] + + @manager.expects(:dequeue_events_for_resource).with(@resource, :refresh) + + @manager.queue_events(@resource, [@event, @event2]) + end end describe "when queueing events for a resource" do @@ -258,4 +268,43 @@ describe Puppet::Transaction::EventManager do end end end + + describe "when queueing then processing events for a given resource" do + before do + @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) + @manager = Puppet::Transaction::EventManager.new(@transaction) + + @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") + @target = Puppet::Type.type(:file).new :path => make_absolute("/your/file") + + @graph = stub 'graph' + @graph.stubs(:matching_edges).returns [] + @graph.stubs(:matching_edges).with(anything, @resource).returns [stub('edge', :target => @target, :callback => :refresh)] + @manager.stubs(:relationship_graph).returns @graph + + @event = Puppet::Transaction::Event.new(:name => :notify, :resource => @target) + @event2 = Puppet::Transaction::Event.new(:name => :service_start, :resource => @target, :invalidate_refreshes => true) + end + + it "should succeed when there's no invalidated event" do + @manager.queue_events(@target, [@event2]) + end + + describe "and the events were dequeued/invalidated" do + before do + @resource.expects(:info).with { |msg| msg.include?("Scheduling refresh") } + @target.expects(:info).with { |msg| msg.include?("Unscheduling") } + end + + it "should not run an event or log" do + @target.expects(:notice).with { |msg| msg.include?("Would have triggered 'refresh'") }.never + @target.expects(:refresh).never + + @manager.queue_events(@resource, [@event]) + @manager.queue_events(@target, [@event2]) + @manager.process_events(@resource) + @manager.process_events(@target) + end + end + end end diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb index 579540d47..2a519368c 100755 --- a/spec/unit/transaction/event_spec.rb +++ b/spec/unit/transaction/event_spec.rb @@ -3,10 +3,19 @@ require 'spec_helper' require 'puppet/transaction/event' +class TestResource + def to_s + "Foo[bar]" + end + def [](v) + nil + end +end + describe Puppet::Transaction::Event do include PuppetSpec::Files - [:previous_value, :desired_value, :property, :resource, :name, :message, :file, :line, :tags, :audited].each do |attr| + [:previous_value, :desired_value, :property, :name, :message, :file, :line, :tags, :audited].each do |attr| it "should support #{attr}" do event = Puppet::Transaction::Event.new event.send(attr.to_s + "=", "foo") @@ -14,12 +23,18 @@ describe Puppet::Transaction::Event do end end + it "should support resource" do + event = Puppet::Transaction::Event.new + event.resource = TestResource.new + event.resource.should == "Foo[bar]" + end + it "should always convert the property to a string" do Puppet::Transaction::Event.new(:property => :foo).property.should == "foo" end it "should always convert the resource to a string" do - Puppet::Transaction::Event.new(:resource => :foo).resource.should == "foo" + Puppet::Transaction::Event.new(:resource => TestResource.new).resource.should == "Foo[bar]" end it "should produce the message when converted to a string" do @@ -99,17 +114,17 @@ describe Puppet::Transaction::Event do it "should use the source description as the source if one is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "/my/param" } - Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => "Foo[bar]", :property => "foo").send_log + Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => TestResource.new, :property => "foo").send_log end it "should use the property as the source if one is available and no source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "foo" } - Puppet::Transaction::Event.new(:resource => "Foo[bar]", :property => "foo").send_log + Puppet::Transaction::Event.new(:resource => TestResource.new, :property => "foo").send_log end it "should use the property as the source if one is available and no property or source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "Foo[bar]" } - Puppet::Transaction::Event.new(:resource => "Foo[bar]").send_log + Puppet::Transaction::Event.new(:resource => TestResource.new).send_log end end diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb index 69638694d..136642292 100755 --- a/spec/unit/transaction/report_spec.rb +++ b/spec/unit/transaction/report_spec.rb @@ -130,6 +130,14 @@ describe Puppet::Transaction::Report do report.exit_status.should == 4 end + it "should produce 4 if failures to restart are present" do + report = Puppet::Transaction::Report.new("apply") + report.add_metric("changes", {"total" => 0}) + report.add_metric("resources", {"failed" => 0}) + report.add_metric("resources", {"failed_to_restart" => 1}) + report.exit_status.should == 4 + end + it "should produce 6 if both changes and failures are present" do report = Puppet::Transaction::Report.new("apply") report.add_metric("changes", {"total" => 1}) diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb index 7e9aac459..7d80c9d94 100755 --- a/spec/unit/transaction/resource_harness_spec.rb +++ b/spec/unit/transaction/resource_harness_spec.rb @@ -104,11 +104,26 @@ describe Puppet::Transaction::ResourceHarness do false end end + + newproperty(:baz) do + desc "A property that raises an Exception (not StandardError) when you try to change it" + def sync + raise Exception.new('baz') + end + + def retrieve + :absent + end + + def insync?(reference_value) + false + end + end end stubProvider end - describe "when an error occurs" do + describe "when a caught error occurs" do before :each do stub_provider = make_stub_provider resource = stub_provider.new :name => 'name', :foo => 1, :bar => 2 @@ -127,6 +142,20 @@ describe Puppet::Transaction::ResourceHarness do end end + describe "when an Exception occurs during sync" do + before :each do + stub_provider = make_stub_provider + @resource = stub_provider.new :name => 'name', :baz => 1 + @resource.expects(:err).never + end + + it "should log and pass the exception through" do + lambda { @harness.evaluate(@resource) }.should raise_error(Exception, /baz/) + @logs.first.message.should == "change from absent to 1 failed: baz" + @logs.first.level.should == :err + end + end + describe "when auditing" do it "should not call insync? on parameters that are merely audited" do stub_provider = make_stub_provider diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb index d991bfe86..49e86dad6 100755 --- a/spec/unit/type/cron_spec.rb +++ b/spec/unit/type/cron_spec.rb @@ -50,6 +50,15 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? end end + describe "command" do + it "should discard leading spaces" do + described_class.new(:name => 'foo', :command => " /bin/true")[:command].should_not match Regexp.new(" ") + end + it "should discard trailing spaces" do + described_class.new(:name => 'foo', :command => "/bin/true ")[:command].should_not match Regexp.new(" ") + end + end + describe "minute" do it "should support absent" do expect { described_class.new(:name => 'foo', :minute => 'absent') }.to_not raise_error diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb index 9b62a73b4..09f3752c8 100755 --- a/spec/unit/type/exec_spec.rb +++ b/spec/unit/type/exec_spec.rb @@ -318,6 +318,17 @@ describe Puppet::Type.type(:exec) do end end + describe "when setting command" do + subject { described_class.new(:name => @command) } + it "fails when passed an Array" do + expect { subject[:command] = [] }.to raise_error Puppet::Error, /Command must be a String/ + end + + it "fails when passed a Hash" do + expect { subject[:command] = {} }.to raise_error Puppet::Error, /Command must be a String/ + end + end + describe "when setting refresh" do it_should_behave_like "all exec command parameters", :refresh end diff --git a/spec/unit/type/file/content_spec.rb b/spec/unit/type/file/content_spec.rb index 1fb21966f..0dfcf4e2f 100755 --- a/spec/unit/type/file/content_spec.rb +++ b/spec/unit/type/file/content_spec.rb @@ -162,36 +162,36 @@ describe content do describe "and the file exists" do before do @resource.stubs(:stat).returns mock("stat") + @content.should = "some content" end it "should return false if the current contents are different from the desired content" do - @content.should = "some content" @content.should_not be_safe_insync("other content") end it "should return true if the sum for the current contents is the same as the sum for the desired content" do - @content.should = "some content" @content.must be_safe_insync("{md5}" + Digest::MD5.hexdigest("some content")) end - describe "and Puppet[:show_diff] is set" do - before do - Puppet[:show_diff] = true - end - - it "should display a diff if the current contents are different from the desired content" do - @content.should = "some content" - @content.expects(:diff).returns("my diff").once - @content.expects(:notice).with("\nmy diff").once - - @content.safe_insync?("other content") - end - - it "should not display a diff if the sum for the current contents is the same as the sum for the desired content" do - @content.should = "some content" - @content.expects(:diff).never - - @content.safe_insync?("{md5}" + Digest::MD5.hexdigest("some content")) + [true, false].product([true, false]).each do |cfg, param| + describe "and Puppet[:show_diff] is #{cfg} and show_diff => #{param}" do + before do + Puppet[:show_diff] = cfg + @resource.stubs(:show_diff?).returns param + end + + if cfg and param + it "should display a diff" do + @content.expects(:diff).returns("my diff").once + @content.expects(:notice).with("\nmy diff").once + @content.should_not be_safe_insync("other content") + end + else + it "should not display a diff" do + @content.expects(:diff).never + @content.should_not be_safe_insync("other content") + end + end end end end diff --git a/spec/unit/type/file/mode_spec.rb b/spec/unit/type/file/mode_spec.rb index fb6cd9913..dc43ae498 100755 --- a/spec/unit/type/file/mode_spec.rb +++ b/spec/unit/type/file/mode_spec.rb @@ -138,5 +138,11 @@ describe Puppet::Type.type(:file).attrclass(:mode) do mode.is_to_s('1755').should == '1755' end end + + describe 'when passed :absent' do + it 'returns :absent' do + mode.is_to_s(:absent).should == :absent + end + end end end diff --git a/spec/unit/type/file/source_spec.rb b/spec/unit/type/file/source_spec.rb index a1d7e5363..36cad8849 100755 --- a/spec/unit/type/file/source_spec.rb +++ b/spec/unit/type/file/source_spec.rb @@ -93,6 +93,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do describe "when returning the metadata" do before do @metadata = stub 'metadata', :source= => nil + @resource.stubs(:[]).with(:links).returns :manage end it "should return already-available metadata" do @@ -108,22 +109,22 @@ describe Puppet::Type.type(:file).attrclass(:source) do it "should collect its metadata using the Metadata class if it is not already set" do @source = source.new(:resource => @resource, :value => @foobar) - Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment).returns @metadata + Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment, :links => :manage).returns @metadata @source.metadata end it "should use the metadata from the first found source" do metadata = stub 'metadata', :source= => nil @source = source.new(:resource => @resource, :value => [@foobar, @feebooz]) - Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment).returns nil - Puppet::FileServing::Metadata.indirection.expects(:find).with(@feebooz_uri, :environment => @environment).returns metadata + Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment, :links => :manage).returns nil + Puppet::FileServing::Metadata.indirection.expects(:find).with(@feebooz_uri, :environment => @environment, :links => :manage).returns metadata @source.metadata.should equal(metadata) end it "should store the found source as the metadata's source" do metadata = mock 'metadata' @source = source.new(:resource => @resource, :value => @foobar) - Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment).returns metadata + Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment, :links => :manage).returns metadata metadata.expects(:source=).with(@foobar_uri) @source.metadata @@ -131,7 +132,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do it "should fail intelligently if an exception is encountered while querying for metadata" do @source = source.new(:resource => @resource, :value => @foobar) - Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment).raises RuntimeError + Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment, :links => :manage).raises RuntimeError @source.expects(:fail).raises ArgumentError lambda { @source.metadata }.should raise_error(ArgumentError) @@ -139,7 +140,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do it "should fail if no specified sources can be found" do @source = source.new(:resource => @resource, :value => @foobar) - Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment).returns nil + Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, :environment => @environment, :links => :manage).returns nil @source.expects(:fail).raises RuntimeError @@ -321,7 +322,8 @@ describe Puppet::Type.type(:file).attrclass(:source) do before(:each) do metadata = Puppet::FileServing::Metadata.new(path, :source => uri, 'type' => 'file') #metadata = stub('remote', :ftype => "file", :source => uri) - Puppet::FileServing::Metadata.indirection.stubs(:find).with(uri, has_key(:environment)).returns metadata + Puppet::FileServing::Metadata.indirection.stubs(:find). + with(uri,all_of(has_key(:environment), has_key(:links))).returns metadata resource[:source] = uri end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index d71c041a8..b4bd68566 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -856,20 +856,39 @@ describe Puppet::Type.type(:file) do describe "#remove_existing" do it "should do nothing if the file doesn't exist" do - file.remove_existing(:file).should == nil + file.remove_existing(:file).should == false end it "should fail if it can't backup the file" do - file.stubs(:stat).returns stub('stat') + file.stubs(:stat).returns stub('stat', :ftype => 'file') file.stubs(:perform_backup).returns false expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up; will not replace/) end + describe "backing up directories" do + it "should not backup directories if force is false" do + file[:force] = false + file.stubs(:stat).returns stub('stat', :ftype => 'directory') + file.expects(:perform_backup).never + file.remove_existing(:file).should == false + end + + it "should backup directories if force is true" do + file[:force] = true + FileUtils.expects(:rmtree).with(file[:path]) + + file.stubs(:stat).returns stub('stat', :ftype => 'directory') + file.expects(:perform_backup).once.returns(true) + + file.remove_existing(:file).should == true + end + end + it "should not do anything if the file is already the right type and not a link" do file.stubs(:stat).returns stub('stat', :ftype => 'file') - file.remove_existing(:file).should == nil + file.remove_existing(:file).should == false end it "should not remove directories and should not invalidate the stat unless force is set" do diff --git a/spec/unit/type/service_spec.rb b/spec/unit/type/service_spec.rb index e9a4640e9..630b44b75 100755 --- a/spec/unit/type/service_spec.rb +++ b/spec/unit/type/service_spec.rb @@ -49,47 +49,60 @@ describe Puppet::Type.type(:service), "when validating attribute values" do svc.should(:ensure).should == :stopped end - it "should support :true as a value to :enable" do - Puppet::Type.type(:service).new(:name => "yay", :enable => :true) - end + describe "the enable property" do + before :each do + @provider.class.stubs(:supports_parameter?).returns true + end + it "should support :true as a value" do + srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :true) + srv.should(:enable).should == :true + end - it "should support :false as a value to :enable" do - Puppet::Type.type(:service).new(:name => "yay", :enable => :false) - end + it "should support :false as a value" do + srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :false) + srv.should(:enable).should == :false + end - it "should support :manual as a value to :enable on Windows" do - Puppet.features.stubs(:microsoft_windows?).returns true + it "should support :manual as a value on Windows" do + Puppet.features.stubs(:microsoft_windows?).returns true - Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) - end + srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) + srv.should(:enable).should == :manual + end - it "should not support :manual as a value to :enable when not on Windows" do - Puppet.features.stubs(:microsoft_windows?).returns false + it "should not support :manual as a value when not on Windows" do + Puppet.features.stubs(:microsoft_windows?).returns false - expect { Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) }.to raise_error( - Puppet::Error, - /Setting enable to manual is only supported on Microsoft Windows\./ - ) + expect { Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) }.to raise_error( + Puppet::Error, + /Setting enable to manual is only supported on Microsoft Windows\./ + ) + end end it "should support :true as a value to :hasstatus" do - Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :true) + srv = Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :true) + srv[:hasstatus].should == :true end it "should support :false as a value to :hasstatus" do - Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :false) + srv = Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :false) + srv[:hasstatus].should == :false end it "should specify :true as the default value of hasstatus" do - Puppet::Type.type(:service).new(:name => "yay")[:hasstatus].should == :true + srv = Puppet::Type.type(:service).new(:name => "yay") + srv[:hasstatus].should == :true end it "should support :true as a value to :hasrestart" do - Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :true) + srv = Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :true) + srv[:hasrestart].should == :true end it "should support :false as a value to :hasrestart" do - Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :false) + srv = Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :false) + srv[:hasrestart].should == :false end it "should allow setting the :enable parameter if the provider has the :enableable feature" do diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 21ecc5ab9..8b19020c6 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -356,7 +356,23 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do end it "should fail if any invalid attributes have been provided" do - expect { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.to raise_error(Puppet::Error) + expect { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.to raise_error(Puppet::Error, /Invalid parameter/) + end + + context "when an attribute fails validation" do + it "should fail with Puppet::ResourceError when PuppetError raised" do + expect { Puppet::Type.type(:file).new(:title => "/foo", :source => "unknown:///") }.to raise_error(Puppet::ResourceError, /Parameter source failed on File\[.*foo\]/) + end + + it "should fail with Puppet::ResourceError when ArgumentError raised" do + expect { Puppet::Type.type(:file).new(:title => "/foo", :mode => "abcdef") }.to raise_error(Puppet::ResourceError, /Parameter mode failed on File\[.*foo\]/) + end + + it "should include the file/line in the error" do + Puppet::Type.type(:file).any_instance.stubs(:file).returns("example.pp") + Puppet::Type.type(:file).any_instance.stubs(:line).returns(42) + expect { Puppet::Type.type(:file).new(:title => "/foo", :source => "unknown:///") }.to raise_error(Puppet::ResourceError, /example.pp:42/) + end end it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do @@ -419,6 +435,35 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do it "should delete the name via the namevar from the originally provided parameters" do Puppet::Type.type(:file).new(:name => make_absolute('/foo')).original_parameters[:path].should be_nil end + + context "when validating the resource" do + it "should call the type's validate method if present" do + Puppet::Type.type(:file).any_instance.expects(:validate) + Puppet::Type.type(:file).new(:name => make_absolute('/foo')) + end + + it "should raise Puppet::ResourceError with resource name when Puppet::Error raised" do + expect do + Puppet::Type.type(:file).new( + :name => make_absolute('/foo'), + :source => "puppet:///", + :content => "foo" + ) + end.to raise_error(Puppet::ResourceError, /Validation of File\[.*foo.*\]/) + end + + it "should raise Puppet::ResourceError with manifest file and line on failure" do + Puppet::Type.type(:file).any_instance.stubs(:file).returns("example.pp") + Puppet::Type.type(:file).any_instance.stubs(:line).returns(42) + expect do + Puppet::Type.type(:file).new( + :name => make_absolute('/foo'), + :source => "puppet:///", + :content => "foo" + ) + end.to raise_error(Puppet::ResourceError, /Validation.*example.pp:42/) + end + end end it "should have a class method for converting a hash into a Puppet::Resource instance" do diff --git a/spec/unit/util/backups_spec.rb b/spec/unit/util/backups_spec.rb index f025d6eae..30fd7b9c8 100755 --- a/spec/unit/util/backups_spec.rb +++ b/spec/unit/util/backups_spec.rb @@ -66,7 +66,7 @@ describe Puppet::Util::Backups do it "should fail when the old backup can't be removed" do File.expects(:lstat).with(backup).returns stub("stat", :ftype => "file") - File.expects(:unlink).raises ArgumentError + File.expects(:unlink).with(backup).raises ArgumentError FileUtils.expects(:cp_r).never FileTest.expects(:exists?).with(path).returns(true) @@ -75,7 +75,7 @@ describe Puppet::Util::Backups do it "should not try to remove backups that don't exist" do File.expects(:lstat).with(backup).raises(Errno::ENOENT) - File.expects(:unlink).never + File.expects(:unlink).with(backup).never FileUtils.stubs(:cp_r) FileTest.expects(:exists?).with(path).returns(true) diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb index 4390ebaf7..71af8f01f 100755 --- a/spec/unit/util/execution_spec.rb +++ b/spec/unit/util/execution_spec.rb @@ -44,6 +44,9 @@ describe Puppet::Util::Execution do Puppet::Util::SUIDManager.stubs(:change_user) Puppet::Util::SUIDManager.stubs(:change_group) + # ensure that we don't really close anything! + (0..256).each {|n| IO.stubs(:new) } + $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) @@ -576,7 +579,7 @@ describe Puppet::Util::Execution do it "should raise an error if a nil option is specified" do expect { Puppet::Util::Execution.execute('fail command', nil) - }.to raise_error(TypeError, /can\'t convert nil into Hash/) + }.to raise_error(TypeError, /(can\'t convert|no implicit conversion of) nil into Hash/) end end end diff --git a/spec/unit/util/filetype_spec.rb b/spec/unit/util/filetype_spec.rb index 88cf723f7..ecf7c3690 100755 --- a/spec/unit/util/filetype_spec.rb +++ b/spec/unit/util/filetype_spec.rb @@ -100,6 +100,12 @@ describe Puppet::Util::FileType do type.should_not be_nil end + # make Puppet::Util::SUIDManager return something deterministic, not the + # uid of the user running the tests, except where overridden below. + before :each do + Puppet::Util::SUIDManager.stubs(:uid).returns 1234 + end + describe "#read" do it "should run crontab -l as the target user" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], user_options).returns crontab diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb index 8776b4e74..7d725cc7d 100755 --- a/spec/unit/util/monkey_patches_spec.rb +++ b/spec/unit/util/monkey_patches_spec.rb @@ -94,6 +94,24 @@ describe Array do [1,2,3].drop(3).should == [] end end + + describe "#respond_to?" do + it "should return true for a standard method (each)" do + [].respond_to?(:each).should be_true + end + + it "should return false for to_hash" do + [].respond_to?(:to_hash).should be_false + end + + it "should accept one argument" do + lambda { [].respond_to?(:each) }.should_not raise_error + end + + it "should accept two arguments" do + lambda { [].respond_to?(:each, false) }.should_not raise_error + end + end end describe IO do diff --git a/spec/unit/util/network_device/cisco/device_spec.rb b/spec/unit/util/network_device/cisco/device_spec.rb index bd4489da3..2fc0c1180 100755 --- a/spec/unit/util/network_device/cisco/device_spec.rb +++ b/spec/unit/util/network_device/cisco/device_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'puppet/util/network_device/cisco/device' +require 'puppet/util/network_device/transport/telnet' describe Puppet::Util::NetworkDevice::Cisco::Device do before(:each) do @@ -16,10 +17,46 @@ describe Puppet::Util::NetworkDevice::Cisco::Device do cisco.enable_password.should == "enable_password" end + describe "decoding the enable password" do + it "should not parse a password if no query is given" do + cisco = described_class.new("telnet://user:password@localhost:23") + cisco.enable_password.should be_nil + end + + it "should not parse a password if no enable param is given" do + cisco = described_class.new("telnet://user:password@localhost:23/?notenable=notapassword") + cisco.enable_password.should be_nil + end + it "should decode sharps" do + cisco = described_class.new("telnet://user:password@localhost:23/?enable=enable_password%23with_a_sharp") + cisco.enable_password.should == "enable_password#with_a_sharp" + end + + it "should decode spaces" do + cisco = described_class.new("telnet://user:password@localhost:23/?enable=enable_password%20with_a_space") + cisco.enable_password.should == "enable_password with_a_space" + end + + it "should only use the query parameter" do + cisco = described_class.new("telnet://enable=:password@localhost:23/?enable=enable_password¬enable=notapassword") + cisco.enable_password.should == "enable_password" + end + end + it "should find the enable password from the options" do cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password", :enable_password => "mypass") cisco.enable_password.should == "mypass" end + + it "should find the debug mode from the options" do + Puppet::Util::NetworkDevice::Transport::Telnet.expects(:new).with(true).returns(@transport) + cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23", :debug => true) + end + + it "should set the debug mode to nil by default" do + Puppet::Util::NetworkDevice::Transport::Telnet.expects(:new).with(nil).returns(@transport) + cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23") + end end describe "when connecting to the physical device" do diff --git a/spec/unit/util/network_device/config_spec.rb b/spec/unit/util/network_device/config_spec.rb index eea3de2dc..25ff6b220 100755 --- a/spec/unit/util/network_device/config_spec.rb +++ b/spec/unit/util/network_device/config_spec.rb @@ -105,6 +105,20 @@ describe Puppet::Util::NetworkDevice::Config do @config.read @config.devices['router.puppetlabs.com'].url.should == 'ssh://test/' end + + it "should parse the debug mode" do + @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/', 'debug') + + @config.read + @config.devices['router.puppetlabs.com'].options.should == { :debug => true } + end + + it "should set the debug mode to false by default" do + @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/') + + @config.read + @config.devices['router.puppetlabs.com'].options.should == { :debug => false } + end end end diff --git a/spec/unit/util/network_device_spec.rb b/spec/unit/util/network_device_spec.rb index 57c26674b..beb6a7ea5 100644 --- a/spec/unit/util/network_device_spec.rb +++ b/spec/unit/util/network_device_spec.rb @@ -7,7 +7,7 @@ require 'puppet/util/network_device' describe Puppet::Util::NetworkDevice do before(:each) do - @device = OpenStruct.new(:name => "name", :provider => "test") + @device = OpenStruct.new(:name => "name", :provider => "test", :url => "telnet://admin:password@127.0.0.1", :options => { :debug => false }) end after(:each) do @@ -16,7 +16,7 @@ describe Puppet::Util::NetworkDevice do class Puppet::Util::NetworkDevice::Test class Device - def initialize(device) + def initialize(device, options) end end end @@ -29,7 +29,7 @@ describe Puppet::Util::NetworkDevice do it "should create a network device instance" do Puppet::Util::NetworkDevice.stubs(:require) - Puppet::Util::NetworkDevice::Test::Device.expects(:new) + Puppet::Util::NetworkDevice::Test::Device.expects(:new).with("telnet://admin:password@127.0.0.1", :debug => false) Puppet::Util::NetworkDevice.init(@device) end diff --git a/spec/unit/util/profiler/logging_spec.rb b/spec/unit/util/profiler/logging_spec.rb new file mode 100644 index 000000000..5316e5ae9 --- /dev/null +++ b/spec/unit/util/profiler/logging_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' +require 'puppet/util/profiler' + +describe Puppet::Util::Profiler::Logging do + let(:logger) { SimpleLog.new } + let(:identifier) { "Profiling ID" } + let(:profiler) { TestLoggingProfiler.new(logger, identifier) } + + it "returns the value of the profiled segment" do + retval = profiler.profile("Testing") { "the return value" } + + retval.should == "the return value" + end + + it "propogates any errors raised in the profiled segment" do + expect do + profiler.profile("Testing") { raise "a problem" } + end.to raise_error("a problem") + end + + it "logs the explanation of the profile results" do + profiler.profile("Testing") { } + + logger.messages.first.should =~ /the explanation/ + end + + it "logs results even when an error is raised" do + begin + profiler.profile("Testing") { raise "a problem" } + rescue + logger.messages.first.should =~ /the explanation/ + end + end + + it "describes the profiled segment" do + profiler.profile("Tested measurement") { } + + logger.messages.first.should =~ /PROFILE \[#{identifier}\] \d Tested measurement/ + end + + it "indicates the order in which segments are profiled" do + profiler.profile("Measurement") { } + profiler.profile("Another measurement") { } + + logger.messages[0].should =~ /1 Measurement/ + logger.messages[1].should =~ /2 Another measurement/ + end + + it "indicates the nesting of profiled segments" do + profiler.profile("Measurement") { profiler.profile("Nested measurement") { } } + profiler.profile("Another measurement") { profiler.profile("Another nested measurement") { } } + + logger.messages[0].should =~ /1.1 Nested measurement/ + logger.messages[1].should =~ /1 Measurement/ + logger.messages[2].should =~ /2.1 Another nested measurement/ + logger.messages[3].should =~ /2 Another measurement/ + end + + class TestLoggingProfiler < Puppet::Util::Profiler::Logging + def start + "the start" + end + + def finish(context) + "the explanation of #{context}" + end + end + + class SimpleLog + attr_reader :messages + + def initialize + @messages = [] + end + + def call(msg) + @messages << msg + end + end +end + diff --git a/spec/unit/util/profiler/none_spec.rb b/spec/unit/util/profiler/none_spec.rb new file mode 100644 index 000000000..0cabfef6f --- /dev/null +++ b/spec/unit/util/profiler/none_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' +require 'puppet/util/profiler' + +describe Puppet::Util::Profiler::None do + let(:profiler) { Puppet::Util::Profiler::None.new } + + it "returns the value of the profiled block" do + retval = profiler.profile("Testing") { "the return value" } + + retval.should == "the return value" + end +end diff --git a/spec/unit/util/profiler/object_counts_spec.rb b/spec/unit/util/profiler/object_counts_spec.rb new file mode 100644 index 000000000..e95fe8253 --- /dev/null +++ b/spec/unit/util/profiler/object_counts_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' +require 'puppet/util/profiler' + +describe Puppet::Util::Profiler::ObjectCounts do + it "reports the changes in the system object counts" do + pending("Can only count objects on ruby 1.9 or greater", :if => RUBY_VERSION < '1.9') do + profiler = Puppet::Util::Profiler::ObjectCounts.new(nil, nil) + + message = profiler.finish(profiler.start) + + message.should =~ / T_STRING: \d+, / + end + end +end diff --git a/spec/unit/util/profiler/wall_clock_spec.rb b/spec/unit/util/profiler/wall_clock_spec.rb new file mode 100644 index 000000000..668f63221 --- /dev/null +++ b/spec/unit/util/profiler/wall_clock_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' +require 'puppet/util/profiler' + +describe Puppet::Util::Profiler::WallClock do + + it "logs the number of seconds it took to execute the segment" do + profiler = Puppet::Util::Profiler::WallClock.new(nil, nil) + + message = profiler.finish(profiler.start) + + message.should =~ /took \d\.\d{4} seconds/ + end +end diff --git a/spec/unit/util/pson_spec.rb b/spec/unit/util/pson_spec.rb index 89d78ea01..e0d79cda6 100755 --- a/spec/unit/util/pson_spec.rb +++ b/spec/unit/util/pson_spec.rb @@ -63,4 +63,9 @@ describe Puppet::Util::Pson do s = ["\xc3\xc3"] PSON.parse( [s].to_pson ).should == [s] end + + it "should be able to parse JSON containing UTF-8 characters in strings" do + s = '{ "foö": "bár" }' + lambda { PSON.parse s }.should_not raise_error + end end diff --git a/spec/unit/util/ssl_spec.rb b/spec/unit/util/ssl_spec.rb new file mode 100644 index 000000000..17c0362a7 --- /dev/null +++ b/spec/unit/util/ssl_spec.rb @@ -0,0 +1,92 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'openssl' +require 'puppet/util/ssl' + +describe Puppet::Util::SSL do + def parse(dn) + Puppet::Util::SSL.subject_from_dn(dn) + end + + describe "when getting a subject from a DN" do + RSpec::Matchers.define :be_a_subject_with do |expected| + match do |actual| + parts = actual.to_a.map { |part| part[0..1] }.flatten + Hash[*parts] == expected + end + end + + NO_PARTS = {} + + it "parses a DN with a single part" do + parse('CN=client.example.org').should be_a_subject_with({ + 'CN' => 'client.example.org' + }) + end + + it "parses a DN with parts separated by slashes" do + parse('/CN=Root CA/OU=Server Operations/O=Example Org').should be_a_subject_with({ + 'CN' => 'Root CA', + 'OU' => 'Server Operations', + 'O' => 'Example Org' + }) + end + + it "parses a DN with a single part preceeded by a slash" do + parse('/CN=client.example.org').should be_a_subject_with({ + 'CN' => 'client.example.org' + }) + end + + it "parses a DN with parts separated by commas" do + parse('O=Foo\, Inc,CN=client2a.example.org').should be_a_subject_with({ + 'O' => 'Foo, Inc', + 'CN' => 'client2a.example.org' + }) + end + + it "finds no parts in something that is not a DN" do + parse('(no)').should be_a_subject_with(NO_PARTS) + end + + it "finds no parts in a DN with an invalid part" do + parse('no=yes,CN=Root CA').should be_a_subject_with(NO_PARTS) + end + + it "finds no parts in an empty DN" do + parse('').should be_a_subject_with(NO_PARTS) + end + end + + describe "when getting a CN from a subject" do + def cn_from(subject) + Puppet::Util::SSL.cn_from_subject(subject) + end + + it "should correctly parse a subject containing only a CN" do + subj = parse('/CN=foo') + cn_from(subj).should == 'foo' + end + + it "should correctly parse a subject containing other components" do + subj = parse('/CN=Root CA/OU=Server Operations/O=Example Org') + cn_from(subj).should == 'Root CA' + end + + it "should correctly parse a subject containing other components with CN not first" do + subj = parse('/emailAddress=foo@bar.com/CN=foo.bar.com/O=Example Org') + cn_from(subj).should == 'foo.bar.com' + end + + it "should return nil for a subject with no CN" do + subj = parse('/OU=Server Operations/O=Example Org') + cn_from(subj).should == nil + end + + it "should return nil for a bare string" do + cn_from("/CN=foo").should == nil + end + end +end + diff --git a/spec/unit/util/windows/root_certs_spec.rb b/spec/unit/util/windows/root_certs_spec.rb new file mode 100755 index 000000000..155707e4e --- /dev/null +++ b/spec/unit/util/windows/root_certs_spec.rb @@ -0,0 +1,15 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/util/windows' + +describe "Puppet::Util::Windows::RootCerts", :if => Puppet::Util::Platform.windows? do + let(:klass) { Puppet::Util::Windows::RootCerts } + let(:x509) { 'mycert' } + + context '#each' do + it "should enumerate each root cert" do + klass.expects(:load_certs).returns([x509]) + klass.instance.to_a.should == [x509] + end + end +end diff --git a/spec/unit/util_spec.rb b/spec/unit/util_spec.rb index fa338d18f..8bcc7b8ac 100755 --- a/spec/unit/util_spec.rb +++ b/spec/unit/util_spec.rb @@ -223,6 +223,9 @@ describe Puppet::Util do $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) + + # ensure that we don't really close anything! + (0..256).each {|n| IO.stubs(:new) } end it "should close all open file descriptors except stdin/stdout/stderr" do @@ -526,4 +529,29 @@ describe Puppet::Util do subject.execute(command) end end + + describe "#deterministic_rand" do + + it "should not fiddle with future rand calls" do + Puppet::Util.deterministic_rand(123,20) + rand_one = rand() + Puppet::Util.deterministic_rand(123,20) + rand().should_not eql(rand_one) + end + + if defined?(Random) == 'constant' && Random.class == Class + it "should not fiddle with the global seed" do + srand(1234) + Puppet::Util.deterministic_rand(123,20) + srand().should eql(1234) + end + # ruby below 1.9.2 variant + else + it "should set a new global seed" do + srand(1234) + Puppet::Util.deterministic_rand(123,20) + srand().should_not eql(1234) + end + end + end end |
