1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
|
# frozen_string_literal: true
require_relative '../../puppet/application'
require_relative '../../puppet/face'
require 'optparse'
class Puppet::Application::FaceBase < Puppet::Application
option("--debug", "-d") do |_arg|
set_log_level(:debug => true)
end
option("--verbose", "-v") do |_|
set_log_level(:verbose => true)
end
option("--render-as FORMAT") do |format|
self.render_as = format.to_sym
end
option("--help", "-h") do |_arg|
if action && !@is_default_action
# Only invoke help on the action if it was specified, not if
# it was the default action.
puts Puppet::Face[:help, :current].help(face.name, action.name)
else
puts Puppet::Face[:help, :current].help(face.name)
end
exit(0)
end
attr_reader :render_as
attr_accessor :face, :action, :type, :arguments
def render_as=(format)
@render_as = Puppet::Network::FormatHandler.format(format)
@render_as or raise ArgumentError, _("I don't know how to render '%{format}'") % { format: format }
end
def render(result, args_and_options)
hook = action.when_rendering(render_as.name)
if hook
# when defining when_rendering on your action you can optionally
# include arguments and options
if hook.arity > 1
result = hook.call(result, *args_and_options)
else
result = hook.call(result)
end
end
render_as.render(result)
end
def preinit
super
Signal.trap(:INT) do
$stderr.puts _("Cancelling Face")
exit(0)
end
end
def parse_options
# We need to parse enough of the command line out early, to identify what
# the action is, so that we can obtain the full set of options to parse.
# REVISIT: These should be configurable versions, through a global
# '--version' option, but we don't implement that yet... --daniel 2011-03-29
@type = Puppet::Util::ConstantInflector.constant2file(self.class.name.to_s.sub(/.+:/, '')).to_sym
@face = Puppet::Face[@type, :current]
# Now, walk the command line and identify the action. We skip over
# arguments based on introspecting the action and all, and find the first
# non-option word to use as the action.
action_name = nil
index = -1
until action_name or (index += 1) >= command_line.args.length
item = command_line.args[index]
if item =~ /^-/
option = @face.options.find do |name|
item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/
end
if option
option = @face.get_option(option)
# If we have an inline argument, just carry on. We don't need to
# care about optional vs mandatory in that case because we do a real
# parse later, and that will totally take care of raising the error
# when we get there. --daniel 2011-04-04
if option.takes_argument? and !item.index('=')
index += 1 unless
option.optional_argument? and command_line.args[index + 1] =~ /^-/
end
else
option = find_global_settings_argument(item)
if option
unless Puppet.settings.boolean? option.name
# As far as I can tell, we treat non-bool options as always having
# a mandatory argument. --daniel 2011-04-05
# ... But, the mandatory argument will not be the next item if an = is
# employed in the long form of the option. --jeffmccune 2012-09-18
index += 1 unless item =~ /^--#{option.name}=/
end
else
option = find_application_argument(item)
if option
index += 1 if option[:argument] and !(option[:optional])
else
raise OptionParser::InvalidOption, item.sub(/=.*$/, '')
end
end
end
else
# Stash away the requested action name for later, and try to fetch the
# action object it represents; if this is an invalid action name that
# will be nil, and handled later.
action_name = item.to_sym
@action = Puppet::Face.find_action(@face.name, action_name)
@face = @action.face if @action
end
end
if @action.nil?
@action = @face.get_default_action()
if @action
@is_default_action = true
else
# First try to handle global command line options
# But ignoring invalid options as this is a invalid action, and
# we want the error message for that instead.
begin
super
rescue OptionParser::InvalidOption
end
face = @face.name
action = action_name.nil? ? 'default' : "'#{action_name}'"
msg = _("'%{face}' has no %{action} action. See `puppet help %{face}`.") % { face: face, action: action }
Puppet.err(msg)
Puppet::Util::Log.force_flushqueue()
exit false
end
end
# Now we can interact with the default option code to build behaviour
# around the full set of options we now know we support.
@action.options.each do |o|
o = @action.get_option(o) # make it the object.
self.class.option(*o.optparse) # ...and make the CLI parse it.
end
# ...and invoke our parent to parse all the command line options.
super
end
def find_global_settings_argument(item)
Puppet.settings.each do |_name, object|
object.optparse_args.each do |arg|
next unless arg =~ /^-/
# sadly, we have to emulate some of optparse here...
pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/
pattern.match item and return object
end
end
nil # nothing found.
end
def find_application_argument(item)
self.class.option_parser_commands.each do |options, _function|
options.each do |option|
next unless option =~ /^-/
pattern = /^#{option.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/
next unless pattern.match(item)
return {
:argument => option =~ /[ =]/,
:optional => option =~ /[ =]\[/
}
end
end
nil # not found
end
def setup
Puppet::Util::Log.newdestination :console
@arguments = command_line.args
# Note: because of our definition of where the action is set, we end up
# with it *always* being the first word of the remaining set of command
# line arguments. So, strip that off when we construct the arguments to
# pass down to the face action. --daniel 2011-04-04
# Of course, now that we have default actions, we should leave the
# "action" name on if we didn't actually consume it when we found our
# action.
@arguments.delete_at(0) unless @is_default_action
# We copy all of the app options to the end of the call; This allows each
# action to read in the options. This replaces the older model where we
# 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').
unless @action
puts Puppet::Face[:help, :current].help(@face.name)
raise _("%{face} does not respond to action %{arg}") % { face: face, arg: arguments.first }
end
# 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, n_("puppet %{face} %{action} takes %{arg_count} argument, but you gave %{given_count}", "puppet %{face} %{action} takes %{arg_count} arguments, but you gave %{given_count}", arity - 1) % { face: @face.name, action: @action.name, arg_count: arity - 1, given_count: count - 1 }
end
end
if @face.deprecated?
Puppet.deprecation_warning(_("'puppet %{face}' is deprecated and will be removed in a future release") % { face: @face.name })
end
result = @face.send(@action.name, *arguments)
puts render(result, arguments) unless result.nil?
status = true
# We need an easy way for the action to set a specific exit code, so we
# rescue SystemExit here; This allows each action to set the desired exit
# code by simply calling Kernel::exit. eg:
#
# exit(2)
#
# --kelsey 2012-02-14
rescue SystemExit => detail
status = detail.status
rescue => detail
Puppet.log_exception(detail)
Puppet.err _("Try 'puppet help %{face} %{action}' for usage") % { face: @face.name, action: @action.name }
ensure
exit status
end
end
|