diff options
| author | Daniel Pittman <daniel@puppetlabs.com> | 2011-05-06 11:08:35 -0700 |
|---|---|---|
| committer | Daniel Pittman <daniel@puppetlabs.com> | 2011-05-06 11:08:35 -0700 |
| commit | f80afbe72b848fe4ed81d8116d4eeb494aa6f61e (patch) | |
| tree | 73c7f27f2ea2fb1668f7067ce05638f5064f540d | |
| parent | 1b12b55b6a2d3581f9643bf09d55727ba1213580 (diff) | |
| parent | b983386ece1b9816e6d3d59a316ad589e35773df (diff) | |
| download | puppet-f80afbe72b848fe4ed81d8116d4eeb494aa6f61e.tar.gz puppet-f80afbe72b848fe4ed81d8116d4eeb494aa6f61e.tar.xz puppet-f80afbe72b848fe4ed81d8116d4eeb494aa6f61e.zip | |
Merge branch '2.7.x' into 2.7.next
Conflicts:
* spec/unit/node/facts_spec.rb
Updates:
* spec/unit/interface/action{,_builder}_spec.rb
=> update for 'when_invoked' block being required.
122 files changed, 2737 insertions, 1028 deletions
@@ -1,3 +1,166 @@ +2.7.0rc2 +=== +61edff9 (#7353) Remove :for_humans format entirely. +d2b5ec6 Adding test for ticket 7139 +6f2a129 add clean-up step to test for ticket_5477 to prevent site.pp from leaking to other tests +92905ff fixed test for ticket_7117 +5076c37 (#7179) Modify default ACL for /node/<name>. +7b71745 (Maint) Adjust documentation whitespace +358418c (#7303) Remove reference to not-yet-extant man action +dce072a (#6962) Add self-documentation data to puppet faces +855a0ec Maint: adjust faces.rb's help to match that of other applications +75f164a (#7304) Remove full puppet help output when subcommand cannot be found +8a8e198 adding test for ticket 7117 +dda3264 (#7353) Note the :for_humans compatibility issue. +efd1181 (#7353) Use :console rendering format in our own code. +5986e8a (#7353) Unify rendering in the face_bace application. +a4e735e (#7353) Add 'console' format to FormatHandler +94f0b09 add base test for ticket 7117 +dc0088f (#7277)Fixing all secret-agent functions, and the agent itself +1f112cd (#7139) Accept '/' as a valid path in filesets +18b3584 (#7329) Consistent naming for rendering formats and hooks. +8f81f56 (#7326) Fix faces on Ruby 1.8.5 +5569fad (#7117) Return the environment as a Puppet::Node::Environment in uri2indirection +5120a95 (#7276) Better reporting from the plugin download action. +bb889bf (#7276) Create a plugin face application. +5490f7a (#6962) Added 'returns' block to action documentation. +0d6ac04 maint: remove emacs 'coding' cookie from files. +48903f5 (#7278) Improve utility of the Catalog select action +45adc1a (#7279) Adding some basic file actions +a4a274b (#7315) Fix `to_pson` method to render correctly. +1b42725 (#7314) Faces fail horribly when one has a syntax error. +86c6ec2 maint: move the indirector face base out of puppet/face +c63e9c2 maint: reset more global state in testing faces... +b20e977 (#7304) Improve help from `puppet foo` +3bb8bd3 (#7317) better error handling in CLI face facade. +b23cc8a (#7282) action without `when_invoked` should fail... +040e0fd (Maint.) Fix accidental debug output in tests. +65b9a3c (#7221) Strip bad whitespace from face and action docs. +97ae812 (#7248) Fail if two aliases of one option are passed... +207deae (#7289) Specify order for option decorations. +1707f27 Revert "maint: better error reporting for argument count mismatch." +cd474b0 maint: better error reporting for argument count mismatch. +69e4b1c (#7122) Enforce call arity on actions in the CLI wrapper. +351b6fc maint: add a 'print' matcher to rspec, to inspect std{out,err} +be4d7e6 (#7269) Fix error reporting for bad render formats... +632a0a0 (#7269) Better error reporting for bad render formats. +80adaea (#7160) Support 'json' as a rendering method for CLI faces. +0256d67 (#6962) Add integration tests on Face documentation. +e8eb290 (#6962) Finish documentation API on Face options. +6e152ad (#6962) Give copyright and license for all faces. +b8525c9 (#6962) Fill out documentation on Faces and Actions +59e7ef1 (#6962) Move documentation support into a module. +092ab09 (#6962) Extend documentation API for Faces. +c627fad (#7251) Let exceptions raised in decorators rise. +bbf777f (#7249) Publicize ActionBuilder DSL methods. +95ed9aa add test for ticket 7101 +6064e8e (#7101) Fix template error messages in Ruby 1.8.5 +49c5152 (#7137) Get rid of spurious info messages in useradd +349bd96 Fix test ticket_6928_puppet_master_parse_fails +f25acf9 maint: add the 'to', 'not_to', and 'to_not' aliases to rspec... +f77304b (#7157) Return a non-zero exit code on face failure. +435c826 maint: use the exit_with helper everywhere... +96195c1 maint: add an "exit was called" matcher for rspec. +822d530 maint: clean up test headers on face spec files. +5c24541 Fix #7084 Make the log messages produced by whits less confusing +c7a0270 (#7121) Download plugins and upload reports in secret agent! +2a2226c Revert "Fixing Facts pson methods more resilient" +7591de7 maint: fix a race in catalog compilation versioning. +aaf7e23 Revert "(7080) Adding json support to Indirector Request" +17d176b Revert "Adding json support to Puppet::Node" +27e0831 (#7181) Rename configurer face to secret_agent. +de2199f (#6928) Don't blow up when the method is undefined... +a0de328 (#6928) backport Symbol#to_proc for Ruby < 1.8.7 +f17f6bb (#7183) Implement "invisible glob" version matching for faces +7414ee7 maint: better disabling of Signal#trap in our tests. +03bd559 maint: more robust listing of valid faces. +7db4793 maint: clean up testing code a fraction... +eeb8236 maint: better error report for a missing version of a face. +677752d maint: handle face clear/reset sanely in the interface spec. +7b3744c maint: stop stubbing log level setting. +220f308 Move tests from Puppet-acceptance repo +379b46d (#7116) Handle application-level options in parse_options +a1db585 maint: fix gratuitous whitespace in the code. +601baf1 maint: remove redundant context from the test. +5d7ef5c (#7062) better argument handling in the action wrapper methods +33b5580 maint: move method comments outside the comment. +c87d6c9 Fixed #7166 - Replaced deprecated stomp "send" method with "publish" +557767b maint: Remove unused faces code +311e3ec maint: mangle grammer in rspec to avoid Jenkins fail... +0fed94f (#7013) Wire up rendering hooks on the CLI. +12098f2 (#7013) Handle rendering modes out in the application layer. +5938452 (#7013) Strip out old face-wide rendering defaults. +5258e06 (#7013) Return bound methods for when_rendering hooks. +86801b5 (#7013) Support 'when_rendering' and 'render_as' in actions. +be23b84 (#7013) better default rendering support for faces +e6caa24 maint: make sure we don't ever default to being default... +36b100a maint: print 'false' in the default render method. +2cf692c maint: delete README.strings, which is out of date. +22355dc maint: test the 'help' face has the default action 'help' +4efba71 maint: drop multi-version support from action loading. +266f937 (#6962) Add 'description' to faces and action. +32c667c (#7132) Reject 'summary' text with newlines embedded. +1251311 (#7108) Update help/man text for puppet kick +eeb1b60 (#7108) Modernize description of --listen in defaults.rb +3ec9526 Maint: puppetmaster -> puppet master in defaults.rb +5a10093 (Maint) Fix a leaking spec, patching intermittent failures. +07b677c Merge remote-tracking branch 'community/feature/puppet-device' into 2.7.x +13e64fe (#7131) Remove support for optional arguments to options +977684e (Maint) Fixing an order-dependent failure. +9d2ec21 (#7013) Add support for required options. +eeb82f8 (Maint) Code cleanup. +d85c2a8 maint: Fix the missed failure from the previous commit +e946a17 maint: Fix a broken Puppet::Node::Facts spec +d80500f maint: speed up testing output of the help face. +9264526 (#7115) Enable default actions. +ab541fa (#7059) Use option hooks for the indirector terminus option. +f770325 (#6978) Enforce the calling convention of option hooks. +c00e03d (#7059) Set the CA location using option hooks. +dca1f07 (#6978) Add before and after decorators to actions from options. +0c60aa2 maint: delete an empty describe block containing no tests. +e424740 Adding json-specific matchers +f37b2e1 Making watchr resilient to syntax errors in tests +d3c94e6 Adding json support to Puppet::Node +155b16d Fixing a failing test resulting from a fixed bug +e0615cb (7080) Adding json support to Indirector Request +07a7a68 Fixing Facts pson methods more resilient +ff08ba2 (7118) Adding summaries for all faces +a509821 Cleanup trailing whitespace +5528911 (#7111) Clarify scoping deprecation warning +ca9d68f (#6408) Update puppet cert help for new subcommand action syntax. +174e87a (#4258) Fix pkgutil spec test to have the correct provider +e119739 (#6928) Add a notice to Parser#validate action when using default +9bc4bce (#7103) Fix HEAD requests in the HTTP handler +cb552af (#4258) Remove superfluous command check that called pkgutil +fd98341 (#4258) Fix fd leak opening pkgutil config files +7726dc3 (#4258) Permit variations of -nv in both pkgutil.conf files +f8c2f1a (#4258) Stop file and config checks from breaking spec +ef86105 (#4258) Check wgetopts in pkgutil.conf +557ed85 (#4258) Fix hash duplication affecting canonical provider instance +7c99dd9 (#4258) Use pkgutil -a to reliably determine package common names/aliases +ab5bc35 (#4258) Update pkgutil spec for recent impl changes +e582709 (#4258) pkgutil: bug fix: if shortname is not equal to package name +58ac7d3 (#4258) pkgutil provider: better handling of short package names +15a53f0 (#4258) pkgutil provider: misc enhancements +15e225b Add spec tests for pkgutil package provider +8462acd * Fix exception on parse failure of pkgutil output * Fix exception when querying latest version for unknown package +3eace85 Fixing indentation +f8e9155 Removing blastwave references and unused PAGER +485ac38 Changing indentation to 2-spaces as per 2.6+ style +9d63171 Single package queries made more robust when dealing with pkgutil noise +f50fac7 Fixing wget verbose regex +3003719 These regular expressions will not match anything. pkgutil doesn't output anything that can be matched. +2725fb3 Add comments that explain what we are ignoring in the package and remove legacy output +143fc74 Ignoring lines from use_gpg and catalog fetching +69a3451 Adding patch from Rudy Gevaert to fix not installed detection +d026bb7 pkgutil provider: Using the --single option which speeds up execution. +ec2a03c pkgutil provider: The path to the admin file is /var/opt/csw/pkgutil/admin +0fc2aa6 pkgutil provider: Correcting a typo in a message. +e02ba01 Using --single in the pkgutil provider. +fc18591 Adding pkgutil support. +9f365b1 Fixed #4258 - Added pkgutil package provider + + 2.7.0rc1 ==== 5915814 Revert "(#6928) Removed --ignoreimport" diff --git a/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb b/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb index d05735e50..792e88b46 100644 --- a/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb +++ b/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb @@ -41,3 +41,6 @@ agents.each { |agent| fail_test "Site.pp not detect at Master?" unless stdout.include? 'ticket_5477_notify' } + +step "Clean-up site.pp" +on master, "rm /etc/puppet/manifests/site.pp" diff --git a/acceptance/tests/ticket_6928_puppet_master_parse_fails.rb b/acceptance/tests/ticket_6928_puppet_master_parse_fails.rb index aac53138a..155e91d3f 100644 --- a/acceptance/tests/ticket_6928_puppet_master_parse_fails.rb +++ b/acceptance/tests/ticket_6928_puppet_master_parse_fails.rb @@ -7,8 +7,7 @@ create_remote_file(master, '/tmp/bad.pp', 'notify{bad:') step "Master: use --parseonly on an invalid manifest, should return 1 and issue deprecation warning" on master, puppet_master( %w{--parseonly /tmp/bad.pp} ), :acceptable_exit_codes => [ 1 ] - fail_test "Deprecation warning not issued for --parseonly" unless - stdout.include? '--parseonly has been removed. Please use \'puppet parser validate <manifest>\'' + assert_match(/--parseonly has been removed. Please use \'puppet parser validate <manifest>\'/, stdout, "Deprecation warning not issued for --parseonly on #{master}" ) step "Agents: create valid, invalid formatted manifests" agents.each do |host| @@ -19,8 +18,7 @@ end step "Agents: use --parseonly on an invalid manifest, should return 1 and issue deprecation warning" agents.each do |host| on(host, "puppet --parseonly /tmp/bad.pp}", :acceptable_exit_codes => [ 1 ]) do - fail_test "Deprecation warning not issued for --parseonly" unless - stdout.include? '--parseonly has been removed. Please use \'puppet parser validate <manifest>\'' + assert_match(/--parseonly has been removed. Please use \'puppet parser validate <manifest>\'/, stdout, "Deprecation warning not issued for --parseonly on #{host}" ) end end @@ -29,10 +27,9 @@ agents.each do |host| on(host, "puppet parser validate /tmp/good.pp", :acceptable_exit_codes => [ 0 ]) end -step "Test Face for ‘parser validate’ with bad manifest -- should fail" +step "Test Faces for ‘parser validate’ with bad manifest -- should fail" agents.each do |host| on(host, "puppet parser validate /tmp/bad.pp", :acceptable_exit_codes => [ 1 ]) do - fail_test "Bad manifest detection failed" unless - stderr.include? 'Could not run: Could not parse for environment production' + assert_match(/err: Could not parse for environment production/, stdout, "Bad manifest detection failed on #{host}" ) end end diff --git a/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb b/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb new file mode 100644 index 000000000..76a6bf818 --- /dev/null +++ b/acceptance/tests/ticket_7117_broke_env_criteria_authconf.rb @@ -0,0 +1,33 @@ +test_name "#7117 Broke the environment criteria in auth.conf" + +# add to auth.conf +add_2_authconf = %q{ +path / +environment override +auth any +allow * +} + +step "Save original auth.conf file and create a temp auth.conf" +on master, "cp #{config['puppetpath']}/auth.conf /tmp/auth.conf-7117; echo '#{add_2_authconf}' > #{config['puppetpath']}/auth.conf" + +# Kill running Puppet Master -- should not be running at this point +step "Master: kill running Puppet Master" +on master, "ps -U puppet | awk '/puppet/ { print \$1 }' | xargs kill || echo \"Puppet Master not running\"" +step "Master: Start Puppet Master" +on master, puppet_master("--certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --noop") + + +# Run test on Agents +step "Agent: agent --test" +on agents, puppet_agent("--test") + +step "Fetch agent facts from Puppet Master" +agents.each do |host| + on(host, "curl -k -H \"Accept: yaml\" https://#{master}:8140/override/facts/\`hostname -f\`") do + assert_match(/--- !ruby\/object:Puppet::Node::Facts/, stdout, "Agent Facts not returned for #{host}") + end +end + +step "Restore original auth.conf file" +on master, "cp -f /tmp/auth.conf-7117 #{config['puppetpath']}/auth.conf" diff --git a/acceptance/tests/ticket_7139_puppet_resource_file_qualified_paths.rm b/acceptance/tests/ticket_7139_puppet_resource_file_qualified_paths.rm new file mode 100644 index 000000000..f773ba17c --- /dev/null +++ b/acceptance/tests/ticket_7139_puppet_resource_file_qualified_paths.rm @@ -0,0 +1,11 @@ +test_name "#7139: Puppet resource file failes on path with leading '/'" + +step "Agents: create valid, invalid formatted manifests" +create_remote_file(agents, '/tmp/ticket-7139', %w{foo bar contents} ) + +step "Run puppet file resource on /tmp/ticket-7139" +agents.each do |host| + on(host, "puppet resource file /tmp/ticket-7139") do + assert_match(/file \{ \'\/tmp\/ticket-7139\':/, stdout, "puppet resource file failed on #{host}") + end +end diff --git a/conf/auth.conf b/conf/auth.conf index 431e4b205..cb202a989 100644 --- a/conf/auth.conf +++ b/conf/auth.conf @@ -53,6 +53,11 @@ path ~ ^/catalog/([^/]+)$ method find allow $1 +# allow nodes to retrieve their own node definition +path ~ ^/node/([^/]+)$ +method find +allow $1 + # allow all nodes to access the certificates services path /certificate_revocation_list/ca method find diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb index 7bebd18bb..7a5ce3400 100644 --- a/lib/puppet/application/face_base.rb +++ b/lib/puppet/application/face_base.rb @@ -15,8 +15,8 @@ class Puppet::Application::FaceBase < Puppet::Application Puppet::Util::Log.level = :info end - option("--render-as FORMAT") do |arg| - @render_as = arg.to_sym + option("--render-as FORMAT") do |format| + self.render_as = format.to_sym end option("--mode RUNMODE", "-r") do |arg| @@ -27,55 +27,23 @@ class Puppet::Application::FaceBase < Puppet::Application attr_accessor :face, :action, :type, :arguments, :render_as - attr_writer :exit_code - # This allows you to set the exit code if you don't want to just exit - # immediately but you need to indicate a failure. - def exit_code - @exit_code || 0 + def render_as=(format) + if format == :json then + @render_as = Puppet::Network::FormatHandler.format(:pson) + else + @render_as = Puppet::Network::FormatHandler.format(format) + end + @render_as or raise ArgumentError, "I don't know how to render '#{format}'" end def render(result) - format = render_as || action.render_as || :for_humans - # Invoke the rendering hook supplied by the user, if appropriate. - if hook = action.when_rendering(format) then + if hook = action.when_rendering(render_as.name) result = hook.call(result) end - if format == :for_humans then - render_for_humans(result) - else - render_method = Puppet::Network::FormatHandler.format(format).render_method - if render_method == "to_pson" - PSON::pretty_generate(result, :allow_nan => true, :max_nesting => false) - else - result.send(render_method) - end - end - end - - def render_for_humans(result) - # String to String - return result if result.is_a? String - return result if result.is_a? Numeric - - # Simple hash to table - if result.is_a? Hash and result.keys.all? { |x| x.is_a? String or x.is_a? Numeric } - output = '' - column_a = result.map do |k,v| k.to_s.length end.max + 2 - column_b = 79 - column_a - result.sort_by { |k,v| k.to_s } .each do |key, value| - output << key.to_s.ljust(column_a) - output << PP.pp(value, '', column_b). - chomp.gsub(/\n */) { |x| x + (' ' * column_a) } - output << "\n" - end - return output - end - - # ...or pretty-print the inspect outcome. - return result.pretty_inspect + render_as.render(result) end def preinit @@ -133,8 +101,13 @@ class Puppet::Application::FaceBase < Puppet::Application end if @action.nil? - @action = @face.get_default_action() - @is_default_action = true + if @action = @face.get_default_action() then + @is_default_action = true + else + Puppet.err "#{face.name} does not have a default action, and no action was given" + Puppet.err Puppet::Face[:help, :current].help(@face.name) + exit false + end end # Now we can interact with the default option code to build behaviour @@ -142,7 +115,7 @@ class Puppet::Application::FaceBase < Puppet::Application @action.options.each do |option| option = @action.get_option(option) # make it the object. self.class.option(*option.optparse) # ...and make the CLI parse it. - end if @action + end # ...and invoke our parent to parse all the command line options. super @@ -194,23 +167,66 @@ class Puppet::Application::FaceBase < Puppet::Application # would invoke the action with options set as global state in the # interface object. --daniel 2011-03-28 @arguments << options + + # If we don't have a rendering format, set one early. + self.render_as ||= (@action.render_as || :console) end def main + status = false + # Call the method associated with the provided action (e.g., 'find'). - if @action - result = @face.send(@action.name, *arguments) - puts render(result) unless result.nil? - else - if arguments.first.is_a? Hash - puts "#{@face} does not have a default action" - else - puts "#{@face} does not respond to action #{arguments.first}" - end + unless @action + puts Puppet::Face[:help, :current].help(@face.name) + raise "#{face} does not respond to action #{arguments.first}" + end - puts Puppet::Face[:help, :current].help(@face.name, *arguments) + # We need to do arity checking here because this is generic code + # calling generic methods – that have argument defaulting. We need to + # make sure we don't accidentally pass the options as the first + # argument to a method that takes one argument. eg: + # + # puppet facts find + # => options => {} + # @arguments => [{}] + # => @face.send :bar, {} + # + # def face.bar(argument, options = {}) + # => bar({}, {}) # oops! we thought the options were the + # # positional argument!! + # + # We could also fix this by making it mandatory to pass the options on + # every call, but that would make the Ruby API much more annoying to + # work with; having the defaulting is a much nicer convention to have. + # + # We could also pass the arguments implicitly, by having a magic + # 'options' method that was visible in the scope of the action, which + # returned the right stuff. + # + # That sounds attractive, but adds complications to all sorts of + # things, especially when you think about how to pass options when you + # are writing Ruby code that calls multiple faces. Especially if + # faces are involved in that. ;) + # + # --daniel 2011-04-27 + if (arity = @action.positional_arg_count) > 0 + unless (count = arguments.length) == arity then + s = arity == 2 ? '' : 's' + raise ArgumentError, "puppet #{@face.name} #{@action.name} takes #{arity-1} argument#{s}, but you gave #{count-1}" + end end - exit(exit_code) + + result = @face.send(@action.name, *arguments) + puts render(result) unless result.nil? + status = true + + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err detail.to_s + Puppet.err "Try 'puppet help #{@face.name} #{@action.name}' for usage" + + ensure + exit status end end diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb index 3dd3f0312..e7fce66b1 100644 --- a/lib/puppet/application/faces.rb +++ b/lib/puppet/application/faces.rb @@ -10,16 +10,54 @@ class Puppet::Application::Faces < Puppet::Application Puppet::Util::Log.level = :debug end - option("--help", "-h") do |arg| - puts "Usage: puppet faces [actions|terminuses] -Lists all available faces, and by default includes all available terminuses and actions. -" - end - option("--verbose", "-v") do Puppet::Util::Log.level = :info end + def help + <<-HELP +puppet-faces(8) -- List available Faces and actions +======== + +SYNOPSIS +-------- +Lists the available subcommands (with applicable terminuses and/or actions) +provided by the Puppet Faces API. This information is automatically read +from the Puppet code present on the system. By default, the output includes +all terminuses and actions. + +USAGE +----- +puppet faces [-d|--debug] [-v|--verbose] [actions|terminuses] + +OPTIONS +------- +Note that any configuration option valid in the configuration file is also +a valid long argument. 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'. + +* --verbose: + Sets the log level to "info." This option has no tangible effect at the time + of this writing. + +* --debug: + Sets the log level to "debug." This option has no tangible effect at the time + of this writing. + +AUTHOR +------ +Puppet Labs + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License + + HELP + end + def list(*arguments) if arguments.empty? arguments = %w{terminuses actions} diff --git a/lib/puppet/application/help.rb b/lib/puppet/application/help.rb index 0d7767632..4829a2036 100644 --- a/lib/puppet/application/help.rb +++ b/lib/puppet/application/help.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- require 'puppet/application/face_base' class Puppet::Application::Help < Puppet::Application::FaceBase diff --git a/lib/puppet/application/plugin.rb b/lib/puppet/application/plugin.rb new file mode 100644 index 000000000..2d0402e43 --- /dev/null +++ b/lib/puppet/application/plugin.rb @@ -0,0 +1,3 @@ +require 'puppet/application/face_base' +class Puppet::Application::Plugin < Puppet::Application::FaceBase +end diff --git a/lib/puppet/application/configurer.rb b/lib/puppet/application/secret_agent.rb index 6e86cd2d4..d704d14b2 100644 --- a/lib/puppet/application/configurer.rb +++ b/lib/puppet/application/secret_agent.rb @@ -1,7 +1,7 @@ require 'puppet/application' require 'puppet/face' -class Puppet::Application::Configurer < Puppet::Application +class Puppet::Application::Secret_agent < Puppet::Application should_parse_config run_mode :agent @@ -17,7 +17,7 @@ class Puppet::Application::Configurer < Puppet::Application end def run_command - report = Puppet::Face[:configurer, '0.0.1'].synchronize(Puppet[:certname]) + report = Puppet::Face[:secret_agent, '0.0.1'].synchronize(Puppet[:certname]) Puppet::Face[:report, '0.0.1'].submit(report) end end diff --git a/lib/puppet/face/catalog.rb b/lib/puppet/face/catalog.rb index 0dcde3591..4624313bc 100644 --- a/lib/puppet/face/catalog.rb +++ b/lib/puppet/face/catalog.rb @@ -1,8 +1,56 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' + +Puppet::Indirector::Face.define(:catalog, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Compile, save, view, and convert catalogs." + description <<-EOT + This face primarily interacts with the compiling subsystem. By default, + it compiles a catalog using the default manifest and the hostname from + `certname`, but you can choose to retrieve a catalog from the server by + specifying `--terminus rest`. You can also choose to print any catalog + in 'dot' format (for easy graph viewing with OmniGraffle or Graphviz) + with '--render-as dot'. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `active_record` + * `compiler` + * `queue` + * `rest` + * `yaml` + EOT -Puppet::Face::Indirector.define(:catalog, '0.0.1') do action(:apply) do - when_invoked do |catalog, options| + summary "Apply a Puppet::Resource::Catalog object" + description <<-EOT + Applies a catalog object retrieved with the `download` action. This + action cannot consume a serialized catalog, and is not intended for + command-line use." + EOT + notes <<-EOT + This action returns a Puppet::Transaction::Report object. + EOT + examples <<-EOT + From `secret_agent.rb`: + + Puppet::Face[:plugin, '0.0.1'].download + + facts = Puppet::Face[:facts, '0.0.1'].find(certname) + catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) + + Puppet::Face[:report, '0.0.1'].submit(report) + EOT + + when_invoked do |options| + catalog = Puppet::Face[:catalog, "0.0.1"].find(Puppet[:certname]) or raise "Could not find catalog for #{Puppet[:certname]}" + catalog = catalog.to_ral + report = Puppet::Transaction::Report.new("apply") report.configuration_version = catalog.version @@ -23,18 +71,38 @@ Puppet::Face::Indirector.define(:catalog, '0.0.1') do end action(:download) do - when_invoked do |certname, facts, options| + summary "Download this node's catalog from the puppet master server" + description <<-EOT + Retrieves a catalog from the puppet master. Unlike the `find` action, + `download` submits facts to the master as part of the request. This + action is not intended for command-line use. + EOT + notes "This action returns a Puppet::Resource::Catalog object." + examples <<-EOT + From `secret_agent.rb`: + + Puppet::Face[:plugin, '0.0.1'].download + + facts = Puppet::Face[:facts, '0.0.1'].find(certname) + catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) + + Puppet::Face[:report, '0.0.1'].submit(report) + EOT + when_invoked do |options| Puppet::Resource::Catalog.indirection.terminus_class = :rest - facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} + Puppet::Resource::Catalog.indirection.cache_class = nil catalog = nil retrieval_duration = thinmark do - catalog = Puppet::Face[:catalog, '0.0.1'].find(certname, facts_to_upload) + catalog = Puppet::Face[:catalog, '0.0.1'].find(Puppet[:certname]) end - catalog = catalog.to_ral - catalog.finalize catalog.retrieval_duration = retrieval_duration catalog.write_class_file - catalog + + Puppet::Resource::Catalog.indirection.terminus_class = :yaml + Puppet::Face[:catalog, "0.0.1"].save(catalog) + Puppet.notice "Saved catalog for #{Puppet[:certname]} to yaml" + nil end end end diff --git a/lib/puppet/face/catalog/select.rb b/lib/puppet/face/catalog/select.rb index ba27117bc..a6c97a627 100644 --- a/lib/puppet/face/catalog/select.rb +++ b/lib/puppet/face/catalog/select.rb @@ -1,10 +1,42 @@ # Select and show a list of resources of a given type. Puppet::Face.define(:catalog, '0.0.1') do action :select do + summary "Select and show a list of resources of a given type" + description <<-EOT + Retrieves a catalog for the specified host and returns an array of + resources of the given type. This action is not intended for + command-line use. + EOT + notes <<-NOTES + The type name for this action must be given in its capitalized form. + That is, calling `catalog select mynode file` will return an empty + array, whereas calling it with 'File' will return a list of the node's + file resources. + + By default, this action will retrieve a catalog from Puppet's compiler + subsystem; you must call the action with `--terminus rest` if you wish + to retrieve a catalog from the puppet master. + NOTES when_invoked do |host, type, options| + # REVISIT: Eventually, type should have a default value that triggers + # the non-specific behaviour. For now, though, this will do. + # --daniel 2011-05-03 catalog = Puppet::Resource::Catalog.indirection.find(host) - catalog.resources.reject { |res| res.type != type }.each { |res| puts res } + if type == '*' + catalog.resources + else + type = type.downcase + catalog.resources.reject { |res| res.type.downcase != type } + end + end + + when_rendering :console do |value| + if value.nil? then + "no matching resources found" + else + value.map {|x| x.to_s }.join("\n") + end end end end diff --git a/lib/puppet/face/certificate.rb b/lib/puppet/face/certificate.rb index 4c2950fb3..ee2b2873f 100644 --- a/lib/puppet/face/certificate.rb +++ b/lib/puppet/face/certificate.rb @@ -1,15 +1,51 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' require 'puppet/ssl/host' -Puppet::Face::Indirector.define(:certificate, '0.0.1') do +Puppet::Indirector::Face.define(:certificate, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Provide access to the CA for certificate management" + description <<-EOT + This face interacts with a local or remote Puppet certificate + authority. Currently, its behavior is not a full superset of puppet + cert; specifically, it is unable to mimic puppet cert's "clean" option, + and its "generate" action submits a CSR rather than creating a + signed certificate. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `ca` + * `file` + * `rest` + EOT + option "--ca-location LOCATION" do + summary "The certificate authority to query" + description <<-EOT + Whether to act on the local certificate authority or one provided by a + remote puppet master. Allowed values are 'local' and 'remote.' + EOT + before_action do |action, args, options| Puppet::SSL::Host.ca_location = options[:ca_location].to_sym end end action :generate do - summary "Generate a new Certificate Signing Request for HOST" + summary "Generate a new certificate signing request for HOST" + description <<-EOT + Generates and submits a certificate signing request (CSR) for the + provided host identifier. This CSR will then have to be signed by a user + with the proper authorization on the certificate authority. + + Puppet agent handles CSR submission automatically. This action is + primarily useful for requesting certificates for individual users and + external applications. + EOT when_invoked do |name, options| host = Puppet::SSL::Host.new(name) @@ -19,7 +55,7 @@ Puppet::Face::Indirector.define(:certificate, '0.0.1') do end action :list do - summary "List all Certificate Signing Requests" + summary "List all certificate signing requests" when_invoked do |options| Puppet::SSL::Host.indirection.search("*", { @@ -29,7 +65,7 @@ Puppet::Face::Indirector.define(:certificate, '0.0.1') do end action :sign do - summary "Sign a Certificate Signing Request for HOST" + summary "Sign a certificate signing request for HOST" when_invoked do |name, options| host = Puppet::SSL::Host.new(name) diff --git a/lib/puppet/face/certificate_request.rb b/lib/puppet/face/certificate_request.rb index 1feba25ab..cc6021517 100644 --- a/lib/puppet/face/certificate_request.rb +++ b/lib/puppet/face/certificate_request.rb @@ -1,4 +1,32 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' -Puppet::Face::Indirector.define(:certificate_request, '0.0.1') do +Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Manage certificate requests." + description <<-EOT + Retrieves and submits certificate signing requests (CSRs). Invoke + `search` with an unread key to retrieve all outstanding CSRs, invoke + `find` with a node certificate name to retrieve a specific request, and + invoke `save` to submit a CSR. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `ca` + * `file` + * `rest` + EOT + examples <<-EOT + Retrieve all CSRs from the local CA: + + puppet certificate_request search no_key --terminus ca + + Retrieve a single CSR from the puppet master's CA: + + puppet certificate_request find mynode.puppetlabs.lan --terminus rest + EOT end diff --git a/lib/puppet/face/certificate_revocation_list.rb b/lib/puppet/face/certificate_revocation_list.rb index 6a75aa578..2722b20f2 100644 --- a/lib/puppet/face/certificate_revocation_list.rb +++ b/lib/puppet/face/certificate_revocation_list.rb @@ -1,4 +1,30 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' -Puppet::Face::Indirector.define(:certificate_revocation_list, '0.0.1') do +Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Manage the list of revoked certificates." + description <<-EOT + This face is primarily for retrieving the certificate revocation + list from the CA. Although it exposes search/save/destroy methods, + they shouldn't be used under normal circumstances. + EOT + notes <<-EOT + Although the find action must be given an argument, this argument is + never read, and can contain the descriptive text of your choice. + + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `ca` + * `file` + * `rest` + EOT + examples <<-EXAMPLES + Retrieve the CRL: + + puppet certificate_revocation_list find crl + EXAMPLES end diff --git a/lib/puppet/face/config.rb b/lib/puppet/face/config.rb index 45cb6b156..9ca41085e 100644 --- a/lib/puppet/face/config.rb +++ b/lib/puppet/face/config.rb @@ -1,7 +1,43 @@ require 'puppet/face' Puppet::Face.define(:config, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Interact with Puppet configuration options" + action(:print) do + summary "Examine Puppet's current configuration options" + description <<-EOT + Prints the value of a single configuration option or a list of + configuration options. + + This action is an alternate interface to the information available with + `puppet agent --configprint`. + EOT + notes <<-EOT + The return data of this action varies depending on its arguments. When + called with "all," `print` will return a complete list of option names + and values. When called with a single configuration option name, it will + return the value of that option. When called with a list of + configuration option names, it will return the corresponding list of + option names and values. + + By default, this action retrieves its configuration information in agent + mode. To examine the master's configuration, supply Puppet's global + `--mode master` option. To examine configurations from a specific + environment, you can use the `--environment` option. + EOT + examples <<-EOT + Get puppet's runfile directory: + + puppet config print rundir + + Get a list of important directories from the master's config: + + puppet config print all --mode master | grep -E "(path|dir)" + EOT + when_invoked do |*args| options = args.pop Puppet.settings[:configprint] = args.join(",") diff --git a/lib/puppet/face/configurer.rb b/lib/puppet/face/configurer.rb deleted file mode 100644 index 74dfb854e..000000000 --- a/lib/puppet/face/configurer.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'puppet/face' - -Puppet::Face.define(:configurer, '0.0.1') do - action(:synchronize) do - when_invoked do |certname, options| - facts = Puppet::Face[:facts, '0.0.1'].find(certname) - catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) - report - end - end -end diff --git a/lib/puppet/face/facts.rb b/lib/puppet/face/facts.rb index 04eab93a5..ecf4e371e 100644 --- a/lib/puppet/face/facts.rb +++ b/lib/puppet/face/facts.rb @@ -1,9 +1,40 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' require 'puppet/node/facts' -Puppet::Face::Indirector.define(:facts, '0.0.1') do - # Upload our facts to the server +Puppet::Indirector::Face.define(:facts, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Retrieve, store, and view facts." + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `active_record` + * `couch` + * `facter` + * `inventory_active_record` + * `memory` + * `network_device` + * `rest` + * `yaml` + EOT + action(:upload) do + summary "Upload our facts to the puppet master." + description <<-EOT + Retrieves facts for the local system and saves them to the puppet master + server. This is essentially a shortcut action: it calls the `find` + action with the facter terminus, then passes the returned facts object + to the `save` action, which uses the rest terminus. + EOT + notes <<-EOT + This action uses the save action, which requires the puppet master's + auth.conf to allow save access to the `facts` REST terminus. See + `http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more details. + EOT + render_as :yaml when_invoked do |options| diff --git a/lib/puppet/face/file.rb b/lib/puppet/face/file.rb index 1aa9462dd..707ceafd4 100644 --- a/lib/puppet/face/file.rb +++ b/lib/puppet/face/file.rb @@ -1,5 +1,21 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' + +Puppet::Indirector::Face.define(:file, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Retrieve and store files in a filebucket" + # TK this needs a description of how to find files in a filebucket, and + # some good use cases for retrieving/storing them. I can't write either + # of these yet. + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `file` + * `rest` + EOT -Puppet::Face::Indirector.define(:file, '0.0.1') do set_indirection_name :file_bucket_file end diff --git a/lib/puppet/face/file/download.rb b/lib/puppet/face/file/download.rb new file mode 100644 index 000000000..f5413d493 --- /dev/null +++ b/lib/puppet/face/file/download.rb @@ -0,0 +1,36 @@ +# Download a specified file into the local filebucket. +Puppet::Face.define(:file, '0.0.1') do + action :download do |*args| + when_invoked do |sum, options| + if sum =~ /^puppet:\/\// # it's a puppet url + require 'puppet/file_serving' + require 'puppet/file_serving/content' + raise "Could not find metadata for #{sum}" unless content = Puppet::FileServing::Content.indirection.find(sum) + file = Puppet::FileBucket::File.new(content.content) + else + tester = Object.new + tester.extend(Puppet::Util::Checksums) + + type = tester.sumtype(sum) + sumdata = tester.sumdata(sum) + + key = "#{type}/#{sumdata}" + + Puppet::FileBucket::File.indirection.terminus_class = :file + if Puppet::FileBucket::File.indirection.find(key) + Puppet.info "Content for '#{sum}' already exists" + return + end + + Puppet::FileBucket::File.indirection.terminus_class = :rest + raise "Could not download content for '#{sum}'" unless file = Puppet::FileBucket::File.indirection.find(key) + end + + + Puppet::FileBucket::File.indirection.terminus_class = :file + Puppet.notice "Saved #{sum} to filebucket" + Puppet::FileBucket::File.indirection.save file + return nil + end + end +end diff --git a/lib/puppet/face/file/store.rb b/lib/puppet/face/file/store.rb new file mode 100644 index 000000000..4c9523b6c --- /dev/null +++ b/lib/puppet/face/file/store.rb @@ -0,0 +1,12 @@ +# Store a specified file in our filebucket. +Puppet::Face.define(:file, '0.0.1') do + action :store do |*args| + when_invoked do |path, options| + file = Puppet::FileBucket::File.new(File.read(path)) + + Puppet::FileBucket::File.indirection.terminus_class = :file + Puppet::FileBucket::File.indirection.save file + file.checksum + end + end +end diff --git a/lib/puppet/face/help.rb b/lib/puppet/face/help.rb index a762fb02e..aef917447 100644 --- a/lib/puppet/face/help.rb +++ b/lib/puppet/face/help.rb @@ -4,13 +4,16 @@ require 'pathname' require 'erb' Puppet::Face.define(:help, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + summary "Displays help about puppet subcommands" action(:help) do summary "Display help about faces and their actions." option "--version VERSION" do - desc "Which version of the interface to show help for" + summary "which version of the interface to show help for" end default @@ -21,29 +24,20 @@ Puppet::Face.define(:help, '0.0.1') do options = args.pop if options.nil? or args.length > 2 then if args.select { |x| x == 'help' }.length > 2 then - c = "\n !\"'),-./7:;<GIJLST\\_`abcdefhiklmnoprstuwx|}".split('') + c = "\n %'(),-./=ADEFHILORSTUXY\\_`gnv|".split('') i = <<-'EOT'.gsub(/\s*/, '').to_i(36) - 2s7ytxy5vpj74kbab5xzf1ik2roinzlefaspjrzckiert5xbaxvwlku3a91w7y1rsd - nenp51gwpulmnrp54nwdil36fjgjarab801y0r5a9nh1hdfgi99arn5c5t3zhxbvzi - u6wx5r1tb7lun7pro69nrxunqqixsh6qmmv0ms0i0yycqw3pystyzmiita0lpxynqs - qkbjwadcx82n76wwpzbht8i8rgvqhqick8mk3cs3rvwdjookpgu0rxw4tcezned5sq - z5x8z9vntyyz0s4h6hjhtwtbytsmmu7ltvdftaixc7fkt276sqm48ab4yv0ot9y26n - z0xniy4pfl1x300lt6h9c8of49vf799ieuxwnoycsjlmtd4qntzit524j0tdn6n5aj - mq3z10apjuhkzprvmu53z1gnacymnoforrz5mbqto062kckgw5463pxwzg8liglub4 - ubnr0dln1s6iy3ummxuhim7m5a7yedl3gyy6ow4qqtmsigv27lysooau24zpsccsvx - ddwygjprqpbwon7i9s1279m1fpinvva8mfh6bgmotrpxsh1c8rc83l3u0utf5i200y - l7ui0ngcbcjyr4erzdee2tqk3fpjvb82t8xhncruhgn7j5dh2m914qzhb0gkoom47k - 6et7rp4tqjnrv0y2apk5qdl1x1hnbkkxup5ys6ip2ksmtpd3ipmrdtswxr5xwfiqtm - 60uyjr1v79irhnkrbbt4fwhgqjby1qflgwt9c1wpayzzucep6npgbn3f1k6cn4pug3 - 1u02wel4tald4hij8m5p49xr8u4ero1ucs5uht42o8nhpmpe7c7xf9t85i85m9m5kk - tgoqkgbu52gy5aoteyp8jkm3vri9fnkmwa5h60zt8otja72joxjb40p2rz2vp8f8q9 - nnggxt3x90pe5u4048ntyuha78q1oikhhpvw9j083yc3l00hz5ehv9c1au5gvctyap - zprub289qruve9qsyuh75j04wzkemqw3uhisrfs92u1ahv2qlqxmorgob16c1vbqkx - ttkoyp2agkt0v5l7lec25p0jqun9y39k41h67aeb5ihiqsftxc9azmg31hc73dk8ur - lj88vgbmgt8yln9rchw60whgxvnv9zn6cxbr482svctswc5a07atj + 3he6737w1aghshs6nwrivl8mz5mu9nywg9tbtlt081uv6fq5kvxse1td3tj1wvccmte806nb + cy6de2ogw0fqjymbfwi6a304vd56vlq71atwmqsvz3gpu0hj42200otlycweufh0hylu79t3 + gmrijm6pgn26ic575qkexyuoncbujv0vcscgzh5us2swklsp5cqnuanlrbnget7rt3956kam + j8adhdrzqqt9bor0cv2fqgkloref0ygk3dekiwfj1zxrt13moyhn217yy6w4shwyywik7w0l + xtuevmh0m7xp6eoswin70khm5nrggkui6z8vdjnrgdqeojq40fya5qexk97g4d8qgw0hvokr + pli1biaz503grqf2ycy0ppkhz1hwhl6ifbpet7xd6jjepq4oe0ofl575lxdzjeg25217zyl4 + nokn6tj5pq7gcdsjre75rqylydh7iia7s3yrko4f5ud9v8hdtqhu60stcitirvfj6zphppmx + 7wfm7i9641d00bhs44n6vh6qvx39pg3urifgr6ihx3e0j1ychzypunyou7iplevitkyg6gbg + wm08oy1rvogcjakkqc1f7y1awdfvlb4ego8wrtgu9vzw4vmj59utwifn2ejcs569dh1oaavi + sc581n7jjg1dugzdu094fdobtx6rsvk3sfctvqnr36xctold EOT - 607.times{i,x=i.divmod(1035);a,b=x.divmod(23);print(c[a]*b)} - raise ArgumentError, "Such panic is really not required." + 353.times{i,x=i.divmod(1184);a,b=x.divmod(37);print(c[a]*b)} end raise ArgumentError, "help only takes two (optional) arguments, a face name, and an action" end diff --git a/lib/puppet/face/help/action.erb b/lib/puppet/face/help/action.erb index eaf131464..c26958a35 100644 --- a/lib/puppet/face/help/action.erb +++ b/lib/puppet/face/help/action.erb @@ -1,3 +1,48 @@ -Use: puppet <%= face.name %> [options] <%= action.name %> [options] +puppet <%= face.name %><%= action.default? ? '' : " #{action.name}" %>(1) -- <%= action.summary || face.summary %> +<%= '=' * (_erbout.length - 1) %> -Summary: <%= action.summary %> +% if action.synopsis +SYNOPSIS +-------- + +<%= action.synopsis %> + +% end +% if action.description +DESCRIPTION +----------- +<%= action.description %> + +%end +% unless action.options.empty? +OPTIONS +------- +% action.options.sort.each do |name| +% option = action.get_option name +<%= " " + option.optparse.join(" |" ) %> +<%= option.summary %> +<%= option.description %> + +% end +% end +% if action.examples +EXAMPLES +-------- +<%= action.examples %> +% end +% if action.notes +NOTES +----- +<%= action.notes %> + +% end +% unless action.authors.empty? +AUTHOR +------ +<%= action.authors.map {|x| " * " + x } .join("\n") %> + +%end +COPYRIGHT AND LICENSE +--------------------- +<%= action.copyright %> +<%= action.license %> diff --git a/lib/puppet/face/help/face.erb b/lib/puppet/face/help/face.erb index efe5fd809..b249981de 100644 --- a/lib/puppet/face/help/face.erb +++ b/lib/puppet/face/help/face.erb @@ -1,7 +1,48 @@ -Use: puppet <%= face.name %> [options] <action> [options] +NAME + <%= face.name %> -- <%= face.summary || "unknown face..." %> -Available actions: +% if face.synopsis +SYNOPSIS +<%= face.synopsis.gsub(/^/, ' ') %> + +% end +% if face.description +DESCRIPTION +<%= face.description.chomp.gsub(/^/, ' ') %> + +%end +% unless face.options.empty? +OPTIONS +% face.options.sort.each do |name| +% option = face.get_option name +<%= " " + option.optparse.join(" |" ) %> +<%= option.summary %> +<%= option.description %> + +% end +% end +ACTIONS +% padding = face.actions.map{|x| x.to_s.length}.max + 2 % face.actions.each do |actionname| % action = face.get_action(actionname) - <%= action.name.to_s.ljust(16) %> <%= action.summary %> + <%= action.name.to_s.ljust(padding) %> <%= action.summary %> % end + +% if face.examples +EXAMPLES +<%= face.examples %> +% end +% if face.notes +NOTES +<%= face.notes %> + +% end +% unless face.authors.empty? +AUTHOR +<%= face.authors.join("\n").gsub(/^/, ' * ') %> + +%end +COPYRIGHT AND LICENSE +<%= face.copyright.gsub(/^/, ' ') %> +<%= face.license.gsub(/^/, ' ') %> + diff --git a/lib/puppet/face/help/global.erb b/lib/puppet/face/help/global.erb index f4c761b2b..80c77ad26 100644 --- a/lib/puppet/face/help/global.erb +++ b/lib/puppet/face/help/global.erb @@ -1,4 +1,4 @@ -puppet <subcommand> [options] <action> [options] +Usage: puppet <subcommand> [options] <action> [options] Available subcommands, from Puppet Faces: % Puppet::Face.faces.sort.each do |name| @@ -16,5 +16,4 @@ Available applications, soon to be ported to Faces: See 'puppet help <subcommand> <action>' for help on a specific subcommand action. See 'puppet help <subcommand>' for help on a specific subcommand. -See 'puppet man <subcommand>' for the full man page. Puppet v<%= Puppet::PUPPETVERSION %> diff --git a/lib/puppet/face/key.rb b/lib/puppet/face/key.rb index 3a11ddb03..67d775ca4 100644 --- a/lib/puppet/face/key.rb +++ b/lib/puppet/face/key.rb @@ -1,4 +1,23 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' + +Puppet::Indirector::Face.define(:key, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Create, save, and remove certificate keys" + + description <<-EOT + Keys are created for you automatically when certificate requests are + generated with 'puppet certificate generate'. You should not have to use + this action directly from the command line. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `ca` + * `file` + EOT -Puppet::Face::Indirector.define(:key, '0.0.1') do end diff --git a/lib/puppet/face/node.rb b/lib/puppet/face/node.rb index 12336df8f..be38ad388 100644 --- a/lib/puppet/face/node.rb +++ b/lib/puppet/face/node.rb @@ -1,3 +1,26 @@ -require 'puppet/face/indirector' -Puppet::Face::Indirector.define(:node, '0.0.1') do +require 'puppet/indirector/face' +Puppet::Indirector::Face.define(:node, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "View and manage node definitions" + + description <<-EOT + This face interacts with node objects, which are what Puppet uses to + build a catalog. A node object consists of the node's facts, + environment, additional top-scope variables, and classes. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `active_record` + * `exec` + * `ldap` + * `memory` + * `plain` + * `rest` + * `yaml` + EOT end diff --git a/lib/puppet/face/parser.rb b/lib/puppet/face/parser.rb index d4aaaf043..e6a9503dd 100644 --- a/lib/puppet/face/parser.rb +++ b/lib/puppet/face/parser.rb @@ -2,19 +2,30 @@ require 'puppet/face' require 'puppet/parser' Puppet::Face.define(:parser, '0.0.1') do - action :validate do - when_invoked do |*args| - args.pop - files = args - if files.empty? - files << Puppet[:manifest] - Puppet.notice "No manifest specified. Validating the default manifest #{Puppet[:manifest]}" - end - files.each do |file| - Puppet[:manifest] = file - Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear - end - nil - end - end + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Interact directly with the parser" + + action :validate do + summary "Validate the syntax of one or more Puppet manifests" + description <<-EOT + This action validates Puppet DSL syntax without compiling a catalog or + syncing any resources. If no manifest files are provided, it will + validate the default site manifest. + EOT + when_invoked do |*args| + args.pop + files = args + if files.empty? + files << Puppet[:manifest] + Puppet.notice "No manifest specified. Validating the default manifest #{Puppet[:manifest]}" + end + files.each do |file| + Puppet[:manifest] = file + Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear + end + nil + end + end end diff --git a/lib/puppet/face/plugin.rb b/lib/puppet/face/plugin.rb new file mode 100644 index 000000000..969d42389 --- /dev/null +++ b/lib/puppet/face/plugin.rb @@ -0,0 +1,47 @@ +require 'puppet/face' +Puppet::Face.define(:plugin, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Interact with the Puppet plugin system" + description <<-EOT + This face provides network access to the puppet master's store of + plugins. It is intended for use in other faces, rather than for direct + command line access. + EOT + notes <<-EOT + The puppet master can serve Ruby code collected from the lib directories + of its modules. These plugins can be used on agent nodes to extend + Facter and implement custom types and providers. + EOT + + action :download do + summary "Download plugins from the configured master" + returns <<-EOT + An array containing the files actually downloaded. If all files + were in sync, this array will be empty. + EOT + notes "This action modifies files on disk without returning any data." + examples <<-EOT + Retrieve plugins from the puppet master: + + Puppet::Face[:plugin, '0.0.1'].download + EOT + + when_invoked do |options| + require 'puppet/configurer/downloader' + Puppet::Configurer::Downloader.new("plugin", + Puppet[:plugindest], + Puppet[:pluginsource], + Puppet[:pluginsignore]).evaluate + end + + when_rendering :console do |value| + if value.empty? then + "No plugins downloaded." + else + "Downloaded these plugins: #{value.join(', ')}" + end + end + end +end diff --git a/lib/puppet/face/report.rb b/lib/puppet/face/report.rb index 6e6f0b335..c8549b14f 100644 --- a/lib/puppet/face/report.rb +++ b/lib/puppet/face/report.rb @@ -1,11 +1,43 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' + +Puppet::Indirector::Face.define(:report, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Create, display, and submit reports" + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `processor` + * `rest` + * `yaml` + EOT -Puppet::Face::Indirector.define(:report, '0.0.1') do action(:submit) do + summary "Submit a report object to the puppet master" + description <<-EOT + This action is essentially a shortcut and wrapper for the `save` action + with a terminus of `rest`. It also can provide additional details in the + event of a report submission failure. It is not intended for use from + a command line. + EOT + examples <<-EOT + From secret_agent.rb: + Puppet::Face[:plugin, '0.0.1'].download + + facts = Puppet::Face[:facts, '0.0.1'].find(certname) + catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) + + Puppet::Face[:report, '0.0.1'].submit(report) + EOT when_invoked do |report, options| begin - Puppet::Transaction::Report.terminus_class = :rest - report.save + Puppet::Transaction::Report.indirection.terminus_class = :rest + Puppet::Face[:report, "0.0.1"].save(report) + Puppet.notice "Uploaded report for #{report.name}" rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not send report: #{detail}" diff --git a/lib/puppet/face/resource.rb b/lib/puppet/face/resource.rb index d162f728a..ed6360888 100644 --- a/lib/puppet/face/resource.rb +++ b/lib/puppet/face/resource.rb @@ -1,4 +1,23 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' -Puppet::Face::Indirector.define(:resource, '0.0.1') do +Puppet::Indirector::Face.define(:resource, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Interact directly with resources via the RAL, like ralsh" + description <<-EOT + This face provides a Ruby API with functionality similar to the puppet + resource (née ralsh) command line application. It is not intended to be + used from the command line. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `ral` + * `rest` + EOT + + examples "TK we really need some examples for this one." end diff --git a/lib/puppet/face/resource_type.rb b/lib/puppet/face/resource_type.rb index 0cdbd719f..77ccefa8f 100644 --- a/lib/puppet/face/resource_type.rb +++ b/lib/puppet/face/resource_type.rb @@ -1,4 +1,17 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' -Puppet::Face::Indirector.define(:resource_type, '0.0.1') do +Puppet::Indirector::Face.define(:resource_type, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "View resource types, classes, and nodes from all manifests" + description "TK I have no idea what this does." + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `parser` + * `rest` + EOT end diff --git a/lib/puppet/face/secret_agent.rb b/lib/puppet/face/secret_agent.rb new file mode 100644 index 000000000..c8c8e6629 --- /dev/null +++ b/lib/puppet/face/secret_agent.rb @@ -0,0 +1,39 @@ +require 'puppet/face' + +Puppet::Face.define(:secret_agent, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "Provides agent-like behavior, with no plugin downloading or reporting" + description <<-EOT + This face currently functions as a proof of concept, demonstrating how + Faces allows the separation of application logic from Puppet's internal + systems; compare the code for puppet agent. It will eventually replace + puppet agent entirely, and can provide a template for users who wish to + implement agent-like functionality with drastically different + application logic. + EOT + + action(:synchronize) do + summary "Retrieve and apply a catalog from the puppet master" + description <<-EOT + This action mimics the behavior of the puppet agent application. It does + not currently daemonize, but can download plugins, submit facts, + retrieve and apply a catalog, and submit a report to the puppet master. + EOT + + when_invoked do |options| + Puppet::Face[:plugin, '0.0.1'].download + + Puppet::Face[:facts, '0.0.1'].upload + + Puppet::Face[:catalog, '0.0.1'].download + + report = Puppet::Face[:catalog, '0.0.1'].apply + + Puppet::Face[:report, '0.0.1'].submit(report) + + return report + end + end +end diff --git a/lib/puppet/face/status.rb b/lib/puppet/face/status.rb index 7085e7cd7..6a29debdd 100644 --- a/lib/puppet/face/status.rb +++ b/lib/puppet/face/status.rb @@ -1,4 +1,30 @@ -require 'puppet/face/indirector' +require 'puppet/indirector/face' -Puppet::Face::Indirector.define(:status, '0.0.1') do +Puppet::Indirector::Face.define(:status, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + + summary "View puppet server status" + description <<-EOT + This subcommand is only useful for determining whether a puppet master + server (or an agent node, if puppet was started with the `--listen` + option) is responding to requests. + + Only the `find` action is valid. If the server is responding to + requests, `find` will retrieve a status object; if not, the connection + will be refused. When invoked with the `local` terminus, `find` will + always return true. + + If you wish to query a server other than the master configured in + puppet.conf, you must set the `--server` and `--masterport` options on + the command line. + EOT + notes <<-EOT + This is an indirector face, which exposes find, search, save, and + destroy actions for an indirected subsystem of Puppet. Valid terminuses + for this face include: + + * `local` + * `rest` + EOT end diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb index c020f036d..f29f70a53 100644 --- a/lib/puppet/file_serving/fileset.rb +++ b/lib/puppet/file_serving/fileset.rb @@ -59,7 +59,7 @@ class Puppet::FileServing::Fileset end def initialize(path, options = {}) - path = path.chomp(File::SEPARATOR) + path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR raise ArgumentError.new("Fileset paths must be fully qualified") unless File.expand_path(path) == path @path = path diff --git a/lib/puppet/face/indirector.rb b/lib/puppet/indirector/face.rb index 6c7708b51..1371c647e 100644 --- a/lib/puppet/face/indirector.rb +++ b/lib/puppet/indirector/face.rb @@ -1,10 +1,24 @@ -require 'puppet' require 'puppet/face' -class Puppet::Face::Indirector < Puppet::Face +class Puppet::Indirector::Face < Puppet::Face option "--terminus TERMINUS" do - desc "REVISIT: You can select a terminus, which has some bigger effect -that we should describe in this file somehow." + summary "The indirector terminus to use for this action" + description <<-EOT +Indirector faces expose indirected subsystems of Puppet. These +subsystems are each able to retrieve and alter a specific type of data +(with the familiar actions of `find`, `search`, `save`, and `destroy`) +from an arbitrary number of pluggable backends. In Puppet parlance, +these backends are called terminuses. + +Almost all indirected subsystems have a `rest` terminus that interacts +with the puppet master's data. Most of them have additional terminuses +for various local data models, which are in turn used by the indirected +subsystem on the puppet master whenever it receives a remote request. + +The terminus for an action is often determined by context, but +occasionally needs to be set explicitly. See the "Notes" section of this +face's manpage for more details. + EOT before_action do |action, args, options| set_terminus(options[:terminus]) @@ -23,11 +37,9 @@ that we should describe in this file somehow." Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort end - def call_indirection_method(method, *args) - options = args.last - + def call_indirection_method(method, key, options) begin - result = indirection.__send__(method, *args) + result = indirection.__send__(method, key, options) rescue => detail puts detail.backtrace if Puppet[:trace] raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" @@ -37,23 +49,38 @@ that we should describe in this file somehow." end action :destroy do - when_invoked { |*args| call_indirection_method(:destroy, *args) } + summary "Delete an object" + when_invoked { |key, options| call_indirection_method(:destroy, key, options) } end action :find do - when_invoked { |*args| call_indirection_method(:find, *args) } + summary "Retrieve an object by name" + when_invoked { |key, options| call_indirection_method(:find, key, options) } end action :save do - when_invoked { |*args| call_indirection_method(:save, *args) } + summary "Create or modify an object" + notes <<-EOT + Save actions cannot currently be invoked from the command line, and are + for API use only. + EOT + when_invoked { |key, options| call_indirection_method(:save, key, options) } end action :search do - when_invoked { |*args| call_indirection_method(:search, *args) } + summary "Search for an object" + when_invoked { |key, options| call_indirection_method(:search, key, options) } end # Print the configuration for the current terminus class action :info do + summary "Print the default terminus class for this face" + description <<-EOT + TK So this is per-face, right? No way to tell what the default terminus + is per-action, for subsystems that switch to REST for save but query + locally for find? + EOT + when_invoked do |*args| if t = indirection.terminus_class puts "Run mode '#{Puppet.run_mode.name}': #{t}" diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb index 1918a3fb5..fd8d654dd 100644 --- a/lib/puppet/indirector/request.rb +++ b/lib/puppet/indirector/request.rb @@ -14,51 +14,6 @@ class Puppet::Indirector::Request OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :instance, :environment] - def self.from_pson(json) - raise ArgumentError, "No indirection name provided in json data" unless indirection_name = json['type'] - raise ArgumentError, "No method name provided in json data" unless method = json['method'] - raise ArgumentError, "No key provided in json data" unless key = json['key'] - - request = new(indirection_name, method, key, json['attributes']) - - if instance = json['instance'] - klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model - if instance.is_a?(klass) - request.instance = instance - else - request.instance = klass.from_pson(instance) - end - end - - request - end - - def to_pson(*args) - result = { - 'document_type' => 'Puppet::Indirector::Request', - 'data' => { - 'type' => indirection_name, - 'method' => method, - 'key' => key - } - } - data = result['data'] - attributes = {} - OPTION_ATTRIBUTES.each do |key| - next unless value = send(key) - attributes[key] = value - end - - options.each do |opt, value| - attributes[opt] = value - end - - data['attributes'] = attributes unless attributes.empty? - data['instance'] = instance if instance - - result.to_pson(*args) - end - # Is this an authenticated request? def authenticated? # Double negative, so we just get true or false @@ -106,11 +61,9 @@ class Puppet::Indirector::Request self.indirection_name = indirection_name self.method = method - options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash } - set_attributes(options) - @options = options + @options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash } if key_or_instance.is_a?(String) || key_or_instance.is_a?(Symbol) key = key_or_instance @@ -200,7 +153,7 @@ class Puppet::Indirector::Request def set_attributes(options) OPTION_ATTRIBUTES.each do |attribute| - if options.include?(attribute.to_sym) + if options.include?(attribute) send(attribute.to_s + "=", options[attribute]) options.delete(attribute) end diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index ced00863d..10e2ec8d7 100644 --- a/lib/puppet/interface.rb +++ b/lib/puppet/interface.rb @@ -1,7 +1,11 @@ require 'puppet' require 'puppet/util/autoload' +require 'puppet/interface/documentation' +require 'prettyprint' class Puppet::Interface + include FullDocs + require 'puppet/interface/face_collection' require 'puppet/interface/action_manager' @@ -65,27 +69,33 @@ class Puppet::Interface Puppet.warning("set_default_format is deprecated (and ineffective); use render_as on your actions instead.") end + ######################################################################## # Documentation. We currently have to rewrite both getters because we share # the same instance between build-time and the runtime instance. When that # splits out this should merge into a module that both the action and face # include. --daniel 2011-04-17 - attr_accessor :summary, :description - def summary(value = nil) - self.summary = value unless value.nil? - @summary - end - def summary=(value) - value = value.to_s - value =~ /\n/ and - raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead." - - @summary = value - end - - def description(value = nil) - self.description = value unless value.nil? - @description + def synopsis + output = PrettyPrint.format do |s| + s.text("puppet #{name} <action>") + s.breakable + + options.each do |option| + option = get_option(option) + wrap = option.required? ? %w{ < > } : %w{ [ ] } + + s.group(0, *wrap) do + option.optparse.each do |item| + unless s.current_group.first? + s.breakable + s.text '|' + s.breakable + end + s.text item + end + end + end + end end @@ -97,9 +107,15 @@ class Puppet::Interface raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" end - @name = Puppet::Interface::FaceCollection.underscorize(name) + @name = Puppet::Interface::FaceCollection.underscorize(name) @version = version + # The few bits of documentation we actually demand. The default license + # is a favour to our end users; if you happen to get that in a core face + # report it as a bug, please. --daniel 2011-04-26 + @authors = [] + @license = 'All Rights Reserved' + instance_eval(&block) if block_given? end @@ -139,12 +155,12 @@ class Puppet::Interface action.get_option(name).__decoration_name(type) end + methods.reverse! if type == :after + + # Exceptions here should propagate up; this implements a hook we can use + # reasonably for option validation. methods.each do |hook| - begin - respond_to? hook and self.__send__(hook, action, passed_args, passed_options) - rescue => e - Puppet.warning("invoking #{action} #{type} hook: #{e}") - end + respond_to? hook and self.__send__(hook, action, passed_args, passed_options) end end diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb index f8eef69b1..114e5341b 100644 --- a/lib/puppet/interface/action.rb +++ b/lib/puppet/interface/action.rb @@ -1,15 +1,28 @@ -# -*- coding: utf-8 -*- require 'puppet/interface' -require 'puppet/interface/option' +require 'puppet/interface/documentation' +require 'prettyprint' class Puppet::Interface::Action + extend Puppet::Interface::DocGen + include Puppet::Interface::FullDocs + def initialize(face, name, attrs = {}) raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ @face = face @name = name.to_sym + + # The few bits of documentation we actually demand. The default license + # is a favour to our end users; if you happen to get that in a core face + # report it as a bug, please. --daniel 2011-04-26 + @authors = [] + @license = 'All Rights Reserved' + attrs.each do |k, v| send("#{k}=", v) end - @options = {} + # @options collects the added options in the order they're declared. + # @options_hash collects the options keyed by alias for quick lookups. + @options = [] + @options_hash = {} @when_rendering = {} end @@ -30,8 +43,32 @@ class Puppet::Interface::Action !!@default end - attr_accessor :summary - + ######################################################################## + # Documentation... + attr_doc :returns + def synopsis + output = PrettyPrint.format do |s| + s.text("puppet #{@face.name}") + s.text(" #{name}") unless default? + s.breakable + + options.each do |option| + option = get_option(option) + wrap = option.required? ? %w{ < > } : %w{ [ ] } + + s.group(0, *wrap) do + option.optparse.each do |item| + unless s.current_group.first? + s.breakable + s.text '|' + s.breakable + end + s.text item + end + end + end + end + end ######################################################################## # Support for rendering formats and all. @@ -39,8 +76,15 @@ class Puppet::Interface::Action unless type.is_a? Symbol raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}" end - return unless @when_rendering.has_key? type - return @when_rendering[type].bind(@face) + # Do we have a rendering hook for this name? + return @when_rendering[type].bind(@face) if @when_rendering.has_key? type + + # How about by another name? + alt = type.to_s.sub(/^to_/, '').to_sym + return @when_rendering[alt].bind(@face) if @when_rendering.has_key? alt + + # Guess not, nothing to run. + return nil end def set_rendering_method_for(type, proc) unless proc.is_a? Proc @@ -83,18 +127,6 @@ class Puppet::Interface::Action ######################################################################## - # Documentation stuff, whee! - attr_accessor :summary, :description - def summary=(value) - value = value.to_s - value =~ /\n/ and - raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead." - - @summary = value - end - - - ######################################################################## # Initially, this was defined to allow the @action.invoke pattern, which is # a very natural way to invoke behaviour given our introspection # capabilities. Heck, our initial plan was to have the faces delegate to @@ -148,11 +180,13 @@ class Puppet::Interface::Action # this stuff work, because it would have been cleaner. Which gives you an # idea how motivated we were to make this cleaner. Sorry. # --daniel 2011-03-31 + attr_reader :positional_arg_count + attr_accessor :when_invoked def when_invoked=(block) internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym - arity = block.arity + arity = @positional_arg_count = block.arity if arity == 0 then # This will never fire on 1.8.7, which treats no arguments as "*args", # but will on 1.9.2, which treats it as "no arguments". Which bites, @@ -195,9 +229,11 @@ WRAPPER if @face.is_a?(Class) @face.class_eval do eval wrapper, nil, file, line end @face.define_method(internal_name, &block) + @when_invoked = @face.instance_method(name) else @face.instance_eval do eval wrapper, nil, file, line end @face.meta_def(internal_name, &block) + @when_invoked = @face.method(name).unbind end end @@ -211,7 +247,8 @@ WRAPPER end option.aliases.each do |name| - @options[name] = option + @options << name + @options_hash[name] = option end option @@ -223,15 +260,15 @@ WRAPPER end def option?(name) - @options.include? name.to_sym + @options_hash.include? name.to_sym end def options - (@options.keys + @face.options).sort + @face.options + @options end def get_option(name, with_inherited_options = true) - option = @options[name.to_sym] + option = @options_hash[name.to_sym] if option.nil? and with_inherited_options option = @face.get_option(name) end @@ -239,12 +276,26 @@ WRAPPER end def validate_args(args) + # Check for multiple aliases for the same option... + args.last.keys.each do |name| + # #7290: If this isn't actually an option, ignore it for now. We should + # probably fail, but that wasn't our API, and I don't want to perturb + # behaviour this late in the RC cycle. --daniel 2011-04-29 + if option = get_option(name) then + overlap = (option.aliases & args.last.keys) + unless overlap.length == 1 then + raise ArgumentError, "Multiple aliases for the same option passed: #{overlap.join(', ')}" + end + end + end + + # Check for missing mandatory options. required = options.map do |name| get_option(name) end.select(&:required?).collect(&:name) - args.last.keys return if required.empty? - raise ArgumentError, "missing required options (#{required.join(', ')})" + raise ArgumentError, "The following options are required: #{required.join(', ')}" end ######################################################################## diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb index fd8b0856f..ba5531f1d 100644 --- a/lib/puppet/interface/action_builder.rb +++ b/lib/puppet/interface/action_builder.rb @@ -9,13 +9,6 @@ class Puppet::Interface::ActionBuilder new(face, name, &block).action end - private - def initialize(face, name, &block) - @face = face - @action = Puppet::Interface::Action.new(face, name) - instance_eval(&block) - end - # Ideally the method we're defining here would be added to the action, and a # method on the face would defer to it, but we can't get scope correct, so # we stick with this. --daniel 2011-03-24 @@ -55,7 +48,7 @@ class Puppet::Interface::ActionBuilder def render_as(value = nil) value.nil? and raise ArgumentError, "You must give a rendering format to render_as" - formats = Puppet::Network::FormatHandler.formats << :for_humans + formats = Puppet::Network::FormatHandler.formats unless formats.include? value raise ArgumentError, "#{value.inspect} is not a valid rendering format: #{formats.sort.join(", ")}" end @@ -66,18 +59,26 @@ class Puppet::Interface::ActionBuilder # Metaprogram the simple DSL from the target class. Puppet::Interface::Action.instance_methods.grep(/=$/).each do |setter| next if setter =~ /^=/ - dsl = setter.sub(/=$/, '') + property = setter.sub(/=$/, '') - unless private_instance_methods.include? dsl + unless public_instance_methods.include? property # Using eval because the argument handling semantics are less awful than # when we use the define_method/block version. The later warns on older # Ruby versions if you pass the wrong number of arguments, but carries # on, which is totally not what we want. --daniel 2011-04-18 - eval <<METHOD -def #{dsl}(value) - @action.#{dsl} = value -end -METHOD + eval <<-METHOD + def #{property}(value) + @action.#{property} = value + end + METHOD end end + + private + def initialize(face, name, &block) + @face = face + @action = Puppet::Interface::Action.new(face, name) + instance_eval(&block) + @action.when_invoked or raise ArgumentError, "actions need to know what to do when_invoked; please add the block" + end end diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb index 440be2d1b..c5eb8e08a 100644 --- a/lib/puppet/interface/action_manager.rb +++ b/lib/puppet/interface/action_manager.rb @@ -1,9 +1,11 @@ -require 'puppet/interface/action_builder' +require 'puppet/interface/action' module Puppet::Interface::ActionManager # Declare that this app can take a specific action, and provide # the code to do so. def action(name, &block) + require 'puppet/interface/action_builder' + @actions ||= {} @default_action ||= nil raise "Action #{name} already defined for #{self}" if action?(name) diff --git a/lib/puppet/interface/documentation.rb b/lib/puppet/interface/documentation.rb new file mode 100644 index 000000000..48e9a8b1a --- /dev/null +++ b/lib/puppet/interface/documentation.rb @@ -0,0 +1,197 @@ +# This isn't usable outside Puppet::Interface; don't load it alone. +class Puppet::Interface + module DocGen + def self.strip_whitespace(text) + text.gsub!(/[ \t\f]+$/, '') + + # We need to identify an indent: the minimum number of whitespace + # characters at the start of any line in the text. + # + # Using split rather than each_line is because the later only takes a + # block on Ruby 1.8.5 / Centos, and we support that. --daniel 2011-05-03 + indent = text.split(/\n/).map {|x| x.index(/[^\s]/) }.compact.min + + if indent > 0 then + text.gsub!(/^[ \t\f]{0,#{indent}}/, '') + end + + return text + end + + # The documentation attributes all have some common behaviours; previously + # we open-coded them across the set of six things, but that seemed + # wasteful - especially given that they were literally the same, and had + # the same bug hidden in them. + # + # This feels a bit like overkill, but at least the common code is common + # now. --daniel 2011-04-29 + def attr_doc(name, &validate) + # Now, which form of the setter do we want, validated or not? + get_arg = "value.to_s" + if validate + define_method(:"_validate_#{name}", validate) + get_arg = "_validate_#{name}(#{get_arg})" + end + + # We use module_eval, which I don't like much, because we can't have an + # argument to a block with a default value in Ruby 1.8, and I don't like + # the side-effects (eg: no argument count validation) of using blocks + # without as metheds. When we are 1.9 only (hah!) you can totally + # replace this with some up-and-up define_method. --daniel 2011-04-29 + module_eval(<<-EOT, __FILE__, __LINE__ + 1) + def #{name}(value = nil) + self.#{name} = value unless value.nil? + @#{name} + end + + def #{name}=(value) + @#{name} = Puppet::Interface::DocGen.strip_whitespace(#{get_arg}) + end + EOT + end + end + + module TinyDocs + extend Puppet::Interface::DocGen + + attr_doc :summary do |value| + value =~ /\n/ and + raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead." + value + end + + attr_doc :description + end + + module FullDocs + extend Puppet::Interface::DocGen + include TinyDocs + + attr_doc :examples + attr_doc :notes + attr_doc :license + + attr_doc :short_description + def short_description(value = nil) + self.short_description = value unless value.nil? + if @short_description.nil? then + return nil if @description.nil? + lines = @description.split("\n") + grab = [5, lines.index('') || 5].min + @short_description = lines[0, grab].join("\n") + end + @short_description + end + + def author(value = nil) + unless value.nil? then + unless value.is_a? String + raise ArgumentError, 'author must be a string; use multiple statements for multiple authors' + end + + if value =~ /\n/ then + raise ArgumentError, 'author should be a single line; use multiple statements for multiple authors' + end + @authors.push(Puppet::Interface::DocGen.strip_whitespace(value)) + end + @authors.empty? ? nil : @authors.join("\n") + end + def authors + @authors + end + def author=(value) + if Array(value).any? {|x| x =~ /\n/ } then + raise ArgumentError, 'author should be a single line; use multiple statements' + end + @authors = Array(value).map{|x| Puppet::Interface::DocGen.strip_whitespace(x) } + end + alias :authors= :author= + + def copyright(owner = nil, years = nil) + if years.nil? and not owner.nil? then + raise ArgumentError, 'copyright takes the owners names, then the years covered' + end + self.copyright_owner = owner unless owner.nil? + self.copyright_years = years unless years.nil? + + if self.copyright_years or self.copyright_owner then + "Copyright #{self.copyright_years} by #{self.copyright_owner}" + else + "Unknown copyright owner and years." + end + end + + attr_accessor :copyright_owner + def copyright_owner=(value) + case value + when String then @copyright_owner = value + when Array then @copyright_owner = value.join(", ") + else + raise ArgumentError, "copyright owner must be a string or an array of strings" + end + @copyright_owner + end + + attr_accessor :copyright_years + def copyright_years=(value) + years = munge_copyright_year value + years = (years.is_a?(Array) ? years : [years]). + sort_by do |x| x.is_a?(Range) ? x.first : x end + + @copyright_years = years.map do |year| + if year.is_a? Range then + "#{year.first}-#{year.last}" + else + year + end + end.join(", ") + end + + def munge_copyright_year(input) + case input + when Range then input + when Integer then + if input < 1970 then + fault = "before 1970" + elsif input > (future = Time.now.year + 2) then + fault = "after #{future}" + end + if fault then + raise ArgumentError, "copyright with a year #{fault} is very strange; did you accidentally add or subtract two years?" + end + + input + + when String then + input.strip.split(/,/).map do |part| + part = part.strip + if part =~ /^\d+$/ then + part.to_i + elsif found = part.split(/-/) then + unless found.length == 2 and found.all? {|x| x.strip =~ /^\d+$/ } + raise ArgumentError, "#{part.inspect} is not a good copyright year or range" + end + Range.new(found[0].to_i, found[1].to_i) + else + raise ArgumentError, "#{part.inspect} is not a good copyright year or range" + end + end + + when Array then + result = [] + input.each do |item| + item = munge_copyright_year item + if item.is_a? Array + result.concat item + else + result << item + end + end + result + + else + raise ArgumentError, "#{input.inspect} is not a good copyright year, set, or range" + end + end + end +end diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb index 6e6afc545..12d3c56b1 100644 --- a/lib/puppet/interface/face_collection.rb +++ b/lib/puppet/interface/face_collection.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- require 'puppet/interface' module Puppet::Interface::FaceCollection @@ -10,21 +9,12 @@ module Puppet::Interface::FaceCollection unless @loaded @loaded = true $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - Dir.glob("puppet/face/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| - iname = file.sub(/\.rb/, '') - begin - require iname - rescue Exception => detail - puts detail.backtrace if Puppet[:trace] - raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" - end - end - end + Dir.glob("#{dir}/puppet/face/*.rb"). + collect {|f| File.basename(f, '.rb') }. + each {|name| self[name, :current] } end end - return @faces.keys.select {|name| @faces[name].length > 0 } + @faces.keys.select {|name| @faces[name].length > 0 } end def self.validate_version(version) @@ -124,6 +114,10 @@ module Puppet::Interface::FaceCollection rescue LoadError => e raise unless e.message =~ %r{-- puppet/face/#{name}$} # ...guess we didn't find the file; return a much better problem. + rescue SyntaxError => e + raise unless e.message =~ %r{puppet/face/#{name}\.rb:\d+: } + Puppet.err "Failed to load face #{name}:\n#{e}" + # ...but we just carry on after complaining. end return get_face(name, version) diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb index f4c56cb2c..b68bdeb12 100644 --- a/lib/puppet/interface/option.rb +++ b/lib/puppet/interface/option.rb @@ -1,4 +1,10 @@ +require 'puppet/interface' + class Puppet::Interface::Option + include Puppet::Interface::TinyDocs + # For compatibility, deprecated, and should go fairly soon... + ['', '='].each { |x| alias :"desc#{x}" :"description#{x}" } + def initialize(parent, *declaration, &block) @parent = parent @optparse = [] @@ -78,7 +84,7 @@ class Puppet::Interface::Option end attr_reader :parent, :name, :aliases, :optparse - attr_accessor :required, :desc + attr_accessor :required attr_accessor :before_action def before_action=(proc) diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb index d42359c07..326a91d92 100644 --- a/lib/puppet/interface/option_manager.rb +++ b/lib/puppet/interface/option_manager.rb @@ -8,6 +8,11 @@ module Puppet::Interface::OptionManager end def add_option(option) + # @options collects the added options in the order they're declared. + # @options_hash collects the options keyed by alias for quick lookups. + @options ||= [] + @options_hash ||= {} + option.aliases.each do |name| if conflict = get_option(name) then raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" @@ -21,25 +26,30 @@ module Puppet::Interface::OptionManager end end - option.aliases.each { |name| @options[name] = option } - option + option.aliases.each do |name| + @options << name + @options_hash[name] = option + end + + return option end def options - @options ||= {} - result = @options.keys + result = (@options ||= []) if self.is_a?(Class) and superclass.respond_to?(:options) - result += superclass.options + result = superclass.options + result elsif self.class.respond_to?(:options) - result += self.class.options + result = self.class.options + result end - result.sort + + return result end def get_option(name, with_inherited_options = true) - @options ||= {} - result = @options[name.to_sym] + @options_hash ||= {} + + result = @options_hash[name.to_sym] if result.nil? and with_inherited_options then if self.is_a?(Class) and superclass.respond_to?(:get_option) result = superclass.get_option(name) @@ -47,6 +57,7 @@ module Puppet::Interface::OptionManager result = self.class.get_option(name) end end + return result end diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb index 4ca3240d4..082c83ee3 100644 --- a/lib/puppet/network/formats.rb +++ b/lib/puppet/network/formats.rb @@ -160,3 +160,39 @@ end # This is really only ever going to be used for Catalogs. Puppet::Network::FormatHandler.create_serialized_formats(:dot, :required_methods => [:render_method]) + + +Puppet::Network::FormatHandler.create(:console, + :mime => 'text/x-console-text', + :weight => 0) do + def json + @json ||= Puppet::Network::FormatHandler.format(:pson) + end + + def render(datum) + # String to String + return datum if datum.is_a? String + return datum if datum.is_a? Numeric + + # Simple hash to table + if datum.is_a? Hash and datum.keys.all? { |x| x.is_a? String or x.is_a? Numeric } + output = '' + column_a = datum.map do |k,v| k.to_s.length end.max + 2 + column_b = 79 - column_a + datum.sort_by { |k,v| k.to_s } .each do |key, value| + output << key.to_s.ljust(column_a) + output << json.render(value). + chomp.gsub(/\n */) { |x| x + (' ' * column_a) } + output << "\n" + end + return output + end + + # ...or pretty-print the inspect outcome. + return json.render(datum) + end + + def render_multiple(data) + data.collect(&:render).join("\n") + end +end diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb index 61307f01e..388d54961 100644 --- a/lib/puppet/network/http/api/v1.rb +++ b/lib/puppet/network/http/api/v1.rb @@ -30,7 +30,7 @@ module Puppet::Network::HTTP::API::V1 method = indirection_method(http_method, indirection) - params[:environment] = environment + params[:environment] = Puppet::Node::Environment.new(environment) raise ArgumentError, "No request key specified in #{uri}" if key == "" or key.nil? diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb index cf76978fe..dfe8f85c4 100644 --- a/lib/puppet/network/rest_authconfig.rb +++ b/lib/puppet/network/rest_authconfig.rb @@ -8,6 +8,7 @@ module Puppet DEFAULT_ACL = [ { :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, + { :acl => "~ ^\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, # this one will allow all file access, and thus delegate # to fileserver.conf { :acl => "/file" }, diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 4bd4d1de6..5b0a98615 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -20,29 +20,6 @@ class Puppet::Node attr_accessor :name, :classes, :source, :ipaddress, :parameters attr_reader :time - def self.from_pson(pson) - raise ArgumentError, "No name provided in pson data" unless name = pson['name'] - - node = new(name) - node.classes = pson['classes'] - node.parameters = pson['parameters'] - node.environment = pson['environment'] - node - end - - def to_pson(*args) - result = { - 'document_type' => "Puppet::Node", - 'data' => {} - } - result['data']['name'] = name - result['data']['classes'] = classes unless classes.empty? - result['data']['parameters'] = parameters unless parameters.empty? - result['data']['environment'] = environment.name - - result.to_pson(*args) - end - def environment return super if @environment diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index 2ff7156c8..577b62b62 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -61,22 +61,18 @@ class Puppet::Node::Facts def self.from_pson(data) result = new(data['name'], data['values']) - result.timestamp = Time.parse(data['timestamp']) if data['timestamp'] - result.expiration = Time.parse(data['expiration']) if data['expiration'] + result.timestamp = Time.parse(data['timestamp']) + result.expiration = Time.parse(data['expiration']) result end def to_pson(*args) - result = { - 'document_type' => "Puppet::Node::Facts", - 'data' => {} - } - - result['data']['name'] = name - result['data']['expiration'] = expiration if expiration - result['data']['timestamp'] = timestamp if timestamp - result['data']['values'] = strip_internal - result.to_pson(*args) + { + 'expiration' => expiration, + 'name' => name, + 'timestamp' => timestamp, + 'values' => strip_internal, + }.to_pson(*args) end # Add internal data to the facts for storage. diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 180a03dc9..27d75bf92 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -20,7 +20,7 @@ class Puppet::Parser::TemplateWrapper def script_line # find which line in the template (if any) we were called from - caller.find { |l| l =~ /#{file}:/ }.first[/:(\d+):/,1] + (caller.find { |l| l =~ /#{file}:/ }||"")[/:(\d+):/,1] end # Should return true if a variable is defined, false if it is not diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb index a6cff9bdc..b742d283f 100644 --- a/lib/puppet/resource/catalog.rb +++ b/lib/puppet/resource/catalog.rb @@ -74,7 +74,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph raise ArgumentError, "Can only add objects that respond to :ref, not instances of #{resource.class}" unless resource.respond_to?(:ref) fail_on_duplicate_type_and_title(resource) title_key = title_key_for_ref(resource.ref) - + @transient_resources << resource if applying? @resource_table[title_key] = resource @@ -339,8 +339,8 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph @relationship_graph end - # Impose our container information on another graph by using it - # to replace any container vertices X with a pair of verticies + # Impose our container information on another graph by using it + # to replace any container vertices X with a pair of verticies # { admissible_X and completed_X } such that that # # 0) completed_X depends on admissible_X @@ -353,8 +353,8 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph # Note that this requires attention to the possible case of containers # which contain or depend on other containers, but has the advantage # that the number of new edges created scales linearly with the number - # of contained verticies regardless of how containers are related; - # alternatives such as replacing container-edges with content-edges + # of contained verticies regardless of how containers are related; + # alternatives such as replacing container-edges with content-edges # scale as the product of the number of external dependences, which is # to say geometrically in the case of nested / chained containers. # @@ -374,8 +374,8 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph admissible = Hash.new { |h,k| k } completed = Hash.new { |h,k| k } containers.each { |x| - admissible[x] = whit_class.new(:name => "admissible_#{x.name}", :catalog => self) - completed[x] = whit_class.new(:name => "completed_#{x.name}", :catalog => self) + admissible[x] = whit_class.new(:name => "admissible_#{x.ref}", :catalog => self) + completed[x] = whit_class.new(:name => "completed_#{x.ref}", :catalog => self) } # # Implement the six requierments listed above diff --git a/lib/puppet/status.rb b/lib/puppet/status.rb index eecd0e18c..ea6a601f3 100644 --- a/lib/puppet/status.rb +++ b/lib/puppet/status.rb @@ -10,7 +10,7 @@ class Puppet::Status @status = status || {"is_alive" => true} end - def to_pson + def to_pson(*args) @status.to_pson end diff --git a/lib/puppet/transaction/event_manager.rb b/lib/puppet/transaction/event_manager.rb index f5da870ed..8f1a695af 100644 --- a/lib/puppet/transaction/event_manager.rb +++ b/lib/puppet/transaction/event_manager.rb @@ -62,7 +62,18 @@ class Puppet::Transaction::EventManager end def queue_events_for_resource(source, target, callback, events) - source.info "Scheduling #{callback} of #{target}" + whit = Puppet::Type.type(:whit) + + # The message that a resource is refreshing the completed-whit for its own class + # is extremely counter-intuitive. Basically everything else is easy to understand, + # if you suppress the whit-lookingness of the whit resources + refreshing_c_whit = target.is_a?(whit) && target.name =~ /^completed_/ + + if refreshing_c_whit + source.debug "The container #{target} will propagate my #{callback} event" + else + source.info "Scheduling #{callback} of #{target}" + end @event_queues[target] ||= {} @event_queues[target][callback] ||= [] @@ -82,7 +93,9 @@ class Puppet::Transaction::EventManager process_noop_events(resource, callback, events) and return false unless events.detect { |e| e.status != "noop" } resource.send(callback) - resource.notice "Triggered '#{callback}' from #{events.length} events" + if not resource.is_a?(Puppet::Type.type(:whit)) + resource.notice "Triggered '#{callback}' from #{events.length} events" + end return true rescue => detail resource.err "Failed to call #{callback}: #{detail}" diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 767959308..572d5796d 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -451,8 +451,6 @@ module Puppet newparam(:ia_load_module, :required_features => :manages_aix_lam) do desc "The name of the I&A module to use to manage this user" - - defaultto "compat" end newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do diff --git a/lib/puppet/type/whit.rb b/lib/puppet/type/whit.rb index 55ed0386e..4c77915b3 100644 --- a/lib/puppet/type/whit.rb +++ b/lib/puppet/type/whit.rb @@ -5,8 +5,14 @@ Puppet::Type.newtype(:whit) do desc "The name of the whit, because it must have one." end + + # Hide the fact that we're a whit from logs def to_s - "(#{name})" + name.sub(/^completed_|^admissible_/, "") + end + + def path + to_s end def refresh diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index 714d03f74..8190f8ac1 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -65,14 +65,9 @@ module Puppet # return to the caller. How strange we are. --daniel 2011-04-11 else unless subcommand_name.nil? then - puts "Error: Unknown Puppet subcommand #{subcommand_name}.\n" + puts "Error: Unknown Puppet subcommand '#{subcommand_name}'" end - - # Doing this at the top of the file is natural, but causes puppet.rb - # to load too early, which causes things to break. This is a nasty - # thing, found in #7065. --daniel 2011-04-11 - require 'puppet/face' - puts Puppet::Face[:help, :current].help + puts "See 'puppet help' for help on available puppet subcommands" end end diff --git a/spec/fixtures/faulty_face/puppet/face/syntax.rb b/spec/fixtures/faulty_face/puppet/face/syntax.rb new file mode 100644 index 000000000..3b1e36c3f --- /dev/null +++ b/spec/fixtures/faulty_face/puppet/face/syntax.rb @@ -0,0 +1,8 @@ +Puppet::Face.define(:syntax, '1.0.0') do + action :foo do + when_invoked do |whom| + "hello, #{whom}" + end + # This 'end' is deliberately omitted, to induce a syntax error. + # Please don't fix that, as it is used for testing. --daniel 2011-05-02 +end diff --git a/spec/integration/application/doc_spec.rb b/spec/integration/application/doc_spec.rb index df9b91608..c1e463033 100755 --- a/spec/integration/application/doc_spec.rb +++ b/spec/integration/application/doc_spec.rb @@ -36,8 +36,8 @@ describe Puppet::Application::Doc do Puppet[:modulepath] = modules_dir Puppet[:manifest] = site_file puppet.options[:mode] = :rdoc - puppet.expects(:exit).with(0) - puppet.run_command + + expect { puppet.run_command }.to exit_with 0 File.should be_exist('doc') ensure diff --git a/spec/integration/faces/documentation_spec.rb b/spec/integration/faces/documentation_spec.rb new file mode 100755 index 000000000..9ddf2f1b3 --- /dev/null +++ b/spec/integration/faces/documentation_spec.rb @@ -0,0 +1,55 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + +describe "documentation of faces" do + it "should generate global help" do + help = nil + expect { help = Puppet::Face[:help, :current].help }.not_to raise_error + help.should be_an_instance_of String + help.length.should be > 200 + end + + ######################################################################## + # Can we actually generate documentation for the face, and the actions it + # has? This avoids situations where the ERB template turns out to have a + # bug in it, triggered in something the user might do. + Puppet::Face.faces.sort.each do |face_name| + # REVISIT: We should walk all versions of the face here... + let :help do Puppet::Face[:help, :current] end + + context "generating help" do + it "for #{face_name}" do + expect { + text = help.help(face_name) + text.should be_an_instance_of String + text.length.should be > 100 + }.not_to raise_error + end + + Puppet::Face[face_name, :current].actions.sort.each do |action_name| + it "for #{face_name}.#{action_name}" do + expect { + text = help.help(face_name, action_name) + text.should be_an_instance_of String + text.length.should be > 100 + }.not_to raise_error + end + end + end + + ######################################################################## + # Ensure that we have authorship and copyright information in *our* faces; + # if you apply this to third party faces you might well be disappointed. + context "licensing of Puppet Labs face '#{face_name}'" do + subject { Puppet::Face[face_name, :current] } + its :license do should =~ /Apache\s*2/ end + its :copyright do should =~ /Puppet Labs/ end + + # REVISIT: This is less that ideal, I think, but right now I am more + # comfortable watching us ship with some copyright than without any; we + # can redress that when it becomes appropriate. --daniel 2011-04-27 + its :copyright do should =~ /2011/ end + end + end +end diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index 78d62fc51..0ff50f47c 100755 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -275,7 +275,7 @@ describe Puppet::Transaction do it "should not attempt to evaluate resources with failed dependencies" do exec = Puppet::Type.type(:exec).new( - :command => "/bin/mkdir /this/path/cannot/possibly/exit", + :command => "/bin/mkdir /this/path/cannot/possibly/exist", :title => "mkdir" ) @@ -309,7 +309,7 @@ describe Puppet::Transaction do ) exec = Puppet::Type.type(:exec).new( - :command => "/bin/mkdir /this/path/cannot/possibly/exit", + :command => "/bin/mkdir /this/path/cannot/possibly/exist", :title => "mkdir", :notify => create_file1 ) diff --git a/spec/lib/puppet/face/basetest.rb b/spec/lib/puppet/face/basetest.rb index e935161ae..9398f5b2d 100755 --- a/spec/lib/puppet/face/basetest.rb +++ b/spec/lib/puppet/face/basetest.rb @@ -1,2 +1,41 @@ require 'puppet/face' -Puppet::Face.define(:basetest, '0.0.1') + +Puppet::Face.define(:basetest, '0.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + summary "This is just so tests don't fail" + + option "--[no-]boolean" + option "--mandatory ARGUMENT" + + action :foo do + option("--action") + when_invoked do |*args| args.length end + end + + action :return_true do + summary "just returns true" + when_invoked do |options| true end + end + + action :return_false do + summary "just returns false" + when_invoked do |options| false end + end + + action :return_nil do + summary "just returns nil" + when_invoked do |options| nil end + end + + action :raise do + summary "just raises an exception" + when_invoked do |options| raise ArgumentError, "your failure" end + end + + action :with_s_rendering_hook do + summary "has a rendering hook for 's'" + when_invoked do |options| "this is not the hook you are looking for" end + when_rendering :s do |value| "you invoked the 's' rendering hook" end + end +end diff --git a/spec/lib/puppet/face/huzzah.rb b/spec/lib/puppet/face/huzzah.rb index 3428c6816..6593d358a 100755 --- a/spec/lib/puppet/face/huzzah.rb +++ b/spec/lib/puppet/face/huzzah.rb @@ -1,5 +1,7 @@ require 'puppet/face' Puppet::Face.define(:huzzah, '2.0.1') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" summary "life is a thing for celebration" - action :bar do "is where beer comes from" end + script :bar do "is where beer comes from" end end diff --git a/spec/lib/puppet/face/version_matching.rb b/spec/lib/puppet/face/version_matching.rb index bfd0013f7..52bc71dbd 100644 --- a/spec/lib/puppet/face/version_matching.rb +++ b/spec/lib/puppet/face/version_matching.rb @@ -4,6 +4,8 @@ require 'puppet/face' # change this you need to ensure that is still correct. --daniel 2011-04-21 ['1.0.0', '1.0.1', '1.1.0', '1.1.1', '2.0.0'].each do |version| Puppet::Face.define(:version_matching, version) do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" summary "version matching face #{version}" script :version do version end end diff --git a/spec/lib/puppet_spec/matchers.rb b/spec/lib/puppet_spec/matchers.rb new file mode 100644 index 000000000..77f580330 --- /dev/null +++ b/spec/lib/puppet_spec/matchers.rb @@ -0,0 +1,87 @@ +require 'stringio' + +######################################################################## +# Backward compatibility for Jenkins outdated environment. +module RSpec + module Matchers + module BlockAliases + alias_method :to, :should unless method_defined? :to + alias_method :to_not, :should_not unless method_defined? :to_not + alias_method :not_to, :should_not unless method_defined? :not_to + end + end +end + + +######################################################################## +# Custom matchers... +RSpec::Matchers.define :have_matching_element do |expected| + match do |actual| + actual.any? { |item| item =~ expected } + end +end + + +RSpec::Matchers.define :exit_with do |expected| + actual = nil + match do |block| + begin + block.call + rescue SystemExit => e + actual = e.status + end + actual and actual == expected + end + failure_message_for_should do |block| + "expected exit with code #{expected} but " + + (actual.nil? ? " exit was not called" : "we exited with #{actual} instead") + end + failure_message_for_should_not do |block| + "expected that exit would not be called with #{expected}" + end + description do + "expect exit with #{expected}" + end +end + + +RSpec::Matchers.define :have_printed do |expected| + match do |block| + $stderr = $stdout = StringIO.new + + begin + block.call + ensure + $stdout.rewind + @actual = $stdout.read + + $stdout = STDOUT + $stderr = STDERR + end + + if @actual then + case expected + when String + @actual.include? expected + when Regexp + expected.match @actual + else + raise ArgumentError, "No idea how to match a #{@actual.class.name}" + end + end + end + + failure_message_for_should do |actual| + if actual.nil? then + "expected #{expected.inspect}, but nothing was printed" + else + "expected #{expected.inspect} to be printed; got:\n#{actual}" + end + end + + description do + "expect #{expected.inspect} to be printed" + end + + diffable +end diff --git a/spec/shared_behaviours/an_indirector_face.rb b/spec/shared_behaviours/an_indirector_face.rb new file mode 100644 index 000000000..cba74b696 --- /dev/null +++ b/spec/shared_behaviours/an_indirector_face.rb @@ -0,0 +1,6 @@ +shared_examples_for "an indirector face" do + [:find, :search, :save, :destroy, :info].each do |action| + it { should be_action action } + it { should respond_to action } + end +end diff --git a/spec/shared_behaviours/documentation_on_faces.rb b/spec/shared_behaviours/documentation_on_faces.rb index 41b4015c9..3cfb178f7 100644 --- a/spec/shared_behaviours/documentation_on_faces.rb +++ b/spec/shared_behaviours/documentation_on_faces.rb @@ -1,34 +1,255 @@ # encoding: UTF-8 shared_examples_for "documentation on faces" do - context "description" do - describe "#summary" do - it "should accept a summary" do - text = "this is my summary" - expect { subject.summary = text }.to_not raise_error - subject.summary.should == text + defined?(Attrs) or + Attrs = [:summary, :description, :examples, :short_description, :notes, :author] + + defined?(SingleLineAttrs) or + SingleLineAttrs = [:summary, :author] + + # Simple, procedural tests that apply to a bunch of methods. + Attrs.each do |attr| + it "should accept a #{attr}" do + expect { subject.send("#{attr}=", "hello") }.not_to raise_error + subject.send(attr).should == "hello" + end + + it "should accept a long (single line) value for #{attr}" do + text = "I never know when to stop with the word banana" + ("na" * 1000) + expect { subject.send("#{attr}=", text) }.to_not raise_error + subject.send(attr).should == text + end + end + + Attrs.each do |getter| + setter = "#{getter}=".to_sym + context "#{getter}" do + it "should strip leading whitespace on a single line" do + subject.send(setter, " death to whitespace") + subject.send(getter).should == "death to whitespace" + end + + it "should strip trailing whitespace on a single line" do + subject.send(setter, "death to whitespace ") + subject.send(getter).should == "death to whitespace" + end + + it "should strip whitespace at both ends at once" do + subject.send(setter, " death to whitespace ") + subject.send(getter).should == "death to whitespace" + end + + multiline_text = "with\nnewlines" + if SingleLineAttrs.include? getter then + it "should not accept multiline values" do + expect { subject.send(setter, multiline_text) }. + to raise_error ArgumentError, /#{getter} should be a single line/ + subject.send(getter).should be_nil + end + else + it "should accept multiline values" do + expect { subject.send(setter, multiline_text) }.not_to raise_error + subject.send(getter).should == multiline_text + end + + [1, 2, 4, 7, 25].each do |length| + context "#{length} chars indent" do + indent = ' ' * length + + it "should strip leading whitespace on multiple lines" do + text = "this\nis\the\final\outcome" + subject.send(setter, text.gsub(/^/, indent)) + subject.send(getter).should == text + end + + it "should not remove formatting whitespace, only global indent" do + text = "this\n is\n the\n ultimate\ntest\n" + subject.send(setter, text.gsub(/^/, indent)) + subject.send(getter).should == text + end + end + end + + it "should strip whitespace with a blank line" do + subject.send(setter, " this\n\n should outdent\n") + subject.send(getter).should == "this\n\nshould outdent\n" + end + end + end + end + + describe "#short_description" do + it "should return the set value if set after description" do + subject.description = "hello\ngoodbye" + subject.short_description = "whatever" + subject.short_description.should == "whatever" + end + + it "should return the set value if set before description" do + subject.short_description = "whatever" + subject.description = "hello\ngoodbye" + subject.short_description.should == "whatever" + end + + it "should return nothing if not set and no description" do + subject.short_description.should be_nil + end + + it "should return the first paragraph of description if not set (where it is one line long)" do + subject.description = "hello" + subject.short_description.should == subject.description + end + + it "should return the first paragraph of description if not set (where there is no paragraph break)" do + subject.description = "hello\ngoodbye" + subject.short_description.should == subject.description + end + + it "should return the first paragraph of description if not set (where there is a paragraph break)" do + subject.description = "hello\ngoodbye\n\nmore\ntext\nhere\n\nfinal\nparagraph" + subject.short_description.should == "hello\ngoodbye" + end + + it "should trim a very, very long first paragraph" do + line = "this is a very, very, very long long line full of text\n" + subject.description = line * 20 + "\n\nwhatever, dude." + + subject.short_description.should == (line * 5).chomp + end + end + + describe "multiple authors" do + authors = %w{John Paul George Ringo} + + context "in the DSL" do + it "should support multiple authors" do + + authors.each {|name| subject.author name } + subject.authors.should =~ authors + + subject.author.should == authors.join("\n") + end + + it "should reject author as an array" do + expect { subject.author ["Foo", "Bar"] }. + to raise_error ArgumentError, /author must be a string/ + end + end + + context "#author=" do + it "should accept a single name" do + subject.author = "Fred" + subject.author.should == "Fred" + end + + it "should accept an array of names" do + subject.author = authors + subject.authors.should =~ authors + subject.author.should == authors.join("\n") end - it "should accept a long, long, long summary" do - text = "I never know when to stop with the word banana" + ("na" * 1000) - expect { subject.summary = text }.to_not raise_error - subject.summary.should == text + it "should not append when set multiple times" do + subject.author = "Fred" + subject.author = "John" + subject.author.should == "John" end - it "should reject a summary with a newline" do - expect { subject.summary = "with\nnewlines" }. - to raise_error ArgumentError, /summary should be a single line/ + it "should reject arrays with embedded newlines" do + expect { subject.author = ["Fred\nJohn"] }. + to raise_error ArgumentError, /author should be a single line/ end end + end + + describe "#license" do + it "should default to reserving rights" do + subject.license.should =~ /All Rights Reserved/ + end + + it "should accept an arbitrary license string on the object" do + subject.license = "foo" + subject.license.should == "foo" + end + + it "should accept symbols to specify existing licenses..." + end + + describe "#copyright" do + it "should fail with just a name" do + expect { subject.copyright("invalid") }. + to raise_error ArgumentError, /copyright takes the owners names, then the years covered/ + end + + [1997, "1997"].each do |year| + it "should accept an entity name and a #{year.class.name} year" do + subject.copyright("me", year) + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /#{year}/ + end + + it "should accept multiple entity names and a #{year.class.name} year" do + subject.copyright ["me", "you"], year + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /\byou\b/ + subject.copyright.should =~ /#{year}/ + end + end + + ["1997-2003", "1997 - 2003", 1997..2003].each do |range| + it "should accept a #{range.class.name} range of years" do + subject.copyright("me", range) + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /1997-2003/ + end + + it "should accept a #{range.class.name} range of years" do + subject.copyright ["me", "you"], range + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /\byou\b/ + subject.copyright.should =~ /1997-2003/ + end + end + + [[1997, 2003], ["1997", 2003], ["1997", "2003"]].each do |input| + it "should accept the set of years #{input.inspect} in an array" do + subject.copyright "me", input + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /1997, 2003/ + end + + it "should accept the set of years #{input.inspect} in an array" do + subject.copyright ["me", "you"], input + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /\byou\b/ + subject.copyright.should =~ /1997, 2003/ + end + end + + it "should warn if someone does math accidentally on the range of years" do + expect { subject.copyright "me", 1997-2003 }. + to raise_error ArgumentError, /copyright with a year before 1970 is very strange; did you accidentally add or subtract two years\?/ + end + + it "should accept complex copyright years" do + years = [1997, 1999, 2000..2002, 2005].reverse + subject.copyright "me", years + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /1997, 1999, 2000-2002, 2005/ + end + end + + # Things that are automatically generated. + [:name, :options, :synopsis].each do |attr| + describe "##{attr}" do + it "should not allow you to set #{attr}" do + subject.should_not respond_to :"#{attr}=" + end - describe "#description" do - it "should accept a description" do - subject.description = "hello" - subject.description.should == "hello" + it "should have a #{attr}" do + subject.send(attr).should_not be_nil end - it "should accept a description with a newline" do - subject.description = "hello \n my \n fine \n friend" - subject.description.should == "hello \n my \n fine \n friend" + it "'s #{attr} should not be empty..." do + subject.send(attr).should_not == '' end end end diff --git a/spec/shared_behaviours/things_that_declare_options.rb b/spec/shared_behaviours/things_that_declare_options.rb index 5300a159a..6e7056157 100755 --- a/spec/shared_behaviours/things_that_declare_options.rb +++ b/spec/shared_behaviours/things_that_declare_options.rb @@ -28,6 +28,8 @@ shared_examples_for "things that declare options" do thing = add_options_to do option "--foo" do desc text + description text + summary text end end @@ -37,9 +39,12 @@ shared_examples_for "things that declare options" do it "should list all the options" do thing = add_options_to do option "--foo" - option "--bar" + option "--bar", '-b' + option "-q", "--quux" + option "-f" + option "--baz" end - thing.options.should =~ [:foo, :bar] + thing.options.should == [:foo, :bar, :b, :q, :quux, :f, :baz] end it "should detect conflicts in long options" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 01ffabc48..6b6b1c2fb 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,9 +17,10 @@ end require 'pathname' require 'tmpdir' -require 'lib/puppet_spec/verbose' -require 'lib/puppet_spec/files' -require 'lib/puppet_spec/fixtures' +require 'puppet_spec/verbose' +require 'puppet_spec/files' +require 'puppet_spec/fixtures' +require 'puppet_spec/matchers' require 'monkey_patches/alias_should_to_must' require 'monkey_patches/publicize_methods' require 'monkey_patches/disable_signal_trap' @@ -74,9 +75,3 @@ RSpec.configure do |config| GC.enable end end - -RSpec::Matchers.define :have_matching_element do |expected| - match do |actual| - actual.any? { |item| item =~ expected } - end -end diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb index b30a8cc6c..2e946e6bb 100755 --- a/spec/unit/application/agent_spec.rb +++ b/spec/unit/application/agent_spec.rb @@ -253,19 +253,16 @@ describe Puppet::Application::Agent do end it "should print puppet config if asked to in Puppet config" do - @puppetd.stubs(:exit) Puppet[:configprint] = "pluginsync" - - Puppet.settings.expects(:print_configs) - - @puppetd.setup + Puppet.settings.expects(:print_configs).returns true + expect { @puppetd.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet[:modulepath] = '/my/path' Puppet[:configprint] = "modulepath" Puppet::Util::Settings.any_instance.expects(:puts).with('/my/path') - lambda { @puppetd.setup }.should raise_error(SystemExit) + expect { @puppetd.setup }.to exit_with 0 end it "should set a central log destination with --centrallogs" do @@ -346,17 +343,14 @@ describe Puppet::Application::Agent do describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do - @puppetd.stubs(:exit) @puppetd.options.stubs(:[]).with(action).returns(true) - @agent.expects(action) - - @puppetd.enable_disable_client(@agent) + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end end it "should finally exit" do - lambda { @puppetd.enable_disable_client(@agent) }.should raise_error(SystemExit) + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end end @@ -410,7 +404,6 @@ describe Puppet::Application::Agent do FileTest.stubs(:exists?).with('auth').returns(true) File.stubs(:exist?).returns(true) @puppetd.options.stubs(:[]).with(:serve).returns([]) - @puppetd.stubs(:exit) @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) end @@ -419,10 +412,7 @@ describe Puppet::Application::Agent do it "should exit if no authorization file" do Puppet.stubs(:err) FileTest.stubs(:exists?).with(Puppet[:authconfig]).returns(false) - - @puppetd.expects(:exit) - - @puppetd.setup_listen + expect { @puppetd.setup_listen }.to exit_with 14 end it "should create a server to listen on at least the Runner handler" do @@ -483,35 +473,27 @@ describe Puppet::Application::Agent do @agent.stubs(:run).returns(:report) @puppetd.options.stubs(:[]).with(:client).returns(:client) @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(false) - @puppetd.stubs(:exit).with(0) Puppet.stubs(:newservice) end it "should exit if no defined --client" do $stderr.stubs(:puts) @puppetd.options.stubs(:[]).with(:client).returns(nil) - - @puppetd.expects(:exit).with(43) - - @puppetd.onetime + expect { @puppetd.onetime }.to exit_with 43 end it "should setup traps" do @daemon.expects(:set_signal_traps) - - @puppetd.onetime + expect { @puppetd.onetime }.to exit_with 0 end it "should let the agent run" do @agent.expects(:run).returns(:report) - - @puppetd.onetime + expect { @puppetd.onetime }.to exit_with 0 end it "should finish by exiting with 0 error code" do - @puppetd.expects(:exit).with(0) - - @puppetd.onetime + expect { @puppetd.onetime }.to exit_with 0 end describe "and --detailed-exitcodes" do @@ -523,18 +505,16 @@ describe Puppet::Application::Agent do Puppet[:noop] = false report = stub 'report', :exit_status => 666 @agent.stubs(:run).returns(report) - @puppetd.expects(:exit).with(666) - @puppetd.onetime + expect { @puppetd.onetime }.to exit_with 666 end it "should exit with the report's computer exit status, even if --noop is set." do Puppet[:noop] = true report = stub 'report', :exit_status => 666 @agent.stubs(:run).returns(report) - @puppetd.expects(:exit).with(666) - @puppetd.onetime + expect { @puppetd.onetime }.to exit_with 666 end end end diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index ec3f083db..74c883a3e 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -93,18 +93,14 @@ describe Puppet::Application::Apply do end it "should print puppet config if asked to in Puppet config" do - @apply.stubs(:exit) - Puppet.settings.stubs(:print_configs?).returns(true) - - Puppet.settings.expects(:print_configs) - - @apply.setup + Puppet.settings.stubs(:print_configs?).returns true + Puppet.settings.expects(:print_configs).returns true + expect { @apply.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) - - lambda { @apply.setup }.should raise_error(SystemExit) + expect { @apply.setup }.to exit_with 1 end it "should tell the report handler to cache locally as yaml" do @@ -155,8 +151,6 @@ describe Puppet::Application::Apply do @transaction = stub_everything 'transaction' @catalog.stubs(:apply).returns(@transaction) - @apply.stubs(:exit) - Puppet::Util::Storage.stubs(:load) Puppet::Configurer.any_instance.stubs(:save_last_run_summary) # to prevent it from trying to write files end @@ -165,7 +159,7 @@ describe Puppet::Application::Apply do @apply.options.stubs(:[]).with(:code).returns("code to run") Puppet.expects(:[]=).with(:code,"code to run") - @apply.main + expect { @apply.main }.to exit_with 0 end it "should set the code to run from STDIN if no arguments" do @@ -174,7 +168,7 @@ describe Puppet::Application::Apply do Puppet.expects(:[]=).with(:code,"code to run") - @apply.main + expect { @apply.main }.to exit_with 0 end it "should set the manifest if a file is passed on command line and the file exists" do @@ -183,7 +177,7 @@ describe Puppet::Application::Apply do Puppet.expects(:[]=).with(:manifest,"site.pp") - @apply.main + expect { @apply.main }.to exit_with 0 end it "should raise an error if a file is passed on command line and the file does not exist" do @@ -200,13 +194,13 @@ describe Puppet::Application::Apply do Puppet.expects(:[]=).with(:manifest,"starwarsIV") Puppet.expects(:warning).with('Only one file can be applied per run. Skipping starwarsI, starwarsII') - @apply.main + expect { @apply.main }.to exit_with 0 end it "should collect the node facts" do Puppet::Node::Facts.indirection.expects(:find).returns(@facts) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should raise an error if we can't find the node" do @@ -218,7 +212,7 @@ describe Puppet::Application::Apply do it "should look for the node" do Puppet::Node.indirection.expects(:find).returns(@node) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should raise an error if we can't find the node" do @@ -232,7 +226,7 @@ describe Puppet::Application::Apply do @node.expects(:merge).with("values") - @apply.main + expect { @apply.main }.to exit_with 0 end it "should load custom classes if loadclasses" do @@ -244,39 +238,39 @@ describe Puppet::Application::Apply do @node.expects(:classes=) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should compile the catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns(@catalog) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should transform the catalog to ral" do @catalog.expects(:to_ral).returns(@catalog) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should finalize the catalog" do @catalog.expects(:finalize) - @apply.main + 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) Puppet::Configurer.any_instance.expects(:execute_postrun_command) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should apply the catalog" do @catalog.expects(:apply).returns(stub_everything('transaction')) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should save the last run summary" do @@ -285,7 +279,7 @@ describe Puppet::Application::Apply do Puppet::Transaction::Report.stubs(:new).returns(report) Puppet::Configurer.any_instance.expects(:save_last_run_summary).with(report) - @apply.main + expect { @apply.main }.to exit_with 0 end describe "with detailed_exitcodes" do @@ -293,18 +287,16 @@ describe Puppet::Application::Apply do Puppet.stubs(:[]).with(:noop).returns(false) @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true) Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) - @apply.expects(:exit).with(666) - @apply.main + expect { @apply.main }.to exit_with 666 end it "should exit with report's computed exit status, even if --noop is set" do Puppet.stubs(:[]).with(:noop).returns(true) @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true) Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) - @apply.expects(:exit).with(666) - @apply.main + expect { @apply.main }.to exit_with 666 end it "should always exit with 0 if option is disabled" do @@ -312,9 +304,8 @@ describe Puppet::Application::Apply do @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(false) report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) - @apply.expects(:exit).with(0) - @apply.main + expect { @apply.main }.to exit_with 0 end it "should always exit with 0 if --noop" do @@ -322,9 +313,8 @@ describe Puppet::Application::Apply do @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true) report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) - @apply.expects(:exit).with(0) - @apply.main + expect { @apply.main }.to exit_with 0 end end end diff --git a/spec/unit/application/cert_spec.rb b/spec/unit/application/cert_spec.rb index 4a91c1e6c..1b1c61ab4 100755 --- a/spec/unit/application/cert_spec.rb +++ b/spec/unit/application/cert_spec.rb @@ -79,18 +79,14 @@ describe Puppet::Application::Cert do end it "should print puppet config if asked to in Puppet config" do - @cert_app.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) - - Puppet.settings.expects(:print_configs) - - @cert_app.setup + Puppet.settings.expects(:print_configs).returns true + expect { @cert_app.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) - - lambda { @cert_app.setup }.should raise_error(SystemExit) + expect { @cert_app.setup }.to exit_with 1 end it "should set the CA location to 'only'" do diff --git a/spec/unit/application/device_spec.rb b/spec/unit/application/device_spec.rb index 832b7e55b..df8cd3eaf 100755 --- a/spec/unit/application/device_spec.rb +++ b/spec/unit/application/device_spec.rb @@ -260,8 +260,7 @@ describe Puppet::Application::Device do end it "should exit if the device list is empty" do - @device.expects(:exit).with(1) - @device.main + expect { @device.main }.to exit_with 1 end describe "for each device" do diff --git a/spec/unit/application/doc_spec.rb b/spec/unit/application/doc_spec.rb index 43a4b9849..971378cd4 100755 --- a/spec/unit/application/doc_spec.rb +++ b/spec/unit/application/doc_spec.rb @@ -106,9 +106,8 @@ describe Puppet::Application::Doc do Puppet::Util::Reference.expects(:reference).with(reference).returns(ref) ref.expects(:doc) - @doc.expects(:exit) - @doc.handle_list(nil) + expect { @doc.handle_list(nil) }.to exit_with 0 end it "should add reference to references list with --reference" do @@ -279,7 +278,6 @@ describe Puppet::Application::Doc do Puppet.settings.stubs(:[]=).with(:document_all, false) Puppet.settings.stubs(:setdefaults) Puppet::Util::RDoc.stubs(:rdoc) - @doc.stubs(:exit) File.stubs(:expand_path).with('modules').returns('modules') File.stubs(:expand_path).with('manifests').returns('manifests') @doc.command_line.stubs(:args).returns([]) @@ -289,30 +287,30 @@ describe Puppet::Application::Doc do @doc.options.expects(:[]).with(:all).returns(true) Puppet.settings.expects(:[]=).with(:document_all, true) - @doc.rdoc + expect { @doc.rdoc }.to exit_with 0 end it "should call Puppet::Util::RDoc.rdoc in full mode" do Puppet::Util::RDoc.expects(:rdoc).with('doc', ['modules','manifests'], nil) - @doc.rdoc + expect { @doc.rdoc }.to exit_with 0 end it "should call Puppet::Util::RDoc.rdoc with a charset if --charset has been provided" do @doc.options.expects(:[]).with(:charset).returns("utf-8") Puppet::Util::RDoc.expects(:rdoc).with('doc', ['modules','manifests'], "utf-8") - @doc.rdoc + expect { @doc.rdoc }.to exit_with 0 end it "should call Puppet::Util::RDoc.rdoc in full mode with outputdir set to doc if no --outputdir" do @doc.options.expects(:[]).with(:outputdir).returns(false) Puppet::Util::RDoc.expects(:rdoc).with('doc', ['modules','manifests'], nil) - @doc.rdoc + expect { @doc.rdoc }.to exit_with 0 end it "should call Puppet::Util::RDoc.manifestdoc in manifest mode" do @doc.manifest = true Puppet::Util::RDoc.expects(:manifestdoc) - @doc.rdoc + expect { @doc.rdoc }.to exit_with 0 end it "should get modulepath and manifestdir values from the environment" do @@ -321,7 +319,7 @@ describe Puppet::Application::Doc do Puppet::Util::RDoc.expects(:rdoc).with('doc', ['envmodules1','envmodules2','envmanifests'], nil) - @doc.rdoc + expect { @doc.rdoc }.to exit_with 0 end end diff --git a/spec/unit/application/face_base_spec.rb b/spec/unit/application/face_base_spec.rb index f7c55c556..2a9a22356 100755 --- a/spec/unit/application/face_base_spec.rb +++ b/spec/unit/application/face_base_spec.rb @@ -7,18 +7,6 @@ class Puppet::Application::FaceBase::Basetest < Puppet::Application::FaceBase end describe Puppet::Application::FaceBase do - before :all do - Puppet::Face.define(:basetest, '0.0.1') do - option("--[no-]boolean") - option("--mandatory MANDATORY") - - action :foo do - option("--action") - when_invoked { |*args| args.length } - end - end - end - let :app do app = Puppet::Application::FaceBase::Basetest.new app.command_line.stubs(:subcommand_name).returns('subcommand') @@ -66,7 +54,7 @@ describe Puppet::Application::FaceBase do it "should use the default action if not given any arguments" do app.command_line.stubs(:args).returns [] - action = stub(:options => []) + action = stub(:options => [], :render_as => nil) Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action) app.stubs(:main) app.run @@ -76,7 +64,7 @@ describe Puppet::Application::FaceBase do it "should use the default action if not given a valid one" do app.command_line.stubs(:args).returns %w{bar} - action = stub(:options => []) + action = stub(:options => [], :render_as => nil) Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action) app.stubs(:main) app.run @@ -88,9 +76,8 @@ describe Puppet::Application::FaceBase do app.command_line.stubs(:args).returns %w{bar} Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) - app.run - app.action.should be_nil - app.arguments.should == [ 'bar', { } ] + expect { app.run }.to exit_with 1 + @logs.first.message.should =~ /does not have a default action/ end it "should report a sensible error when options with = fail" do @@ -162,7 +149,7 @@ describe Puppet::Application::FaceBase do end it "should handle application-level options" do - app.command_line.stubs(:args).returns %w{help --verbose help} + app.command_line.stubs(:args).returns %w{basetest --verbose return_true} app.preinit app.parse_options app.face.name.should == :basetest @@ -189,7 +176,7 @@ describe Puppet::Application::FaceBase do describe "#main" do before :each do - app.expects(:exit).with(0) + app.stubs(:puts) # don't dump text to screen. app.face = Puppet::Face[:basetest, '0.0.1'] app.action = app.face.get_action(:foo) @@ -198,69 +185,101 @@ describe Puppet::Application::FaceBase do it "should send the specified verb and name to the face" do app.face.expects(:foo).with(*app.arguments) - app.main + expect { app.main }.to exit_with 0 end it "should lookup help when it cannot do anything else" do app.action = nil - Puppet::Face[:help, :current].expects(:help).with(:basetest, *app.arguments) - app.stubs(:puts) # meh. Don't print nil, thanks. --daniel 2011-04-12 - app.main + Puppet::Face[:help, :current].expects(:help).with(:basetest) + expect { app.main }.to exit_with 1 end it "should use its render method to render any result" do app.expects(:render).with(app.arguments.length + 1) - app.stubs(:puts) # meh. Don't print nil, thanks. --daniel 2011-04-12 - app.main + expect { app.main }.to exit_with 0 end end - describe "#render" do + describe "error reporting" do before :each do - app.face = Puppet::Face[:basetest, '0.0.1'] - app.action = app.face.get_action(:foo) + app.stubs(:puts) # don't dump text to screen. + + app.render_as = :json + app.face = Puppet::Face[:basetest, '0.0.1'] + app.arguments = [{}] # we always have options in there... end - ["hello", 1, 1.0].each do |input| - it "should just return a #{input.class.name}" do - app.render(input).should == input - end + it "should exit 0 when the action returns true" do + app.action = app.face.get_action :return_true + expect { app.main }.to exit_with 0 end - [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| - it "should render #{input.class} using the 'pp' library" do - app.render(input).should == input.pretty_inspect - end + it "should exit 0 when the action returns false" do + app.action = app.face.get_action :return_false + expect { app.main }.to exit_with 0 + end + + it "should exit 0 when the action returns nil" do + app.action = app.face.get_action :return_nil + expect { app.main }.to exit_with 0 + end + + it "should exit non-0 when the action raises" do + app.action = app.face.get_action :return_raise + expect { app.main }.not_to exit_with 0 end + end - it "should render a non-trivially-keyed Hash with the 'pp' library" do - hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } - app.render(hash).should == hash.pretty_inspect + describe "#render" do + before :each do + app.face = Puppet::Face[:basetest, '0.0.1'] + app.action = app.face.get_action(:foo) end - it "should render a {String,Numeric}-keyed Hash into a table" do - object = Object.new - hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, - 5 => 5, 6.0 => 6 } + context "default rendering" do + before :each do app.setup end - # Gotta love ASCII-betical sort order. Hope your objects are better - # structured for display than my test one is. --daniel 2011-04-18 - app.render(hash).should == <<EOT + ["hello", 1, 1.0].each do |input| + it "should just return a #{input.class.name}" do + app.render(input).should == input + end + end + + [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| + it "should render #{input.class} using JSON" do + app.render(input).should == input.to_pson.chomp + end + end + + it "should render a non-trivially-keyed Hash with using JSON" do + hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } + app.render(hash).should == hash.to_pson.chomp + end + + it "should render a {String,Numeric}-keyed Hash into a table" do + object = Object.new + hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, + 5 => 5, 6.0 => 6 } + + # Gotta love ASCII-betical sort order. Hope your objects are better + # structured for display than my test one is. --daniel 2011-04-18 + app.render(hash).should == <<EOT 5 5 6.0 6 -four #{object.pretty_inspect.chomp} +four #{object.to_pson.chomp} one 1 three {} two [] EOT - end + end - it "should render a hash nicely with a multi-line value" do - hash = { - "number" => { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, - "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } - } - app.render(hash).should == <<EOT + it "should render a hash nicely with a multi-line value" do + pending "Moving to PSON rather than PP makes this unsupportable." + hash = { + "number" => { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, + "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } + } + app.render(hash).should == <<EOT number {"1"=>"1111111111111111111111111111111111111111", "2"=>"2222222222222222222222222222222222222222", "3"=>"3333333333333333333333333333333333333333"} @@ -268,12 +287,46 @@ text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"=>"cccccccccccccccccccccccccccccccccccccccc"} EOT + end + + it "should invoke the action rendering hook while rendering" do + app.action.set_rendering_method_for(:console, proc { |value| "bi-winning!" }) + app.render("bi-polar?").should == "bi-winning!" + end + + it "should render JSON when asked for json" do + app.render_as = :json + json = app.render({ :one => 1, :two => 2 }) + json.should =~ /"one":\s*1\b/ + json.should =~ /"two":\s*2\b/ + PSON.parse(json).should == { "one" => 1, "two" => 2 } + end + end + + it "should fail early if asked to render an invalid format" do + app.command_line.stubs(:args).returns %w{--render-as interpretive-dance help help} + # We shouldn't get here, thanks to the exception, and our expectation on + # it, but this helps us fail if that slips up and all. --daniel 2011-04-27 + Puppet::Face[:help, :current].expects(:help).never + + expect { + expect { app.run }.to exit_with 1 + }.to have_printed(/I don't know how to render 'interpretive-dance'/) + end + + it "should work if asked to render a NetworkHandler format" do + app.command_line.stubs(:args).returns %w{dummy find dummy --render-as yaml} + expect { + expect { app.run }.to exit_with 0 + }.to have_printed(/--- 3/) end - it "should invoke the action rendering hook while rendering" do - app.action.set_rendering_method_for(:for_humans, proc { |value| "bi-winning!" }) - app.action.render_as = :for_humans - app.render("bi-polar?").should == "bi-winning!" + it "should invoke when_rendering hook 's' when asked to render-as 's'" do + app.command_line.stubs(:args).returns %w{with_s_rendering_hook --render-as s} + app.action = app.face.get_action(:with_s_rendering_hook) + expect { + expect { app.run }.to exit_with 0 + }.to have_printed(/you invoked the 's' rendering hook/) end end end diff --git a/spec/unit/application/facts_spec.rb b/spec/unit/application/facts_spec.rb new file mode 100755 index 000000000..2981dc805 --- /dev/null +++ b/spec/unit/application/facts_spec.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/application/facts' + +describe Puppet::Application::Facts do + before :each do + subject.command_line.stubs(:subcommand_name).returns 'facts' + end + + it "should fail if no key is given to find" do + subject.command_line.stubs(:args).returns %w{find} + expect { + expect { subject.run }.to exit_with 1 + }.to have_printed /err: puppet facts find takes 1 argument, but you gave 0/ + @logs.first.to_s.should =~ /puppet facts find takes 1 argument, but you gave 0/ + end + + it "should return facts if a key is given to find" do + subject.command_line.stubs(:args).returns %w{find whatever --render-as yaml} + + expect { + expect { subject.run }.to exit_with 0 + }.should have_printed(/object:Puppet::Node::Facts/) + + @logs.should be_empty + end +end diff --git a/spec/unit/application/filebucket_spec.rb b/spec/unit/application/filebucket_spec.rb index 92bc0410a..ee30e7d12 100755 --- a/spec/unit/application/filebucket_spec.rb +++ b/spec/unit/application/filebucket_spec.rb @@ -78,18 +78,14 @@ describe Puppet::Application::Filebucket do end it "should print puppet config if asked to in Puppet config" do - @filebucket.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) - - Puppet.settings.expects(:print_configs) - - @filebucket.setup + Puppet.settings.expects(:print_configs).returns(true) + expect { @filebucket.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) - - lambda { @filebucket.setup }.should raise_error(SystemExit) + expect { @filebucket.setup }.to exit_with 1 end describe "with local bucket" do diff --git a/spec/unit/application/indirection_base_spec.rb b/spec/unit/application/indirection_base_spec.rb index 57740384a..910774c14 100755 --- a/spec/unit/application/indirection_base_spec.rb +++ b/spec/unit/application/indirection_base_spec.rb @@ -1,14 +1,15 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application/indirection_base' -require 'puppet/face/indirector' +require 'puppet/indirector/face' ######################################################################## # Stub for testing; the names are critical, sadly. --daniel 2011-03-30 class Puppet::Application::TestIndirection < Puppet::Application::IndirectionBase end -face = Puppet::Face::Indirector.define(:testindirection, '0.0.1') do +face = Puppet::Indirector::Face.define(:testindirection, '0.0.1') do + summary "fake summary" end # REVISIT: This horror is required because we don't allow anything to be # :current except for if it lives on, and is loaded from, disk. --daniel 2011-03-29 @@ -27,11 +28,12 @@ describe Puppet::Application::IndirectionBase do Puppet::Indirector::Indirection.expects(:instance). with(:testindirection).returns(terminus) - subject.command_line.instance_variable_set('@args', %w{--terminus foo save}) + subject.command_line.instance_variable_set('@args', %w{--terminus foo save bar}) # Not a very nice thing. :( $stderr.stubs(:puts) + Puppet.stubs(:err) - expect { subject.run }.should raise_error SystemExit + expect { subject.run }.to exit_with 0 end end diff --git a/spec/unit/application/inspect_spec.rb b/spec/unit/application/inspect_spec.rb index fda61c6e4..571683f37 100755 --- a/spec/unit/application/inspect_spec.rb +++ b/spec/unit/application/inspect_spec.rb @@ -19,7 +19,7 @@ describe Puppet::Application::Inspect do Puppet[:configprint] = "all" Puppet.settings.expects(:print_configs).returns(true) - lambda { @inspect.setup }.should raise_error(SystemExit) + expect { @inspect.setup }.to exit_with 0 end it "should fail if reporting is turned off" do diff --git a/spec/unit/application/kick_spec.rb b/spec/unit/application/kick_spec.rb index 742c0fff6..b24e78452 100755 --- a/spec/unit/application/kick_spec.rb +++ b/spec/unit/application/kick_spec.rb @@ -184,9 +184,7 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do $stderr.stubs(:puts) @kick.classes = ['class'] - @kick.expects(:exit).with(24) - - @kick.setup + expect { @kick.setup }.to exit_with 24 end end end @@ -212,9 +210,7 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do describe "the test command" do it "should exit with exit code 0 " do - @kick.expects(:exit).with(0) - - @kick.test + expect { @kick.test }.to exit_with 0 end end @@ -226,7 +222,6 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do @kick.options.stubs(:[]).with(:foreground).returns(false) @kick.options.stubs(:[]).with(:debug).returns(false) @kick.stubs(:print) - @kick.stubs(:exit) @kick.preinit @kick.stubs(:parse_options) @kick.setup @@ -236,17 +231,15 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do it "should create as much childs as --parallel" do @kick.options.stubs(:[]).with(:parallel).returns(3) @kick.hosts = ['host1', 'host2', 'host3'] - @kick.stubs(:exit).raises(SystemExit) Process.stubs(:wait).returns(1).then.returns(2).then.returns(3).then.raises(Errno::ECHILD) @kick.expects(:fork).times(3).returns(1).then.returns(2).then.returns(3) - lambda { @kick.main }.should raise_error + expect { @kick.main }.to raise_error SystemExit end it "should delegate to run_for_host per host" do @kick.hosts = ['host1', 'host2'] - @kick.stubs(:exit).raises(SystemExit) @kick.stubs(:fork).returns(1).yields Process.stubs(:wait).returns(1).then.raises(Errno::ECHILD) @@ -272,31 +265,22 @@ describe Puppet::Application::Kick, :if => Puppet.features.posix? do 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) - @kick.run_for_host('host') + expect { @kick.run_for_host('host') }.to exit_with 0 end it "should exit the child with 0 on success" do @agent_run.stubs(:status).returns("success") - - @kick.expects(:exit).with(0) - - @kick.run_for_host('host') + expect { @kick.run_for_host('host') }.to exit_with 0 end it "should exit the child with 3 on running" do @agent_run.stubs(:status).returns("running") - - @kick.expects(:exit).with(3) - - @kick.run_for_host('host') + expect { @kick.run_for_host('host') }.to exit_with 3 end it "should exit the child with 12 on unknown answer" do @agent_run.stubs(:status).returns("whatever") - - @kick.expects(:exit).with(12) - - @kick.run_for_host('host') + expect { @kick.run_for_host('host') }.to exit_with 12 end end end diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index 2a24086f3..2f6a328e2 100755 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -152,18 +152,14 @@ describe Puppet::Application::Master do end it "should print puppet config if asked to in Puppet config" do - @master.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) - - Puppet.settings.expects(:print_configs) - - @master.setup + Puppet.settings.expects(:print_configs).returns(true) + expect { @master.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) - - lambda { @master.setup }.should raise_error(SystemExit) + expect { @master.setup }.to exit_with 1 end it "should tell Puppet.settings to use :main,:ssl,:master and :metrics category" do @@ -241,7 +237,6 @@ describe Puppet::Application::Master do Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @master.stubs(:jj) - @master.stubs(:exit) Puppet.features.stubs(:pson?).returns true end @@ -255,7 +250,7 @@ describe Puppet::Application::Master do Puppet::Resource::Catalog.indirection.expects(:find).with("foo").returns Puppet::Resource::Catalog.new $stdout.stubs(:puts) - @master.compile + 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 @@ -267,25 +262,21 @@ describe Puppet::Application::Master do @master.options[:node] = "foo" @master.expects(:jj).with("rescat") - @master.compile + expect { @master.compile }.to exit_with 0 end it "should exit with error code 30 if no catalog can be found" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).returns nil - @master.expects(:exit).with(30) $stderr.expects(:puts) - - @master.compile + expect { @master.compile }.to exit_with 30 end it "should exit with error code 30 if there's a failure" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).raises ArgumentError - @master.expects(:exit).with(30) $stderr.expects(:puts) - - @master.compile + expect { @master.compile }.to exit_with 30 end end diff --git a/spec/unit/application/queue_spec.rb b/spec/unit/application/queue_spec.rb index d71c879a9..15e3927a1 100755 --- a/spec/unit/application/queue_spec.rb +++ b/spec/unit/application/queue_spec.rb @@ -86,18 +86,14 @@ describe Puppet::Application::Queue do end it "should print puppet config if asked to in Puppet config" do - @queue.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) - - Puppet.settings.expects(:print_configs) - - @queue.setup + Puppet.settings.expects(:print_configs).returns(true) + expect { @queue.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) - - lambda { @queue.setup }.should raise_error(SystemExit) + expect { @queue.setup }.to exit_with 1 end it "should call setup_logs" do diff --git a/spec/unit/application/resource_spec.rb b/spec/unit/application/resource_spec.rb index af60f12c1..9ee6dd71b 100755 --- a/spec/unit/application/resource_spec.rb +++ b/spec/unit/application/resource_spec.rb @@ -77,10 +77,8 @@ describe Puppet::Application::Resource do type2 = stub_everything 'type2', :name => :type2 Puppet::Type.stubs(:loadall) Puppet::Type.stubs(:eachtype).multiple_yields(type1,type2) - @resource.stubs(:exit) - @resource.expects(:puts).with(['type1','type2']) - @resource.handle_types(nil) + expect { @resource.handle_types(nil) }.to exit_with 0 end it "should add param to extra_params list" do diff --git a/spec/unit/application/configurer_spec.rb b/spec/unit/application/secret_agent_spec.rb index 791a367ea..eba936447 100755 --- a/spec/unit/application/configurer_spec.rb +++ b/spec/unit/application/secret_agent_spec.rb @@ -1,11 +1,11 @@ #!/usr/bin/env rspec require 'spec_helper' -require 'puppet/application/configurer' +require 'puppet/application/secret_agent' require 'puppet/indirector/catalog/rest' require 'puppet/indirector/report/rest' require 'tempfile' -describe "Puppet::Application::Configurer" do +describe "Puppet::Application::Secret_agent" do it "should retrieve and apply a catalog and submit a report" do pending "REVISIT: 2.7 changes broke this, and we want the merge published" @@ -25,7 +25,7 @@ describe "Puppet::Application::Configurer" do Puppet::Util::Log.stubs(:newdestination) - Puppet::Application::Configurer.new.run + Puppet::Application::Secret_agent.new.run @report.status.should == "changed" end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index de1ca1257..fc1bbceb6 100755 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -27,11 +27,11 @@ describe Puppet::Application do end it "should not find classes outside the namespace" do - lambda { @klass.find("String") }.should raise_error(SystemExit) + expect { @klass.find("String") }.to exit_with 1 end it "should exit if it can't find a class" do - lambda { @klass.find("ThisShallNeverEverEverExistAsdf") }.should raise_error(SystemExit) + expect { @klass.find("ThisShallNeverEverEverExist") }.to exit_with 1 end end @@ -286,10 +286,8 @@ describe Puppet::Application do describe "when using --help" do it "should call exit" do - @app.expects(:exit) @app.stubs(:puts) - - @app.handle_help(nil) + expect { @app.handle_help(nil) }.to exit_with 0 end end @@ -301,8 +299,7 @@ describe Puppet::Application do it "should exit after printing the version" do @app.stubs(:puts) - - lambda { @app.handle_version(nil) }.should raise_error(SystemExit) + expect { @app.handle_version(nil) }.to exit_with 0 end end @@ -505,22 +502,19 @@ describe Puppet::Application do it "should warn and exit if no command can be called" do $stderr.expects(:puts) - @app.expects(:exit).with(1) - @app.run + expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns no command" do @app.stubs(:get_command).returns(nil) $stderr.expects(:puts) - @app.expects(:exit).with(1) - @app.run + expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns an invalid command" do @app.stubs(:get_command).returns(:this_function_doesnt_exist) $stderr.expects(:puts) - @app.expects(:exit).with(1) - @app.run + expect { @app.run }.to exit_with 1 end end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index ed8dec2a3..e2679a966 100755 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -86,7 +86,6 @@ describe Puppet::Daemon do describe "when stopping" do before do @daemon.stubs(:remove_pidfile) - @daemon.stubs(:exit) Puppet::Util::Log.stubs(:close_all) # to make the global safe to mock, set it to a subclass of itself, # then restore it in an after pass @@ -102,34 +101,29 @@ describe Puppet::Daemon do server = mock 'server' server.expects(:stop) @daemon.stubs(:server).returns server - - @daemon.stop + expect { @daemon.stop }.to exit_with 0 end it 'should request a stop from Puppet::Application' do Puppet::Application.expects(:stop!) - @daemon.stop + expect { @daemon.stop }.to exit_with 0 end it "should remove its pidfile" do @daemon.expects(:remove_pidfile) - - @daemon.stop + expect { @daemon.stop }.to exit_with 0 end it "should close all logs" do Puppet::Util::Log.expects(:close_all) - - @daemon.stop + expect { @daemon.stop }.to exit_with 0 end it "should exit unless called with ':exit => false'" do - @daemon.expects(:exit) - @daemon.stop + expect { @daemon.stop }.to exit_with 0 end it "should not exit if called with ':exit => false'" do - @daemon.expects(:exit).never @daemon.stop :exit => false end end diff --git a/spec/unit/face/catalog_spec.rb b/spec/unit/face/catalog_spec.rb index 28c2aa9be..c77a9d153 100755 --- a/spec/unit/face/catalog_spec.rb +++ b/spec/unit/face/catalog_spec.rb @@ -1,4 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' require 'puppet/face' + describe Puppet::Face[:catalog, '0.0.1'] do it "should actually have some testing..." end diff --git a/spec/unit/face/certificate_request_spec.rb b/spec/unit/face/certificate_request_spec.rb index a83a92df8..e237800ff 100755 --- a/spec/unit/face/certificate_request_spec.rb +++ b/spec/unit/face/certificate_request_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:certificate_request, '0.0.1'] do it "should actually have some tests..." end diff --git a/spec/unit/face/certificate_revocation_list_spec.rb b/spec/unit/face/certificate_revocation_list_spec.rb index 22c0fa2bf..1033df7ff 100755 --- a/spec/unit/face/certificate_revocation_list_spec.rb +++ b/spec/unit/face/certificate_revocation_list_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:certificate_revocation_list, '0.0.1'] do it "should actually have some tests..." end diff --git a/spec/unit/face/certificate_spec.rb b/spec/unit/face/certificate_spec.rb index b0bbf1af6..0cb905b75 100755 --- a/spec/unit/face/certificate_spec.rb +++ b/spec/unit/face/certificate_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + require 'puppet/ssl/host' describe Puppet::Face[:certificate, '0.0.1'] do diff --git a/spec/unit/face/config_spec.rb b/spec/unit/face/config_spec.rb index 6004d700f..0c762f2aa 100755 --- a/spec/unit/face/config_spec.rb +++ b/spec/unit/face/config_spec.rb @@ -1,5 +1,6 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'puppet/face' describe Puppet::Face[:config, '0.0.1'] do it "should use Settings#print_config_options when asked to print" do diff --git a/spec/unit/face/facts_spec.rb b/spec/unit/face/facts_spec.rb index 6ab6ad5be..27b5b9e3d 100755 --- a/spec/unit/face/facts_spec.rb +++ b/spec/unit/face/facts_spec.rb @@ -1,5 +1,6 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'puppet/face' describe Puppet::Face[:facts, '0.0.1'] do it "should define an 'upload' action" do @@ -8,9 +9,15 @@ describe Puppet::Face[:facts, '0.0.1'] do describe "when uploading" do it "should set the terminus_class to :facter" + it "should set the cache_class to :rest" + it "should find the current certname" + end - it "should set the cach_eclass to :rest" + describe "#find" do + it { should be_action :find } - it "should find the current certname" + it "should fail without a key" do + expect { subject.find }.to raise_error ArgumentError, /wrong number of arguments/ + end end end diff --git a/spec/unit/face/file_spec.rb b/spec/unit/face/file_spec.rb index 97e8bcc08..c3f05720f 100755 --- a/spec/unit/face/file_spec.rb +++ b/spec/unit/face/file_spec.rb @@ -1,3 +1,12 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:file, '0.0.1'] do - it "should actually have some tests..." + it_should_behave_like "an indirector face" + + [:download, :store].each do |action| + it { should be_action action } + it { should respond_to action } + end end diff --git a/spec/unit/face/help_spec.rb b/spec/unit/face/help_spec.rb index faa5f9617..ef66660ff 100755 --- a/spec/unit/face/help_spec.rb +++ b/spec/unit/face/help_spec.rb @@ -1,5 +1,6 @@ +#!/usr/bin/env rspec require 'spec_helper' -require 'puppet/face/help' +require 'puppet/face' describe Puppet::Face[:help, '0.0.1'] do it "should have a help action" do @@ -46,6 +47,12 @@ describe Puppet::Face[:help, '0.0.1'] do context "when listing subcommands" do subject { Puppet::Face[:help, :current].help } + RSpec::Matchers.define :have_a_summary do + match do |instance| + instance.summary.is_a?(String) + end + end + # Check a precondition for the next block; if this fails you have # something odd in your set of face, and we skip testing things that # matter. --daniel 2011-04-10 @@ -65,6 +72,12 @@ describe Puppet::Face[:help, '0.0.1'] do end end + Puppet::Face.faces.each do |name| + it "should have a summary for #{name}" do + Puppet::Face[name, :current].should have_a_summary + end + end + it "should list all legacy applications" do Puppet::Face[:help, :current].legacy_applications.each do |appname| subject.should =~ %r{ #{appname} } diff --git a/spec/unit/face/key_spec.rb b/spec/unit/face/key_spec.rb index 10d664790..7de4c6e76 100755 --- a/spec/unit/face/key_spec.rb +++ b/spec/unit/face/key_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:key, '0.0.1'] do it "should actually have some tests..." end diff --git a/spec/unit/face/node_spec.rb b/spec/unit/face/node_spec.rb index d19312c58..027a4cce0 100755 --- a/spec/unit/face/node_spec.rb +++ b/spec/unit/face/node_spec.rb @@ -1,5 +1,6 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'puppet/face' describe Puppet::Face[:node, '0.0.1'] do it "REVISIT: really should have some tests" diff --git a/spec/unit/face/plugin_spec.rb b/spec/unit/face/plugin_spec.rb new file mode 100755 index 000000000..383aaa3d3 --- /dev/null +++ b/spec/unit/face/plugin_spec.rb @@ -0,0 +1,10 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + +describe Puppet::Face[:plugin, '0.0.1'] do + [:download].each do |action| + it { should be_action action } + it { should respond_to action } + end +end diff --git a/spec/unit/face/report_spec.rb b/spec/unit/face/report_spec.rb index b1b28167e..befc4e496 100755 --- a/spec/unit/face/report_spec.rb +++ b/spec/unit/face/report_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:report, '0.0.1'] do it "should actually have some tests..." end diff --git a/spec/unit/face/resource_spec.rb b/spec/unit/face/resource_spec.rb index 084e2a6a9..0671af4c2 100755 --- a/spec/unit/face/resource_spec.rb +++ b/spec/unit/face/resource_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:resource, '0.0.1'] do it "should actually have some tests..." end diff --git a/spec/unit/face/resource_type_spec.rb b/spec/unit/face/resource_type_spec.rb index 2adaedca1..30a1adfcb 100755 --- a/spec/unit/face/resource_type_spec.rb +++ b/spec/unit/face/resource_type_spec.rb @@ -1,3 +1,7 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + describe Puppet::Face[:resource_type, '0.0.1'] do it "should actually have some tests..." end diff --git a/spec/unit/face/configurer_spec.rb b/spec/unit/face/secret_agent_spec.rb index 56b45031f..a5ec01f27 100755 --- a/spec/unit/face/configurer_spec.rb +++ b/spec/unit/face/secret_agent_spec.rb @@ -1,13 +1,13 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'puppet/face' require 'puppet/indirector/catalog/rest' require 'tempfile' -describe Puppet::Face[:configurer, '0.0.1'] do +describe Puppet::Face[:secret_agent, '0.0.1'] do describe "#synchronize" do it "should retrieve and apply a catalog and return a report" do - pending "REVISIT: 2.7 changes broke this, and we want the merge published" - + pending "This test doesn't work, but the code actually does - tested by LAK" dirname = Dir.mktmpdir("puppetdir") Puppet[:vardir] = dirname Puppet[:confdir] = dirname @@ -16,7 +16,7 @@ describe Puppet::Face[:configurer, '0.0.1'] do @catalog.add_resource(@file) Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) - report = subject.synchronize("foo") + report = subject.synchronize report.kind.should == "apply" report.status.should == "changed" diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index a369ad39c..41810650a 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -20,6 +20,13 @@ describe Puppet::FileServing::Fileset, " when initializing" do fileset.path.should == path end + it "should not fail if the path is just the file separator" do + 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("/some/file").returns nil proc { Puppet::FileServing::Fileset.new("/some/file") }.should raise_error(ArgumentError) diff --git a/spec/unit/face/indirector_spec.rb b/spec/unit/indirector/face_spec.rb index bb06fcfe2..1530f7270 100755 --- a/spec/unit/face/indirector_spec.rb +++ b/spec/unit/indirector/face_spec.rb @@ -1,10 +1,10 @@ #!/usr/bin/env rspec require 'spec_helper' -require 'puppet/face/indirector' +require 'puppet/indirector/face' -describe Puppet::Face::Indirector do +describe Puppet::Indirector::Face do subject do - instance = Puppet::Face::Indirector.new(:test, '0.0.1') + instance = Puppet::Indirector::Face.new(:test, '0.0.1') indirection = stub('indirection', :name => :stub_indirection, :reset_terminus_class => nil) @@ -13,33 +13,33 @@ describe Puppet::Face::Indirector do end it "should be able to return a list of indirections" do - Puppet::Face::Indirector.indirections.should be_include("catalog") + Puppet::Indirector::Face.indirections.should be_include("catalog") end it "should be able to return a list of terminuses for a given indirection" do - Puppet::Face::Indirector.terminus_classes(:catalog).should be_include("compiler") + Puppet::Indirector::Face.terminus_classes(:catalog).should be_include("compiler") end describe "as an instance" do it "should be able to determine its indirection" do # Loading actions here an get, um, complicated Puppet::Face.stubs(:load_actions) - Puppet::Face::Indirector.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) + Puppet::Indirector::Face.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) end end [:find, :search, :save, :destroy].each do |method| it "should define a '#{method}' action" do - Puppet::Face::Indirector.should be_action(method) + Puppet::Indirector::Face.should be_action(method) end it "should call the indirection method with options when the '#{method}' action is invoked" do - subject.indirection.expects(method).with(:test, "myargs", {}) - subject.send(method, :test, "myargs") + subject.indirection.expects(method).with(:test, {}) + subject.send(method, :test) end it "should forward passed options" do - subject.indirection.expects(method).with(:test, "action", {'one'=>'1'}) - subject.send(method, :test, 'action', {'one'=>'1'}) + subject.indirection.expects(method).with(:test, {'one'=>'1'}) + subject.send(method, :test, {'one'=>'1'}) end end @@ -54,6 +54,6 @@ describe Puppet::Face::Indirector do end it "should define a class-level 'info' action" do - Puppet::Face::Indirector.should be_action(:info) + Puppet::Indirector::Face.should be_action(:info) end end diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb index ba7dc815e..87b9af438 100755 --- a/spec/unit/indirector/request_spec.rb +++ b/spec/unit/indirector/request_spec.rb @@ -301,99 +301,4 @@ describe Puppet::Indirector::Request do lambda { @request.query_string }.should raise_error(ArgumentError) end end - - describe "when converting to json" do - before do - @request = Puppet::Indirector::Request.new(:facts, :find, "foo") - end - - it "should produce a hash with the document_type set to 'request'" do - @request.should set_json_document_type_to("Puppet::Indirector::Request") - end - - it "should set the 'key'" do - @request.should set_json_attribute("key").to("foo") - end - - it "should include an attribute for its indirection name" do - @request.should set_json_attribute("type").to("facts") - end - - it "should include a 'method' attribute set to its method" do - @request.should set_json_attribute("method").to("find") - end - - it "should add all attributes under the 'attributes' attribute" do - @request.ip = "127.0.0.1" - @request.should set_json_attribute("attributes", "ip").to("127.0.0.1") - end - - it "should add all options under the 'attributes' attribute" do - @request.options["opt"] = "value" - PSON.parse(@request.to_pson)["data"]['attributes']['opt'].should == "value" - end - - it "should include the instance if provided" do - facts = Puppet::Node::Facts.new("foo") - @request.instance = facts - PSON.parse(@request.to_pson)["data"]['instance'].should be_instance_of(Puppet::Node::Facts) - end - end - - describe "when converting from json" do - before do - @request = Puppet::Indirector::Request.new(:facts, :find, "foo") - @klass = Puppet::Indirector::Request - @format = Puppet::Network::FormatHandler.format('pson') - end - - def from_json(json) - @format.intern(Puppet::Indirector::Request, json) - end - - it "should set the 'key'" do - from_json(@request.to_pson).key.should == "foo" - end - - it "should fail if no key is provided" do - json = PSON.parse(@request.to_pson) - json['data'].delete("key") - lambda { from_json(json.to_pson) }.should raise_error(ArgumentError) - end - - it "should set its indirector name" do - from_json(@request.to_pson).indirection_name.should == :facts - end - - it "should fail if no type is provided" do - json = PSON.parse(@request.to_pson) - json['data'].delete("type") - lambda { from_json(json.to_pson) }.should raise_error(ArgumentError) - end - - it "should set its method" do - from_json(@request.to_pson).method.should == "find" - end - - it "should fail if no method is provided" do - json = PSON.parse(@request.to_pson) - json['data'].delete("method") - lambda { from_json(json.to_pson) }.should raise_error(ArgumentError) - end - - it "should initialize with all attributes and options" do - @request.ip = "127.0.0.1" - @request.options["opt"] = "value" - result = from_json(@request.to_pson) - result.options[:opt].should == "value" - result.ip.should == "127.0.0.1" - end - - it "should set its instance as an instance if one is provided" do - facts = Puppet::Node::Facts.new("foo") - @request.instance = facts - result = from_json(@request.to_pson) - result.instance.should be_instance_of(Puppet::Node::Facts) - end - end end diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb index 6f36d2b44..e9f10a1a6 100755 --- a/spec/unit/interface/action_builder_spec.rb +++ b/spec/unit/interface/action_builder_spec.rb @@ -7,7 +7,8 @@ describe Puppet::Interface::ActionBuilder do let :face do Puppet::Interface.new(:puppet_interface_actionbuilder, '0.0.1') end it "should build an action" do - action = Puppet::Interface::ActionBuilder.build(nil, :foo) do + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end end action.should be_a(Puppet::Interface::Action) action.name.should == :foo @@ -26,17 +27,25 @@ describe Puppet::Interface::ActionBuilder do should raise_error("Action :foo must specify a block") end + it "should require an invocation block" do + expect { + Puppet::Interface::ActionBuilder.build(face, :foo) {} + }.to raise_error(/actions need to know what to do when_invoked; please add the block/) + end + describe "when handling options" do it "should have a #option DSL function" do method = nil Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end method = self.method(:option) end - method.should be + method.should be_an_instance_of Method end it "should define an option without a block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end option "--bar" end action.should be_option :bar @@ -44,6 +53,7 @@ describe Puppet::Interface::ActionBuilder do it "should accept an empty block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end option "--bar" do # This space left deliberately blank. end @@ -58,15 +68,18 @@ describe Puppet::Interface::ActionBuilder do option '-w' action(:foo) do + when_invoked do true end option '-x', '--ex' option '-y', '--why' end action(:bar) do + when_invoked do true end option '-z', '--zee' end action(:baz) do + when_invoked do true end option '-z', '--zed' end end @@ -75,6 +88,7 @@ describe Puppet::Interface::ActionBuilder do it 'should add the options from the specified action' do foo = face.get_action(:foo) action = Puppet::Interface::ActionBuilder.build(face, :inherit_options) do + when_invoked do true end inherit_options_from foo end action.options.should == foo.options @@ -84,15 +98,17 @@ describe Puppet::Interface::ActionBuilder do foo = face.get_action(:foo) bar = face.get_action(:bar) action = Puppet::Interface::ActionBuilder.build(face, :inherit_options) do + when_invoked do true end inherit_options_from foo inherit_options_from bar end - action.options.should == (foo.options + bar.options).uniq.sort + action.options.should == (foo.options + bar.options).uniq end it 'should permit symbolic names for actions in the same face' do foo = face.get_action(:foo) action = Puppet::Interface::ActionBuilder.build(face, :inherit_options) do + when_invoked do true end inherit_options_from :foo end action.options.should == foo.options @@ -101,6 +117,7 @@ describe Puppet::Interface::ActionBuilder do it 'should raise a useful error if you supply a bad action name' do expect do Puppet::Interface::ActionBuilder.build(face, :inherit_options) do + when_invoked do true end inherit_options_from :nowhere end end.to raise_error /nowhere/ @@ -110,6 +127,7 @@ describe Puppet::Interface::ActionBuilder do context "inline documentation" do it "should set the summary" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end summary "this is some text" end action.summary.should == "this is some text" @@ -119,13 +137,16 @@ describe Puppet::Interface::ActionBuilder do context "action defaulting" do it "should set the default to true" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end default end action.default.should be_true end it "should not be default by, er, default. *cough*" do - action = Puppet::Interface::ActionBuilder.build(face, :foo) do end + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end + end action.default.should be_false end end @@ -134,6 +155,7 @@ describe Puppet::Interface::ActionBuilder do it "should fail if no rendering format is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering do true end end }.to raise_error ArgumentError, /must give a rendering format to when_rendering/ @@ -142,6 +164,7 @@ describe Puppet::Interface::ActionBuilder do it "should fail if no block is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json end }.to raise_error ArgumentError, /must give a block to when_rendering/ @@ -150,6 +173,7 @@ describe Puppet::Interface::ActionBuilder do it "should fail if the block takes no arguments" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do true end end }.to raise_error ArgumentError, /when_rendering methods take one argument, the result, not/ @@ -158,6 +182,7 @@ describe Puppet::Interface::ActionBuilder do it "should fail if the block takes more than one argument" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do |a, b, c| true end end }.to raise_error ArgumentError, /when_rendering methods take one argument, the result, not/ @@ -166,6 +191,7 @@ describe Puppet::Interface::ActionBuilder do it "should fail if the block takes a variable number of arguments" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do |*args| true end end }.to raise_error(ArgumentError, @@ -174,6 +200,7 @@ describe Puppet::Interface::ActionBuilder do it "should stash a rendering block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do |a| true end end action.when_rendering(:json).should be_an_instance_of Method @@ -182,6 +209,7 @@ describe Puppet::Interface::ActionBuilder do it "should fail if you try to set the same rendering twice" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do |a| true end when_rendering :json do |a| true end end @@ -190,6 +218,7 @@ describe Puppet::Interface::ActionBuilder do it "should work if you set two different renderings" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do |a| true end when_rendering :yaml do |a| true end end @@ -199,6 +228,7 @@ describe Puppet::Interface::ActionBuilder do it "should be bound to the face when called" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end when_rendering :json do |a| self end end action.when_rendering(:json).call(true).should == face @@ -207,13 +237,16 @@ describe Puppet::Interface::ActionBuilder do context "#render_as" do it "should default to nil (eg: based on context)" do - action = Puppet::Interface::ActionBuilder.build(face, :foo) do end + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end + end action.render_as.should be_nil end it "should fail if not rendering format is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end render_as end }.to raise_error ArgumentError, /must give a rendering format to render_as/ @@ -222,23 +255,18 @@ describe Puppet::Interface::ActionBuilder do Puppet::Network::FormatHandler.formats.each do |name| it "should accept #{name.inspect} format" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end render_as name end action.render_as.should == name end end - it "should accept :for_humans format" do - action = Puppet::Interface::ActionBuilder.build(face, :foo) do - render_as :for_humans - end - action.render_as.should == :for_humans - end - [:if_you_define_this_format_you_frighten_me, "json", 12].each do |input| it "should fail if given #{input.inspect}" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do true end render_as input end }.to raise_error ArgumentError, /#{input.inspect} is not a valid rendering format/ diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb index 07d517c18..5a479ad5c 100755 --- a/spec/unit/interface/action_manager_spec.rb +++ b/spec/unit/interface/action_manager_spec.rb @@ -94,7 +94,7 @@ describe Puppet::Interface::ActionManager do end it "should be able to indicate when an action is defined" do - subject.action(:foo) { "something" } + subject.action(:foo) { when_invoked do true end } subject.should be_action(:foo) end end @@ -218,25 +218,29 @@ describe Puppet::Interface::ActionManager do describe "#action" do it 'should add an action' do - subject.action(:foo) { } + subject.action(:foo) { when_invoked do true end } subject.get_action(:foo).should be_a Puppet::Interface::Action end it 'should support default actions' do - subject.action(:foo) { default } + subject.action(:foo) { when_invoked do true end; default } subject.get_default_action.should == subject.get_action(:foo) end it 'should not support more than one default action' do - subject.action(:foo) { default } - expect { subject.action(:bar) { default } }.should raise_error + subject.action(:foo) { when_invoked do true end; default } + expect { subject.action(:bar) { + when_invoked do true end + default + } + }.should raise_error /cannot both be default/ end end describe "#get_action" do let :parent_class do parent_class = Class.new(Puppet::Interface) - parent_class.action(:foo) {} + parent_class.action(:foo) { when_invoked do true end } parent_class end diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb index 735fbcb72..f43968709 100755 --- a/spec/unit/interface/action_spec.rb +++ b/spec/unit/interface/action_spec.rb @@ -74,25 +74,29 @@ describe Puppet::Interface::Action do option '-w' action(:foo) do + when_invoked do true end option '-x', '--ex' option '-y', '--why' end action(:bar) do + when_invoked do true end option '-z', '--zee' end action(:baz) do + when_invoked do true end option '-z', '--zed' end action(:noopts) do # no options declared + when_invoked do true end end end end - subject { action = face.action(:new_action) { } } + subject { action = face.action(:new_action) { when_invoked do true end } } it 'should add the options from the specified action' do subject.inherit_options_from(foo = face.get_action(:foo)) @@ -108,7 +112,7 @@ describe Puppet::Interface::Action do it 'should add the options from multiple actions' do subject.inherit_options_from(foo = face.get_action(:foo)) subject.inherit_options_from(bar = face.get_action(:bar)) - subject.options.should == (foo.options + bar.options).uniq.sort + subject.options.should == (foo.options + bar.options).uniq end it 'should not inherit face options' do @@ -205,6 +209,7 @@ describe Puppet::Interface::Action do it "should support options with an empty block" do face = Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do + when_invoked do true end option "--bar" do # this line left deliberately blank end @@ -217,7 +222,10 @@ describe Puppet::Interface::Action do it "should return only action level options when there are no face options" do face = Puppet::Interface.new(:action_level_options, '0.0.1') do - action :foo do option "--bar" end + action :foo do + when_invoked do true end + option "--bar" + end end face.get_action(:foo).options.should =~ [:bar] @@ -226,8 +234,8 @@ describe Puppet::Interface::Action do describe "with both face and action options" do let :face do Puppet::Interface.new(:action_level_options, '0.0.1') do - action :foo do option "--bar" end - action :baz do option "--bim" end + action :foo do when_invoked do true end ; option "--bar" end + action :baz do when_invoked do true end ; option "--bim" end option "--quux" end end @@ -241,7 +249,10 @@ describe Puppet::Interface::Action do parent.option "--foo" child = parent.new(:inherited_options, '0.0.1') do option "--bar" - action :action do option "--baz" end + action :action do + when_invoked do true end + option "--baz" + end end action = child.get_action(:action) @@ -270,7 +281,10 @@ describe Puppet::Interface::Action do it_should_behave_like "things that declare options" do def add_options_to(&block) face = Puppet::Interface.new(:with_options, '0.0.1') do - action(:foo, &block) + action(:foo) do + when_invoked do true end + self.instance_eval &block + end end face.get_action(:foo) end @@ -292,7 +306,7 @@ describe Puppet::Interface::Action do when_invoked { } end end - expect { face.bar }.to raise_error ArgumentError, /missing required options \(foo\)/ + expect { face.bar }.to raise_error ArgumentError, /The following options are required: foo/ end it "should fail when a required face option is not provided" do @@ -300,184 +314,251 @@ describe Puppet::Interface::Action do option('--foo') { required } action(:bar) { when_invoked { } } end - expect { face.bar }.to raise_error ArgumentError, /missing required options \(foo\)/ + expect { face.bar }.to raise_error ArgumentError, /The following options are required: foo/ end end - context "with action decorators" do - context "local only" do + context "with decorators" do + context "declared locally" do let :face do Puppet::Interface.new(:action_decorators, '0.0.1') do action :bar do when_invoked do true end end - def report(arg) end + def reported; @reported; end + def report(arg) + (@reported ||= []) << arg + end end end - it "should call action before decorators" do - face.action(:baz) do - option "--baz" do - before_action do |action, args, options| - report(:action_option) - end - end - when_invoked do true end + it "should execute before advice on action options in declaration order" do + face.action(:boo) do + option("--foo") { before_action { |_,_,_| report :foo } } + option("--bar", '-b') { before_action { |_,_,_| report :bar } } + option("-q", "--quux") { before_action { |_,_,_| report :quux } } + option("-f") { before_action { |_,_,_| report :f } } + option("--baz") { before_action { |_,_,_| report :baz } } + when_invoked { } end - face.expects(:report).with(:action_option) - face.baz :baz => true + face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 + face.reported.should == [ :foo, :bar, :quux, :f, :baz ] end - it "should call action after decorators" do - face.action(:baz) do - option "--baz" do - after_action do |action, args, options| - report(:action_option) - end - end - when_invoked do true end + it "should execute after advice on action options in declaration order" do + face.action(:boo) do + option("--foo") { after_action { |_,_,_| report :foo } } + option("--bar", '-b') { after_action { |_,_,_| report :bar } } + option("-q", "--quux") { after_action { |_,_,_| report :quux } } + option("-f") { after_action { |_,_,_| report :f } } + option("--baz") { after_action { |_,_,_| report :baz } } + when_invoked { } end - face.expects(:report).with(:action_option) - face.baz :baz => true + face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 + face.reported.should == [ :foo, :bar, :quux, :f, :baz ].reverse end - it "should call local before decorators" do - face.option "--foo FOO" do - before_action do |action, args, options| - report(:before) - end + it "should execute before advice on face options in declaration order" do + face.instance_eval do + option("--foo") { before_action { |_,_,_| report :foo } } + option("--bar", '-b') { before_action { |_,_,_| report :bar } } + option("-q", "--quux") { before_action { |_,_,_| report :quux } } + option("-f") { before_action { |_,_,_| report :f } } + option("--baz") { before_action { |_,_,_| report :baz } } end - face.expects(:report).with(:before) - face.bar({:foo => 12}) + face.script(:boo) { } + + face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 + face.reported.should == [ :foo, :bar, :quux, :f, :baz ] end - it "should call local after decorators" do - face.option "--foo FOO" do - after_action do |action, args, options| report(:after) end + it "should execute after advice on face options in declaration order" do + face.instance_eval do + option("--foo") { after_action { |_,_,_| report :foo } } + option("--bar", '-b') { after_action { |_,_,_| report :bar } } + option("-q", "--quux") { after_action { |_,_,_| report :quux } } + option("-f") { after_action { |_,_,_| report :f } } + option("--baz") { after_action { |_,_,_| report :baz } } end - face.expects(:report).with(:after) - face.bar({:foo => 12}) + face.script(:boo) { } + + face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 + face.reported.should == [ :foo, :bar, :quux, :f, :baz ].reverse end - context "with inactive decorators" do - it "should not invoke a decorator if the options are empty" do - face.option "--foo FOO" do - before_action do |action, args, options| - report :before_action - end + it "should execute before advice on face options before action options" do + face.instance_eval do + option("--face-foo") { before_action { |_,_,_| report :face_foo } } + option("--face-bar", '-r') { before_action { |_,_,_| report :face_bar } } + action(:boo) do + option("--action-foo") { before_action { |_,_,_| report :action_foo } } + option("--action-bar", '-b') { before_action { |_,_,_| report :action_bar } } + option("-q", "--action-quux") { before_action { |_,_,_| report :action_quux } } + option("-a") { before_action { |_,_,_| report :a } } + option("--action-baz") { before_action { |_,_,_| report :action_baz } } + when_invoked { } end - face.expects(:report).never # I am testing the negative. - face.bar + option("-u", "--face-quux") { before_action { |_,_,_| report :face_quux } } + option("-f") { before_action { |_,_,_| report :f } } + option("--face-baz") { before_action { |_,_,_| report :face_baz } } + end + + expected_calls = [ :face_foo, :face_bar, :face_quux, :f, :face_baz, + :action_foo, :action_bar, :action_quux, :a, :action_baz ] + face.boo Hash[ *expected_calls.zip([]).flatten ] + face.reported.should == expected_calls + end + + it "should execute after advice on face options in declaration order" do + face.instance_eval do + option("--face-foo") { after_action { |_,_,_| report :face_foo } } + option("--face-bar", '-r') { after_action { |_,_,_| report :face_bar } } + action(:boo) do + option("--action-foo") { after_action { |_,_,_| report :action_foo } } + option("--action-bar", '-b') { after_action { |_,_,_| report :action_bar } } + option("-q", "--action-quux") { after_action { |_,_,_| report :action_quux } } + option("-a") { after_action { |_,_,_| report :a } } + option("--action-baz") { after_action { |_,_,_| report :action_baz } } + when_invoked { } + end + option("-u", "--face-quux") { after_action { |_,_,_| report :face_quux } } + option("-f") { after_action { |_,_,_| report :f } } + option("--face-baz") { after_action { |_,_,_| report :face_baz } } end - context "with some decorators only" do - before :each do - face.option "--foo" do - before_action do |action, args, options| report :foo end - end - face.option "--bar" do - before_action do |action, args, options| report :bar end - end - end + expected_calls = [ :face_foo, :face_bar, :face_quux, :f, :face_baz, + :action_foo, :action_bar, :action_quux, :a, :action_baz ] + face.boo Hash[ *expected_calls.zip([]).flatten ] + face.reported.should == expected_calls.reverse + end - it "should work with the foo option" do - face.expects(:report).with(:foo) - face.bar(:foo => true) - end + it "should not invoke a decorator if the options are empty" do + face.option("--foo FOO") { before_action { |_,_,_| report :before_action } } + face.expects(:report).never + face.bar + end - it "should work with the bar option" do - face.expects(:report).with(:bar) - face.bar(:bar => true) - end + context "passing a subset of the options" do + before :each do + face.option("--foo") { before_action { |_,_,_| report :foo } } + face.option("--bar") { before_action { |_,_,_| report :bar } } + end - it "should work with both options" do - face.expects(:report).with(:foo) - face.expects(:report).with(:bar) - face.bar(:foo => true, :bar => true) - end + it "should invoke only foo's advice when passed only 'foo'" do + face.bar(:foo => true) + face.reported.should == [ :foo ] + end + + it "should invoke only bar's advice when passed only 'bar'" do + face.bar(:bar => true) + face.reported.should == [ :bar ] + end + + it "should invoke advice for all passed options" do + face.bar(:foo => true, :bar => true) + face.reported.should == [ :foo, :bar ] end end end - context "with inherited decorators" do + context "and inheritance" do let :parent do - parent = Class.new(Puppet::Interface) - parent.script :on_parent do :on_parent end - parent.define_method :report do |arg| arg end - parent + Class.new(Puppet::Interface) do + script(:on_parent) { :on_parent } + + def reported; @reported; end + def report(arg) + (@reported ||= []) << arg + end + end end let :child do - child = parent.new(:inherited_decorators, '0.0.1') do - script :on_child do :on_child end + parent.new(:inherited_decorators, '0.0.1') do + script(:on_child) { :on_child } end end - context "with a child decorator" do + context "locally declared face options" do subject do - child.option "--foo FOO" do - before_action do |action, args, options| - report(:child_before) - end - end - child.expects(:report).with(:child_before) + child.option("--foo=") { before_action { |_,_,_| report :child_before } } child end - it "child actions should invoke the decorator" do - subject.on_child({:foo => true, :bar => true}).should == :on_child + it "should be invoked when calling a child action" do + subject.on_child(:foo => true, :bar => true).should == :on_child + subject.reported.should == [ :child_before ] end - it "parent actions should invoke the decorator" do - subject.on_parent({:foo => true, :bar => true}).should == :on_parent + it "should be invoked when calling a parent action" do + subject.on_parent(:foo => true, :bar => true).should == :on_parent + subject.reported.should == [ :child_before ] end end - context "with a parent decorator" do + context "inherited face option decorators" do subject do - parent.option "--foo FOO" do - before_action do |action, args, options| - report(:parent_before) - end - end - child.expects(:report).with(:parent_before) + parent.option("--foo=") { before_action { |_,_,_| report :parent_before } } child end - it "child actions should invoke the decorator" do - subject.on_child({:foo => true, :bar => true}).should == :on_child + it "should be invoked when calling a child action" do + subject.on_child(:foo => true, :bar => true).should == :on_child + subject.reported.should == [ :parent_before ] end - it "parent actions should invoke the decorator" do - subject.on_parent({:foo => true, :bar => true}).should == :on_parent + it "should be invoked when calling a parent action" do + subject.on_parent(:foo => true, :bar => true).should == :on_parent + subject.reported.should == [ :parent_before ] end end - context "with child and parent decorators" do + context "with both inherited and local face options" do + # Decorations should be invoked in declaration order, according to + # inheritance (e.g. parent class options should be handled before + # subclass options). subject do - parent.option "--foo FOO" do - before_action { |action, args, options| report(:parent_before) } - after_action { |action, args, options| report(:parent_after) } + child.option "-c" do + before_action { |action, args, options| report :c_before } + after_action { |action, args, options| report :c_after } end - child.option "--bar BAR" do - before_action { |action, args, options| report(:child_before) } - after_action { |action, args, options| report(:child_after) } + + parent.option "-a" do + before_action { |action, args, options| report :a_before } + after_action { |action, args, options| report :a_after } end - child.expects(:report).with(:child_before) - child.expects(:report).with(:parent_before) - child.expects(:report).with(:parent_after) - child.expects(:report).with(:child_after) + child.option "-d" do + before_action { |action, args, options| report :d_before } + after_action { |action, args, options| report :d_after } + end + + parent.option "-b" do + before_action { |action, args, options| report :b_before } + after_action { |action, args, options| report :b_after } + end + + child.script(:decorations) { report :invoked } child end - it "child actions should invoke all the decorator" do - subject.on_child({:foo => true, :bar => true}).should == :on_child + it "should invoke all decorations when calling a child action" do + subject.decorations(:a => 1, :b => 1, :c => 1, :d => 1) + subject.reported.should == [ + :a_before, :b_before, :c_before, :d_before, + :invoked, + :d_after, :c_after, :b_after, :a_after + ] end - it "parent actions should invoke all the decorator" do - subject.on_parent({:foo => true, :bar => true}).should == :on_parent + it "should invoke all decorations when calling a parent action" do + subject.decorations(:a => 1, :b => 1, :c => 1, :d => 1) + subject.reported.should == [ + :a_before, :b_before, :c_before, :d_before, + :invoked, + :d_after, :c_after, :b_after, :a_after + ] end end end @@ -486,7 +567,9 @@ describe Puppet::Interface::Action do it_should_behave_like "documentation on faces" do subject do face = Puppet::Interface.new(:action_documentation, '0.0.1') do - action :documentation do end + action :documentation do + when_invoked do true end + end end face.get_action(:documentation) end @@ -500,4 +583,23 @@ describe Puppet::Interface::Action do it "should fail if a second block is given for the same type" it "should return the block if asked" end + + context "#validate_args" do + subject do + Puppet::Interface.new(:validate_args, '1.0.0') do + script :test do true end + end + end + + it "should fail if a required option is not passed" do + subject.option "--foo" do required end + expect { subject.test }.to raise_error ArgumentError, /options are required/ + end + + it "should fail if two aliases to one option are passed" do + subject.option "--foo", "-f" + expect { subject.test :foo => true, :f => true }. + to raise_error ArgumentError, /Multiple aliases for the same option/ + end + end end diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb index 890e06a9e..4ad8787c5 100755 --- a/spec/unit/interface/face_collection_spec.rb +++ b/spec/unit/interface/face_collection_spec.rb @@ -16,12 +16,13 @@ describe Puppet::Interface::FaceCollection do @original_faces = subject.instance_variable_get("@faces").dup @original_required = $".dup $".delete_if do |path| path =~ %r{/face/.*\.rb$} end - subject.instance_variable_get("@faces").clear + subject.instance_variable_get(:@faces).clear + subject.instance_variable_set(:@loaded, false) end after :each do - subject.instance_variable_set("@faces", @original_faces) - $".clear ; @original_required.each do |item| $" << item end + subject.instance_variable_set(:@faces, @original_faces) + @original_required.each {|f| $".push f unless $".include? f } end describe "::prefix_match?" do @@ -159,4 +160,21 @@ describe Puppet::Interface::FaceCollection do end end end + + context "faulty faces" do + before :each do + $:.unshift "#{PuppetSpec::FIXTURE_DIR}/faulty_face" + end + + after :each do + $:.delete_if {|x| x == "#{PuppetSpec::FIXTURE_DIR}/faulty_face"} + end + + it "should not die if a face has a syntax error" do + subject.faces.should be_include :help + subject.faces.should_not be_include :syntax + @logs.should_not be_empty + @logs.first.message.should =~ /syntax error/ + end + end end diff --git a/spec/unit/interface/option_builder_spec.rb b/spec/unit/interface/option_builder_spec.rb index e9346852c..3e91c683b 100755 --- a/spec/unit/interface/option_builder_spec.rb +++ b/spec/unit/interface/option_builder_spec.rb @@ -16,13 +16,15 @@ describe Puppet::Interface::OptionBuilder do option.should be_an_instance_of Puppet::Interface::Option end - it "should support documentation declarations" do - text = "this is the description" - option = Puppet::Interface::OptionBuilder.build(face, "--foo") do - desc text + [:description, :summary].each do |doc| + it "should support #{doc} declarations" do + text = "this is the #{doc}" + option = Puppet::Interface::OptionBuilder.build(face, "--foo") do + self.send doc, text + end + option.should be_an_instance_of Puppet::Interface::Option + option.send(doc).should == text end - option.should be_an_instance_of Puppet::Interface::Option - option.desc.should == text end context "before_action hook" do diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb index a1d70cf64..e28e55aac 100755 --- a/spec/unit/interface_spec.rb +++ b/spec/unit/interface_spec.rb @@ -76,7 +76,11 @@ describe Puppet::Interface do # Required documentation methods... { :summary => "summary", - :description => "This is the description of the stuff\n\nWhee" + :description => "This is the description of the stuff\n\nWhee", + :examples => "This is my example", + :short_description => "This is my custom short description", + :notes => "These are my notes...", + :author => "This is my authorship data", }.each do |attr, value| it "should support #{attr} in the builder" do face = subject.new(:builder, '1.0.0') do @@ -142,6 +146,7 @@ describe Puppet::Interface do option "--foo" option "--bar" action :baz do + when_invoked { true } option "--quux" end end @@ -151,7 +156,10 @@ describe Puppet::Interface do it "should fail when a face option duplicates an action option" do expect { subject.new(:action_level_options, '0.0.1') do - action :bar do option "--foo" end + action :bar do + when_invoked { true } + option "--foo" + end option "--foo" end }.should raise_error ArgumentError, /Option foo conflicts with existing option foo on/i @@ -159,8 +167,8 @@ describe Puppet::Interface do it "should work when two actions have the same option" do face = subject.new(:with_options, '0.0.1') do - action :foo do option "--quux" end - action :bar do option "--quux" end + action :foo do when_invoked { true } ; option "--quux" end + action :bar do when_invoked { true } ; option "--quux" end end face.get_action(:foo).options.should =~ [:quux] @@ -172,14 +180,14 @@ describe Puppet::Interface do let :parent do parent = Class.new(subject) parent.option("--inherited") - parent.action(:parent_action) do end + parent.action(:parent_action) do when_invoked { true } end parent end let :face do face = parent.new(:example, '0.2.1') face.option("--local") - face.action(:face_action) do end + face.action(:face_action) do when_invoked { true } end face end diff --git a/spec/unit/network/formats_spec.rb b/spec/unit/network/formats_spec.rb index 72d355192..62c2dbb9d 100755 --- a/spec/unit/network/formats_spec.rb +++ b/spec/unit/network/formats_spec.rb @@ -330,4 +330,66 @@ describe "Puppet Network Format" do end end end + + describe ":console format" do + subject { Puppet::Network::FormatHandler.format(:console) } + it { should be_an_instance_of Puppet::Network::Format } + let :json do Puppet::Network::FormatHandler.format(:pson) end + + [:intern, :intern_multiple].each do |method| + it "should not implement #{method}" do + expect { subject.send(method, String, 'blah') }.to raise_error NotImplementedError + end + end + + ["hello", 1, 1.0].each do |input| + it "should just return a #{input.inspect}" do + subject.render(input).should == input + end + end + + [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| + it "should render #{input.inspect} as JSON" do + subject.render(input).should == json.render(input).chomp + end + end + + it "should render a non-trivially-keyed Hash as JSON" do + hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } + subject.render(hash).should == json.render(hash).chomp + end + + it "should render a {String,Numeric}-keyed Hash into a table" do + object = Object.new + hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, + 5 => 5, 6.0 => 6 } + + # Gotta love ASCII-betical sort order. Hope your objects are better + # structured for display than my test one is. --daniel 2011-04-18 + subject.render(hash).should == <<EOT +5 5 +6.0 6 +four #{json.render(object).chomp} +one 1 +three {} +two [] +EOT + end + + it "should render a hash nicely with a multi-line value" do + pending "Moving to PSON rather than PP makes this unsupportable." + hash = { + "number" => { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, + "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } + } + subject.render(hash).should == <<EOT +number {"1"=>"1111111111111111111111111111111111111111", + "2"=>"2222222222222222222222222222222222222222", + "3"=>"3333333333333333333333333333333333333333"} +text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "c"=>"cccccccccccccccccccccccccccccccccccccccc"} +EOT + end + end end diff --git a/spec/unit/network/http/api/v1_spec.rb b/spec/unit/network/http/api/v1_spec.rb index bd95071c1..a952f24e2 100755 --- a/spec/unit/network/http/api/v1_spec.rb +++ b/spec/unit/network/http/api/v1_spec.rb @@ -31,7 +31,7 @@ describe Puppet::Network::HTTP::API::V1 do end it "should use the first field of the URI as the environment" do - @tester.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].should == "env" + @tester.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].to_s.should == "env" end it "should fail if the environment is not alphanumeric" do @@ -39,7 +39,11 @@ describe Puppet::Network::HTTP::API::V1 do end it "should use the environment from the URI even if one is specified in the parameters" do - @tester.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"})[3][:environment].should == "env" + @tester.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"})[3][:environment].to_s.should == "env" + end + + it "should return the environment as a Puppet::Node::Environment" do + @tester.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].should be_a Puppet::Node::Environment end it "should use the second field of the URI as the indirection name" do diff --git a/spec/unit/network/rest_authconfig_spec.rb b/spec/unit/network/rest_authconfig_spec.rb index 499a14b78..e1403997f 100755 --- a/spec/unit/network/rest_authconfig_spec.rb +++ b/spec/unit/network/rest_authconfig_spec.rb @@ -5,18 +5,7 @@ require 'puppet/network/rest_authconfig' describe Puppet::Network::RestAuthConfig do - DEFAULT_ACL = [ - { :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, - # this one will allow all file access, and thus delegate - # to fileserver.conf - { :acl => "/file" }, - { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true }, - { :acl => "/report", :method => :save, :authenticated => true }, - { :acl => "/certificate/ca", :method => :find, :authenticated => false }, - { :acl => "/certificate/", :method => :find, :authenticated => false }, - { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false }, - { :acl => "/status", :method => [:find], :authenticated => true }, - ] + DEFAULT_ACL = Puppet::Network::RestAuthConfig::DEFAULT_ACL before :each do FileTest.stubs(:exists?).returns(true) diff --git a/spec/unit/node/facts_spec.rb b/spec/unit/node/facts_spec.rb index 07f5f7e1b..efaa76e12 100755 --- a/spec/unit/node/facts_spec.rb +++ b/spec/unit/node/facts_spec.rb @@ -110,11 +110,7 @@ describe Puppet::Node::Facts, "when indirecting" do end it "should accept properly formatted pson" do - facts = Puppet::Node::Facts.new("foo") - facts.values = {"a" => "1", "b" => "2", "c" => "3"} - facts.expiration = Time.now - #pson = %Q({"document_type": "Puppet::Node::Facts", "data: {"name": "foo", "expiration": "#{@expiration}", "timestamp": "#{@timestamp}", "values": {"a": "1", "b": "2", "c": "3"}}}) - pson = %Q({"data": {"name":"foo", "expiration":"#{@expiration}", "timestamp": "#{@timestamp}", "values":{"a":"1","b":"2","c":"3"}}, "document_type":"Puppet::Node::Facts"}) + pson = %Q({"name": "foo", "expiration": "#{@expiration}", "timestamp": "#{@timestamp}", "values": {"a": "1", "b": "2", "c": "3"}}) format = Puppet::Network::FormatHandler.format('pson') facts = format.intern(Puppet::Node::Facts,pson) facts.name.should == 'foo' @@ -127,29 +123,10 @@ describe Puppet::Node::Facts, "when indirecting" do facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3}) facts.expiration = @expiration result = PSON.parse(facts.to_pson) - result.name.should == facts.name - result.values.should == facts.values - result.timestamp.should == facts.timestamp - result.expiration.should == facts.expiration - result.type.should == Puppet::Node::Facts - end - - it "should not include nil values" do - facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3}) - - # XXX:LAK For some reason this is resurrection the full instance, instead - # of just returning the hash. This code works, but I can't figure out what's - # going on. - newfacts = PSON.parse(facts.to_pson) - newfacts.expiration.should be_nil - end - - it "should be able to handle nil values" do - pson = %Q({"name": "foo", "values": {"a": "1", "b": "2", "c": "3"}}) - format = Puppet::Network::FormatHandler.format('pson') - facts = format.intern(Puppet::Node::Facts,pson) - facts.name.should == 'foo' - facts.expiration.should be_nil + result['name'].should == facts.name + result['values'].should == facts.values.reject { |key, value| key.to_s =~ /_/ } + result['timestamp'].should == facts.timestamp.to_s + result['expiration'].should == facts.expiration.to_s end end end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index e8f826dca..169a9cdcf 100755 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -36,69 +36,6 @@ describe Puppet::Node do node.environment.name.should == :bar end end - - describe "when converting to json" do - before do - @node = Puppet::Node.new("mynode") - end - - it "should provide its name" do - @node.should set_json_attribute('name').to("mynode") - end - - it "should include the classes if set" do - @node.classes = %w{a b c} - @node.should set_json_attribute("classes").to(%w{a b c}) - end - - it "should not include the classes if there are none" do - @node.should_not set_json_attribute('classes') - end - - it "should include parameters if set" do - @node.parameters = {"a" => "b", "c" => "d"} - @node.should set_json_attribute('parameters').to({"a" => "b", "c" => "d"}) - end - - it "should not include the parameters if there are none" do - @node.should_not set_json_attribute('parameters') - end - - it "should include the environment" do - @node.environment = "production" - @node.should set_json_attribute('environment').to('production') - end - end - - describe "when converting from json" do - before do - @node = Puppet::Node.new("mynode") - @format = Puppet::Network::FormatHandler.format('pson') - end - - def from_json(json) - @format.intern(Puppet::Node, json) - end - - it "should set its name" do - Puppet::Node.should read_json_attribute('name').from(@node.to_pson).as("mynode") - end - - it "should include the classes if set" do - @node.classes = %w{a b c} - Puppet::Node.should read_json_attribute('classes').from(@node.to_pson).as(%w{a b c}) - end - - it "should include parameters if set" do - @node.parameters = {"a" => "b", "c" => "d"} - Puppet::Node.should read_json_attribute('parameters').from(@node.to_pson).as({"a" => "b", "c" => "d"}) - end - - it "should include the environment" do - @node.environment = "production" - Puppet::Node.should read_json_attribute('environment').from(@node.to_pson).as(Puppet::Node::Environment.new(:production)) - end - end end describe Puppet::Node, "when initializing" do diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 48aeb98dd..88f7167f3 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -48,6 +48,13 @@ describe Puppet::Parser::Compiler do end before :each do + # Push me faster, I wanna go back in time! (Specifically, freeze time + # across the test since we have a bunch of version == timestamp code + # hidden away in the implementation and we keep losing the race.) + # --daniel 2011-04-21 + now = Time.now + Time.stubs(:now).returns(now) + @node = Puppet::Node.new "testnode" @known_resource_types = Puppet::Resource::TypeCollection.new "development" @compiler = Puppet::Parser::Compiler.new(@node) diff --git a/spec/unit/simple_graph_spec.rb b/spec/unit/simple_graph_spec.rb index c8fea3b58..472ebbd36 100755 --- a/spec/unit/simple_graph_spec.rb +++ b/spec/unit/simple_graph_spec.rb @@ -525,6 +525,10 @@ describe Puppet::SimpleGraph do def to_s @name end + + def ref + "Container[#{self}]" + end end require "puppet/resource/catalog" @@ -536,7 +540,7 @@ describe Puppet::SimpleGraph do @middle = Container.new("middle", ["e", "f", @two]) @top = Container.new("top", ["g", "h", @middle, @one, @three]) @empty = Container.new("empty", []) - + @whit = Puppet::Type.type(:whit) @stage = Puppet::Type.type(:stage).new(:name => "foo") @@ -574,7 +578,7 @@ describe Puppet::SimpleGraph do end def whit_called(name) - x = @depgraph.vertices.find { |v| v.is_a?(@whit) && v.name =~ /#{name}/ } + x = @depgraph.vertices.find { |v| v.is_a?(@whit) && v.name =~ /#{Regexp.escape(name)}/ } x.should_not be_nil def x.to_s "Whit[#{name}]" @@ -586,11 +590,11 @@ describe Puppet::SimpleGraph do end def admissible_sentinal_of(x) - @depgraph.vertex?(x) ? x : whit_called("admissible_#{x.name}") + @depgraph.vertex?(x) ? x : whit_called("admissible_#{x.ref}") end def completed_sentinal_of(x) - @depgraph.vertex?(x) ? x : whit_called("completed_#{x.name}") + @depgraph.vertex?(x) ? x : whit_called("completed_#{x.ref}") end before do @@ -618,7 +622,7 @@ describe Puppet::SimpleGraph do # 0) completed_X depends on admissible_X # it "every container's completed sentinal should depend on its admissible sentinal" do - containers.each { |container| + containers.each { |container| @depgraph.path_between(admissible_sentinal_of(container),completed_sentinal_of(container)).should be } end @@ -626,7 +630,7 @@ describe Puppet::SimpleGraph do # 1) contents of X each depend on admissible_X # it "all contained objects should depend on their container's admissible sentinal" do - containers.each { |container| + containers.each { |container| contents_of(container).each { |leaf| @depgraph.should be_edge(admissible_sentinal_of(container),admissible_sentinal_of(leaf)) } @@ -636,7 +640,7 @@ describe Puppet::SimpleGraph do # 2) completed_X depends on each on the contents of X # it "completed sentinals should depend on their container's contents" do - containers.each { |container| + containers.each { |container| contents_of(container).each { |leaf| @depgraph.should be_edge(completed_sentinal_of(leaf),completed_sentinal_of(container)) } diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index 2624c2234..d14da538e 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -681,16 +681,14 @@ describe Puppet::SSL::Host do @host.expects(:certificate).returns(nil) @host.expects(:generate).raises(RuntimeError) @host.expects(:puts) - @host.expects(:exit).with(1).raises(SystemExit) - lambda { @host.wait_for_cert(0) }.should raise_error(SystemExit) + expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do @host.stubs(:certificate).returns nil @host.expects(:generate) @host.expects(:puts) - @host.expects(:exit).with(1).raises(SystemExit) - lambda { @host.wait_for_cert(0) }.should raise_error(SystemExit) + expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do diff --git a/spec/unit/status_spec.rb b/spec/unit/status_spec.rb index 820807638..0c572fd95 100755 --- a/spec/unit/status_spec.rb +++ b/spec/unit/status_spec.rb @@ -15,6 +15,10 @@ describe Puppet::Status do Puppet::Status.new.status.to_pson.should == '{"is_alive":true}' end + it "should render to a pson hash" do + PSON::pretty_generate(Puppet::Status.new).should =~ /"is_alive":\s*true/ + end + it "should accept a hash from pson" do status = Puppet::Status.new( { "is_alive" => false } ) status.status.should == { "is_alive" => false } diff --git a/spec/unit/type/whit_spec.rb b/spec/unit/type/whit_spec.rb index 4d0949900..9a076d1f1 100755 --- a/spec/unit/type/whit_spec.rb +++ b/spec/unit/type/whit_spec.rb @@ -5,6 +5,6 @@ whit = Puppet::Type.type(:whit).new(:name => "Foo::Bar") describe whit do it "should stringify in a way that users will regognise" do - whit.to_s.should == "(Foo::Bar)" + whit.to_s.should == "Foo::Bar" end end diff --git a/spec/unit/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb index 81612ee5c..d60bbb198 100755 --- a/spec/unit/util/command_line_spec.rb +++ b/spec/unit/util/command_line_spec.rb @@ -1,7 +1,7 @@ #!/usr/bin/env rspec require 'spec_helper' - +require 'puppet/face' require 'puppet/util/command_line' describe Puppet::Util::CommandLine do @@ -98,11 +98,9 @@ describe Puppet::Util::CommandLine do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline.expects(:system).never - text = Puppet::Face[:help, :current].help - commandline.expects(:puts).with { |x| x =~ /Unknown Puppet subcommand/ } - commandline.expects(:puts).with text - - commandline.execute + expect { + commandline.execute + }.to have_printed(/Unknown Puppet subcommand 'whatever'/) end end end |
