diff options
| author | Daniel Pittman <daniel@puppetlabs.com> | 2011-04-27 14:21:42 -0700 |
|---|---|---|
| committer | Daniel Pittman <daniel@puppetlabs.com> | 2011-04-28 14:50:12 -0700 |
| commit | 69e4b1c5c024f4e6087054a7d8e77d30cacc62c8 (patch) | |
| tree | 8286e2f2ea9090f8e60fd30d55cc7020574673f8 /lib/puppet/application | |
| parent | 351b6fc5327baf8f2339fed04c3a8e54280fc394 (diff) | |
| download | puppet-69e4b1c5c024f4e6087054a7d8e77d30cacc62c8.tar.gz puppet-69e4b1c5c024f4e6087054a7d8e77d30cacc62c8.tar.xz puppet-69e4b1c5c024f4e6087054a7d8e77d30cacc62c8.zip | |
(#7122) Enforce call arity on actions in the CLI wrapper.
We had a problem, previously, in the generic translation of command line
arguments to Ruby method calls: we could mistake the options, added by the CLI
wrapper, for a positional argument to the action method.
This was caused by a combination of factors, but primarily that the wrapper
methods for actions are designed to present a friendly, helpful Ruby API for
internal use. Consequently, they have a default value if you don't wish to
pass options.
Unfortunately, this meant that the options that the CLI *always* passed could
be treated as a positional argument instead, and the default set of options
added to the back of the call.
To resolve this we now check the number of positional arguments in the CLI
wrapper, and raise an exception if they are mismatched. This makes the
generic CLI handling do the right thing in adapting the command line
arguments to the Ruby API.
(As an aside, we would have had a similar-but-different failure mode if we
type-checked positional arguments: these calls would have failed with an
invalid argument validation error.)
Reviewed-By: Max Martin <max@puppetlabs.com>
Diffstat (limited to 'lib/puppet/application')
| -rw-r--r-- | lib/puppet/application/face_base.rb | 45 |
1 files changed, 44 insertions, 1 deletions
diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb index d02769412..69c3ad5ad 100644 --- a/lib/puppet/application/face_base.rb +++ b/lib/puppet/application/face_base.rb @@ -214,12 +214,55 @@ class Puppet::Application::FaceBase < Puppet::Application # Call the method associated with the provided action (e.g., 'find'). if @action begin + # 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 + raise ArgumentError, "wrong number of arguments (#{count} for #{arity})" + end + end + 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 + + case detail + when ArgumentError then + got, want = /\((\d+) for (\d+)\)/.match(detail.to_s).to_a.map {|x| x.to_i } + Puppet.err "puppet #{@face.name} #{@action.name}: #{want} argument expected but #{got} given" + Puppet.err "Try 'puppet help #{@face.name} #{@action.name}' for usage" + + else # generic exception handling, alas. + Puppet.err detail.to_s + end end else puts "#{face} does not respond to action #{arguments.first}" |
