From 5592034fdb8bf3a72ab3133d69443490f9ad7b78 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 7 Apr 2011 13:18:26 -0700 Subject: (#7012) global rename of strings to faces. This just changes filenames and directories; files are exact copies rather than having additional modifications to make clearer each step of this process. This does leave a currently broken build. :/ --- lib/puppet/application/faces.rb | 95 ++++++++++++++ lib/puppet/application/faces_base.rb | 150 +++++++++++++++++++++++ lib/puppet/application/string.rb | 95 -------------- lib/puppet/application/string_base.rb | 150 ----------------------- lib/puppet/faces.rb | 104 ++++++++++++++++ lib/puppet/faces/action.rb | 121 ++++++++++++++++++ lib/puppet/faces/action_builder.rb | 31 +++++ lib/puppet/faces/action_manager.rb | 49 ++++++++ lib/puppet/faces/catalog.rb | 40 ++++++ lib/puppet/faces/catalog/select.rb | 10 ++ lib/puppet/faces/certificate.rb | 46 +++++++ lib/puppet/faces/certificate_request.rb | 4 + lib/puppet/faces/certificate_revocation_list.rb | 4 + lib/puppet/faces/config.rb | 12 ++ lib/puppet/faces/configurer.rb | 12 ++ lib/puppet/faces/faces_collection.rb | 123 +++++++++++++++++++ lib/puppet/faces/facts.rb | 18 +++ lib/puppet/faces/file.rb | 5 + lib/puppet/faces/indirector.rb | 94 ++++++++++++++ lib/puppet/faces/key.rb | 4 + lib/puppet/faces/node.rb | 5 + lib/puppet/faces/option.rb | 82 +++++++++++++ lib/puppet/faces/option_builder.rb | 25 ++++ lib/puppet/faces/option_manager.rb | 56 +++++++++ lib/puppet/faces/report.rb | 15 +++ lib/puppet/faces/resource.rb | 4 + lib/puppet/faces/resource_type.rb | 4 + lib/puppet/faces/status.rb | 4 + lib/puppet/string.rb | 104 ---------------- lib/puppet/string/action.rb | 121 ------------------ lib/puppet/string/action_builder.rb | 31 ----- lib/puppet/string/action_manager.rb | 49 -------- lib/puppet/string/catalog.rb | 40 ------ lib/puppet/string/catalog/select.rb | 10 -- lib/puppet/string/certificate.rb | 46 ------- lib/puppet/string/certificate_request.rb | 4 - lib/puppet/string/certificate_revocation_list.rb | 4 - lib/puppet/string/config.rb | 12 -- lib/puppet/string/configurer.rb | 12 -- lib/puppet/string/facts.rb | 18 --- lib/puppet/string/file.rb | 5 - lib/puppet/string/indirector.rb | 94 -------------- lib/puppet/string/key.rb | 4 - lib/puppet/string/node.rb | 5 - lib/puppet/string/option.rb | 82 ------------- lib/puppet/string/option_builder.rb | 25 ---- lib/puppet/string/option_manager.rb | 56 --------- lib/puppet/string/report.rb | 15 --- lib/puppet/string/resource.rb | 4 - lib/puppet/string/resource_type.rb | 4 - lib/puppet/string/status.rb | 4 - lib/puppet/string/string_collection.rb | 123 ------------------- 52 files changed, 1117 insertions(+), 1117 deletions(-) create mode 100644 lib/puppet/application/faces.rb create mode 100644 lib/puppet/application/faces_base.rb delete mode 100644 lib/puppet/application/string.rb delete mode 100644 lib/puppet/application/string_base.rb create mode 100644 lib/puppet/faces.rb create mode 100644 lib/puppet/faces/action.rb create mode 100644 lib/puppet/faces/action_builder.rb create mode 100644 lib/puppet/faces/action_manager.rb create mode 100644 lib/puppet/faces/catalog.rb create mode 100644 lib/puppet/faces/catalog/select.rb create mode 100644 lib/puppet/faces/certificate.rb create mode 100644 lib/puppet/faces/certificate_request.rb create mode 100644 lib/puppet/faces/certificate_revocation_list.rb create mode 100644 lib/puppet/faces/config.rb create mode 100644 lib/puppet/faces/configurer.rb create mode 100644 lib/puppet/faces/faces_collection.rb create mode 100644 lib/puppet/faces/facts.rb create mode 100644 lib/puppet/faces/file.rb create mode 100644 lib/puppet/faces/indirector.rb create mode 100644 lib/puppet/faces/key.rb create mode 100644 lib/puppet/faces/node.rb create mode 100644 lib/puppet/faces/option.rb create mode 100644 lib/puppet/faces/option_builder.rb create mode 100644 lib/puppet/faces/option_manager.rb create mode 100644 lib/puppet/faces/report.rb create mode 100644 lib/puppet/faces/resource.rb create mode 100644 lib/puppet/faces/resource_type.rb create mode 100644 lib/puppet/faces/status.rb delete mode 100644 lib/puppet/string.rb delete mode 100644 lib/puppet/string/action.rb delete mode 100644 lib/puppet/string/action_builder.rb delete mode 100644 lib/puppet/string/action_manager.rb delete mode 100644 lib/puppet/string/catalog.rb delete mode 100644 lib/puppet/string/catalog/select.rb delete mode 100644 lib/puppet/string/certificate.rb delete mode 100644 lib/puppet/string/certificate_request.rb delete mode 100644 lib/puppet/string/certificate_revocation_list.rb delete mode 100644 lib/puppet/string/config.rb delete mode 100644 lib/puppet/string/configurer.rb delete mode 100644 lib/puppet/string/facts.rb delete mode 100644 lib/puppet/string/file.rb delete mode 100644 lib/puppet/string/indirector.rb delete mode 100644 lib/puppet/string/key.rb delete mode 100644 lib/puppet/string/node.rb delete mode 100644 lib/puppet/string/option.rb delete mode 100644 lib/puppet/string/option_builder.rb delete mode 100644 lib/puppet/string/option_manager.rb delete mode 100644 lib/puppet/string/report.rb delete mode 100644 lib/puppet/string/resource.rb delete mode 100644 lib/puppet/string/resource_type.rb delete mode 100644 lib/puppet/string/status.rb delete mode 100644 lib/puppet/string/string_collection.rb (limited to 'lib/puppet') diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb new file mode 100644 index 000000000..0a6a798ce --- /dev/null +++ b/lib/puppet/application/faces.rb @@ -0,0 +1,95 @@ +require 'puppet/application' +require 'puppet/string' + +class Puppet::Application::String < Puppet::Application + + should_parse_config + run_mode :agent + + option("--debug", "-d") do |arg| + Puppet::Util::Log.level = :debug + end + + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end + + def list(*arguments) + if arguments.empty? + arguments = %w{terminuses actions} + end + strings.each do |name| + str = "#{name}:\n" + if arguments.include?("terminuses") + begin + terms = terminus_classes(name.to_sym) + str << "\tTerminuses: #{terms.join(", ")}\n" + rescue => detail + puts detail.backtrace if Puppet[:trace] + $stderr.puts "Could not load terminuses for #{name}: #{detail}" + end + end + + if arguments.include?("actions") + begin + actions = actions(name.to_sym) + str << "\tActions: #{actions.join(", ")}\n" + rescue => detail + puts detail.backtrace if Puppet[:trace] + $stderr.puts "Could not load actions for #{name}: #{detail}" + end + end + + print str + end + end + + attr_accessor :verb, :name, :arguments + + def main + # Call the method associated with the provided action (e.g., 'find'). + send(verb, *arguments) + end + + def setup + Puppet::Util::Log.newdestination :console + + load_applications # Call this to load all of the apps + + @verb, @arguments = command_line.args + @arguments ||= [] + + validate + end + + def validate + unless verb + raise "You must specify 'find', 'search', 'save', or 'destroy' as a verb; 'save' probably does not work right now" + end + + unless respond_to?(verb) + raise "Command '#{verb}' not found for 'string'" + end + end + + def strings + Puppet::String.strings + end + + def terminus_classes(indirection) + Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort + end + + def actions(indirection) + return [] unless string = Puppet::String[indirection, '0.0.1'] + string.load_actions + return string.actions.sort { |a, b| a.to_s <=> b.to_s } + end + + def load_applications + command_line.available_subcommands.each do |app| + command_line.require_application app + end + end +end + diff --git a/lib/puppet/application/faces_base.rb b/lib/puppet/application/faces_base.rb new file mode 100644 index 000000000..09d02c079 --- /dev/null +++ b/lib/puppet/application/faces_base.rb @@ -0,0 +1,150 @@ +require 'puppet/application' +require 'puppet/string' + +class Puppet::Application::StringBase < Puppet::Application + should_parse_config + run_mode :agent + + option("--debug", "-d") do |arg| + Puppet::Util::Log.level = :debug + end + + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end + + option("--format FORMAT") do |arg| + @format = arg.to_sym + end + + option("--mode RUNMODE", "-r") do |arg| + raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) + self.class.run_mode(arg.to_sym) + set_run_mode self.class.run_mode + end + + + attr_accessor :string, :action, :type, :arguments, :format + 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 + end + + # Override this if you need custom rendering. + def render(result) + render_method = Puppet::Network::FormatHandler.format(format).render_method + if render_method == "to_pson" + jj result + exit(0) + else + result.send(render_method) + end + end + + def preinit + super + trap(:INT) do + $stderr.puts "Cancelling String" + exit(0) + end + + # 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. + + # TODO: These should be configurable versions, through a global + # '--version' option, but we don't implement that yet... --daniel 2011-03-29 + @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym + @string = Puppet::String[@type, :current] + @format = @string.default_format + + # 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 = nil + index = -1 + until @action or (index += 1) >= command_line.args.length do + item = command_line.args[index] + if item =~ /^-/ then + option = @string.options.find do |name| + item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ + end + if option then + option = @string.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('=') then + index += 1 unless + (option.optional_argument? and command_line.args[index + 1] =~ /^-/) + end + elsif option = find_global_settings_argument(item) then + unless Puppet.settings.boolean? option.name then + # As far as I can tell, we treat non-bool options as always having + # a mandatory argument. --daniel 2011-04-05 + index += 1 # ...so skip the argument. + end + else + raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}" + end + else + action = @string.get_action(item.to_sym) + if action.nil? then + raise ArgumentError, "#{@string} does not have an #{item.inspect} action!" + end + @action = action + end + end + + @action or raise ArgumentError, "No action given on the command line!" + + # Finally, 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 |option| + option = @action.get_option(option) # make it the object. + self.class.option(*option.optparse) # ...and make the CLI parse it. + end + 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 + return nil # nothing 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 string action. --daniel 2011-04-04 + @arguments.delete_at(0) + + # 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 + end + + + def main + # Call the method associated with the provided action (e.g., 'find'). + if result = @string.send(@action.name, *arguments) + puts render(result) + end + exit(exit_code) + end +end diff --git a/lib/puppet/application/string.rb b/lib/puppet/application/string.rb deleted file mode 100644 index 0a6a798ce..000000000 --- a/lib/puppet/application/string.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'puppet/application' -require 'puppet/string' - -class Puppet::Application::String < Puppet::Application - - should_parse_config - run_mode :agent - - option("--debug", "-d") do |arg| - Puppet::Util::Log.level = :debug - end - - option("--verbose", "-v") do - Puppet::Util::Log.level = :info - end - - def list(*arguments) - if arguments.empty? - arguments = %w{terminuses actions} - end - strings.each do |name| - str = "#{name}:\n" - if arguments.include?("terminuses") - begin - terms = terminus_classes(name.to_sym) - str << "\tTerminuses: #{terms.join(", ")}\n" - rescue => detail - puts detail.backtrace if Puppet[:trace] - $stderr.puts "Could not load terminuses for #{name}: #{detail}" - end - end - - if arguments.include?("actions") - begin - actions = actions(name.to_sym) - str << "\tActions: #{actions.join(", ")}\n" - rescue => detail - puts detail.backtrace if Puppet[:trace] - $stderr.puts "Could not load actions for #{name}: #{detail}" - end - end - - print str - end - end - - attr_accessor :verb, :name, :arguments - - def main - # Call the method associated with the provided action (e.g., 'find'). - send(verb, *arguments) - end - - def setup - Puppet::Util::Log.newdestination :console - - load_applications # Call this to load all of the apps - - @verb, @arguments = command_line.args - @arguments ||= [] - - validate - end - - def validate - unless verb - raise "You must specify 'find', 'search', 'save', or 'destroy' as a verb; 'save' probably does not work right now" - end - - unless respond_to?(verb) - raise "Command '#{verb}' not found for 'string'" - end - end - - def strings - Puppet::String.strings - end - - def terminus_classes(indirection) - Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort - end - - def actions(indirection) - return [] unless string = Puppet::String[indirection, '0.0.1'] - string.load_actions - return string.actions.sort { |a, b| a.to_s <=> b.to_s } - end - - def load_applications - command_line.available_subcommands.each do |app| - command_line.require_application app - end - end -end - diff --git a/lib/puppet/application/string_base.rb b/lib/puppet/application/string_base.rb deleted file mode 100644 index 09d02c079..000000000 --- a/lib/puppet/application/string_base.rb +++ /dev/null @@ -1,150 +0,0 @@ -require 'puppet/application' -require 'puppet/string' - -class Puppet::Application::StringBase < Puppet::Application - should_parse_config - run_mode :agent - - option("--debug", "-d") do |arg| - Puppet::Util::Log.level = :debug - end - - option("--verbose", "-v") do - Puppet::Util::Log.level = :info - end - - option("--format FORMAT") do |arg| - @format = arg.to_sym - end - - option("--mode RUNMODE", "-r") do |arg| - raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) - self.class.run_mode(arg.to_sym) - set_run_mode self.class.run_mode - end - - - attr_accessor :string, :action, :type, :arguments, :format - 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 - end - - # Override this if you need custom rendering. - def render(result) - render_method = Puppet::Network::FormatHandler.format(format).render_method - if render_method == "to_pson" - jj result - exit(0) - else - result.send(render_method) - end - end - - def preinit - super - trap(:INT) do - $stderr.puts "Cancelling String" - exit(0) - end - - # 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. - - # TODO: These should be configurable versions, through a global - # '--version' option, but we don't implement that yet... --daniel 2011-03-29 - @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym - @string = Puppet::String[@type, :current] - @format = @string.default_format - - # 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 = nil - index = -1 - until @action or (index += 1) >= command_line.args.length do - item = command_line.args[index] - if item =~ /^-/ then - option = @string.options.find do |name| - item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ - end - if option then - option = @string.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('=') then - index += 1 unless - (option.optional_argument? and command_line.args[index + 1] =~ /^-/) - end - elsif option = find_global_settings_argument(item) then - unless Puppet.settings.boolean? option.name then - # As far as I can tell, we treat non-bool options as always having - # a mandatory argument. --daniel 2011-04-05 - index += 1 # ...so skip the argument. - end - else - raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}" - end - else - action = @string.get_action(item.to_sym) - if action.nil? then - raise ArgumentError, "#{@string} does not have an #{item.inspect} action!" - end - @action = action - end - end - - @action or raise ArgumentError, "No action given on the command line!" - - # Finally, 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 |option| - option = @action.get_option(option) # make it the object. - self.class.option(*option.optparse) # ...and make the CLI parse it. - end - 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 - return nil # nothing 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 string action. --daniel 2011-04-04 - @arguments.delete_at(0) - - # 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 - end - - - def main - # Call the method associated with the provided action (e.g., 'find'). - if result = @string.send(@action.name, *arguments) - puts render(result) - end - exit(exit_code) - end -end diff --git a/lib/puppet/faces.rb b/lib/puppet/faces.rb new file mode 100644 index 000000000..517cf4506 --- /dev/null +++ b/lib/puppet/faces.rb @@ -0,0 +1,104 @@ +require 'puppet' +require 'puppet/util/autoload' + +class Puppet::String + require 'puppet/string/string_collection' + + require 'puppet/string/action_manager' + include Puppet::String::ActionManager + extend Puppet::String::ActionManager + + require 'puppet/string/option_manager' + include Puppet::String::OptionManager + extend Puppet::String::OptionManager + + include Puppet::Util + + class << self + # This is just so we can search for actions. We only use its + # list of directories to search. + # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb + def autoloader + @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") + end + + def strings + Puppet::String::StringCollection.strings + end + + def string?(name, version) + Puppet::String::StringCollection.string?(name, version) + end + + def register(instance) + Puppet::String::StringCollection.register(instance) + end + + def define(name, version, &block) + if string?(name, version) + string = Puppet::String::StringCollection[name, version] + else + string = self.new(name, version) + Puppet::String::StringCollection.register(string) + string.load_actions + end + + string.instance_eval(&block) if block_given? + + return string + end + + alias :[] :define + end + + attr_accessor :default_format + + def set_default_format(format) + self.default_format = format.to_sym + end + + attr_accessor :type, :verb, :version, :arguments + attr_reader :name + + def initialize(name, version, &block) + unless Puppet::String::StringCollection.validate_version(version) + raise ArgumentError, "Cannot create string #{name.inspect} with invalid version number '#{version}'!" + end + + @name = Puppet::String::StringCollection.underscorize(name) + @version = version + @default_format = :pson + + instance_eval(&block) if block_given? + end + + # Try to find actions defined in other files. + def load_actions + path = "puppet/string/#{name}" + + loaded = [] + [path, "#{name}@#{version}/#{path}"].each do |path| + Puppet::String.autoloader.search_directories.each do |dir| + fdir = ::File.join(dir, path) + next unless FileTest.directory?(fdir) + + Dir.chdir(fdir) do + Dir.glob("*.rb").each do |file| + aname = file.sub(/\.rb/, '') + if loaded.include?(aname) + Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + next + end + loaded << aname + Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + require "#{Dir.pwd}/#{aname}" + end + end + end + end + end + + def to_s + "Puppet::String[#{name.inspect}, #{version.inspect}]" + end +end diff --git a/lib/puppet/faces/action.rb b/lib/puppet/faces/action.rb new file mode 100644 index 000000000..0f5032ffb --- /dev/null +++ b/lib/puppet/faces/action.rb @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +require 'puppet/string' +require 'puppet/string/option' + +class Puppet::String::Action + attr_reader :name + + def to_s + "#{@string}##{@name}" + end + + def initialize(string, name, attrs = {}) + raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ + @string = string + @name = name.to_sym + @options = {} + attrs.each do |k, v| send("#{k}=", v) end + 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 string delegate to + # the action object for invocation and all. + # + # It turns out that we have a binding problem to solve: @string was bound to + # the parent class, not the subclass instance, and we don't pass the + # appropriate context or change the binding enough to make this work. + # + # We could hack around it, by either mandating that you pass the context in + # to invoke, or try to get the binding right, but that has probably got + # subtleties that we don't instantly think of – especially around threads. + # + # So, we are pulling this method for now, and will return it to life when we + # have the time to resolve the problem. For now, you should replace... + # + # @action = @string.get_action(name) + # @action.invoke(arg1, arg2, arg3) + # + # ...with... + # + # @action = @string.get_action(name) + # @string.send(@action.name, arg1, arg2, arg3) + # + # I understand that is somewhat cumbersome, but it functions as desired. + # --daniel 2011-03-31 + # + # PS: This code is left present, but commented, to support this chunk of + # documentation, for the benefit of the reader. + # + # def invoke(*args, &block) + # @string.send(name, *args, &block) + # end + + def when_invoked=(block) + # We need to build an instance method as a wrapper, using normal code, to + # be able to expose argument defaulting between the caller and definer in + # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. + # + # In future this also gives us a place to hook in additional behaviour + # such as calling out to the action instance to validate and coerce + # parameters, which avoids any exciting context switching and all. + # + # Hopefully we can improve this when we finally shuffle off the last of + # Ruby 1.8 support, but that looks to be a few "enterprise" release eras + # away, so we are pretty stuck with this for now. + # + # Patches to make this work more nicely with Ruby 1.9 using runtime + # version checking and all are welcome, but they can't actually help if + # the results are not totally hidden away in here. + # + # Incidentally, we though about vendoring evil-ruby and actually adjusting + # the internal C structure implementation details under the hood to make + # 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 + + internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym + file = __FILE__ + "+eval" + line = __LINE__ + 1 + wrapper = "def #{@name}(*args, &block) + args << {} unless args.last.is_a? Hash + args << block if block_given? + self.__send__(#{internal_name.inspect}, *args) + end" + + if @string.is_a?(Class) + @string.class_eval do eval wrapper, nil, file, line end + @string.define_method(internal_name, &block) + else + @string.instance_eval do eval wrapper, nil, file, line end + @string.meta_def(internal_name, &block) + end + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + elsif conflict = @string.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@string}" + end + end + + option.aliases.each do |name| + @options[name] = option + end + + option + end + + def option?(name) + @options.include? name.to_sym + end + + def options + (@options.keys + @string.options).sort + end + + def get_option(name) + @options[name.to_sym] || @string.get_option(name) + end +end diff --git a/lib/puppet/faces/action_builder.rb b/lib/puppet/faces/action_builder.rb new file mode 100644 index 000000000..e7c03273b --- /dev/null +++ b/lib/puppet/faces/action_builder.rb @@ -0,0 +1,31 @@ +require 'puppet/string' +require 'puppet/string/action' + +class Puppet::String::ActionBuilder + attr_reader :action + + def self.build(string, name, &block) + raise "Action #{name.inspect} must specify a block" unless block + new(string, name, &block).action + end + + private + def initialize(string, name, &block) + @string = string + @action = Puppet::String::Action.new(string, name) + instance_eval(&block) + end + + # Ideally the method we're defining here would be added to the action, and a + # method on the string would defer to it, but we can't get scope correct, + # so we stick with this. --daniel 2011-03-24 + def when_invoked(&block) + raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action + @action.when_invoked = block + end + + def option(*declaration, &block) + option = Puppet::String::OptionBuilder.build(@action, *declaration, &block) + @action.add_option(option) + end +end diff --git a/lib/puppet/faces/action_manager.rb b/lib/puppet/faces/action_manager.rb new file mode 100644 index 000000000..9f0aa7582 --- /dev/null +++ b/lib/puppet/faces/action_manager.rb @@ -0,0 +1,49 @@ +require 'puppet/string/action_builder' + +module Puppet::String::ActionManager + # Declare that this app can take a specific action, and provide + # the code to do so. + def action(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + action = Puppet::String::ActionBuilder.build(self, name, &block) + @actions[action.name] = action + end + + # This is the short-form of an action definition; it doesn't use the + # builder, just creates the action directly from the block. + def script(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + @actions[name] = Puppet::String::Action.new(self, name, :when_invoked => block) + end + + def actions + @actions ||= {} + result = @actions.keys + + if self.is_a?(Class) and superclass.respond_to?(:actions) + result += superclass.actions + elsif self.class.respond_to?(:actions) + result += self.class.actions + end + result.sort + end + + def get_action(name) + @actions ||= {} + result = @actions[name.to_sym] + if result.nil? + if self.is_a?(Class) and superclass.respond_to?(:get_action) + result = superclass.get_action(name) + elsif self.class.respond_to?(:get_action) + result = self.class.get_action(name) + end + end + return result + end + + def action?(name) + actions.include?(name.to_sym) + end +end diff --git a/lib/puppet/faces/catalog.rb b/lib/puppet/faces/catalog.rb new file mode 100644 index 000000000..441c7ee7d --- /dev/null +++ b/lib/puppet/faces/catalog.rb @@ -0,0 +1,40 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:catalog, '0.0.1') do + action(:apply) do + when_invoked do |catalog, options| + report = Puppet::Transaction::Report.new("apply") + report.configuration_version = catalog.version + + Puppet::Util::Log.newdestination(report) + + begin + benchmark(:notice, "Finished catalog run") do + catalog.apply(:report => report) + end + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Failed to apply catalog: #{detail}" + end + + report.finalize_report + report + end + end + + action(:download) do + when_invoked do |certname, facts, options| + Puppet::Resource::Catalog.terminus_class = :rest + facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} + catalog = nil + retrieval_duration = thinmark do + catalog = Puppet::String[:catalog, '0.0.1'].find(certname, facts_to_upload) + end + catalog = catalog.to_ral + catalog.finalize + catalog.retrieval_duration = retrieval_duration + catalog.write_class_file + catalog + end + end +end diff --git a/lib/puppet/faces/catalog/select.rb b/lib/puppet/faces/catalog/select.rb new file mode 100644 index 000000000..11670e2e7 --- /dev/null +++ b/lib/puppet/faces/catalog/select.rb @@ -0,0 +1,10 @@ +# Select and show a list of resources of a given type. +Puppet::String.define(:catalog, '0.0.1') do + action :select do + when_invoked do |host, type, options| + catalog = Puppet::Resource::Catalog.indirection.find(host) + + catalog.resources.reject { |res| res.type != type }.each { |res| puts res } + end + end +end diff --git a/lib/puppet/faces/certificate.rb b/lib/puppet/faces/certificate.rb new file mode 100644 index 000000000..e8773ae2e --- /dev/null +++ b/lib/puppet/faces/certificate.rb @@ -0,0 +1,46 @@ +require 'puppet/string/indirector' +require 'puppet/ssl/host' + +Puppet::String::Indirector.define(:certificate, '0.0.1') do + # REVISIT: This should use a pre-invoke hook to run the common code that + # needs to happen before we invoke any action; that would be much nicer than + # the "please repeat yourself" stuff found in here right now. + # + # option "--ca-location LOCATION" do + # type [:whatever, :location, :symbols] + # hook :before do |value| + # Puppet::SSL::Host.ca_location = value + # end + # end + # + # ...but should I pass the arguments as well? + # --daniel 2011-04-05 + option "--ca-location LOCATION" + + action :generate do + when_invoked do |name, options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + host = Puppet::SSL::Host.new(name) + host.generate_certificate_request + host.certificate_request.class.indirection.save(host.certificate_request) + end + end + + action :list do + when_invoked do |options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + Puppet::SSL::Host.indirection.search("*", { + :for => :certificate_request, + }).map { |h| h.inspect } + end + end + + action :sign do + when_invoked do |name, options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + host = Puppet::SSL::Host.new(name) + host.desired_state = 'signed' + Puppet::SSL::Host.indirection.save(host) + end + end +end diff --git a/lib/puppet/faces/certificate_request.rb b/lib/puppet/faces/certificate_request.rb new file mode 100644 index 000000000..218b40b98 --- /dev/null +++ b/lib/puppet/faces/certificate_request.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:certificate_request, '0.0.1') do +end diff --git a/lib/puppet/faces/certificate_revocation_list.rb b/lib/puppet/faces/certificate_revocation_list.rb new file mode 100644 index 000000000..9731b4f2d --- /dev/null +++ b/lib/puppet/faces/certificate_revocation_list.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:certificate_revocation_list, '0.0.1') do +end diff --git a/lib/puppet/faces/config.rb b/lib/puppet/faces/config.rb new file mode 100644 index 000000000..8a9417148 --- /dev/null +++ b/lib/puppet/faces/config.rb @@ -0,0 +1,12 @@ +require 'puppet/string' + +Puppet::String.define(:config, '0.0.1') do + action(:print) do + when_invoked do |*args| + options = args.pop + Puppet.settings[:configprint] = args.join(",") + Puppet.settings.print_config_options + nil + end + end +end diff --git a/lib/puppet/faces/configurer.rb b/lib/puppet/faces/configurer.rb new file mode 100644 index 000000000..257f97e90 --- /dev/null +++ b/lib/puppet/faces/configurer.rb @@ -0,0 +1,12 @@ +require 'puppet/string' + +Puppet::String.define(:configurer, '0.0.1') do + action(:synchronize) do + when_invoked do |certname, options| + facts = Puppet::String[:facts, '0.0.1'].find(certname) + catalog = Puppet::String[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::String[:catalog, '0.0.1'].apply(catalog) + report + end + end +end diff --git a/lib/puppet/faces/faces_collection.rb b/lib/puppet/faces/faces_collection.rb new file mode 100644 index 000000000..ecd99359d --- /dev/null +++ b/lib/puppet/faces/faces_collection.rb @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +require 'puppet/string' + +module Puppet::String::StringCollection + SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ + + @strings = Hash.new { |hash, key| hash[key] = {} } + + def self.strings + unless @loaded + @loaded = true + $LOAD_PATH.each do |dir| + next unless FileTest.directory?(dir) + Dir.chdir(dir) do + Dir.glob("puppet/string/v*/*.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 + end + end + return @strings.keys + end + + def self.validate_version(version) + !!(SEMVER_VERSION =~ version.to_s) + end + + def self.cmp_versions(a, b) + a, b = [a, b].map do |x| + parts = SEMVER_VERSION.match(x).to_a[1..4] + parts[0..2] = parts[0..2].map { |e| e.to_i } + parts + end + + cmp = a[0..2] <=> b[0..2] + if cmp == 0 + cmp = a[3] <=> b[3] + cmp = +1 if a[3].empty? && !b[3].empty? + cmp = -1 if b[3].empty? && !a[3].empty? + end + cmp + end + + def self.[](name, version) + @strings[underscorize(name)][version] if string?(name, version) + end + + def self.string?(name, version) + name = underscorize(name) + return true if @strings[name].has_key?(version) + + # We always load the current version file; the common case is that we have + # the expected version and any compatibility versions in the same file, + # the default. Which means that this is almost always the case. + # + # We use require to avoid executing the code multiple times, like any + # other Ruby library that we might want to use. --daniel 2011-04-06 + begin + require "puppet/string/#{name}" + + # If we wanted :current, we need to index to find that; direct version + # requests just work™ as they go. --daniel 2011-04-06 + if version == :current then + # We need to find current out of this. This is the largest version + # number that doesn't have a dedicated on-disk file present; those + # represent "experimental" versions of strings, which we don't fully + # support yet. + # + # We walk the versions from highest to lowest and take the first version + # that is not defined in an explicitly versioned file on disk as the + # current version. + # + # This constrains us to only ship experimental versions with *one* + # version in the file, not multiple, but given you can't reliably load + # them except by side-effect when you ignore that rule this seems safe + # enough... + # + # Given those constraints, and that we are not going to ship a versioned + # interface that is not :current in this release, we are going to leave + # these thoughts in place, and just punt on the actual versioning. + # + # When we upgrade the core to support multiple versions we can solve the + # problems then; as lazy as possible. + # + # We do support multiple versions in the same file, though, so we sort + # versions here and return the last item in that set. + # + # --daniel 2011-04-06 + latest_ver = @strings[name].keys.sort {|a, b| cmp_versions(a, b) }.last + @strings[name][:current] = @strings[name][latest_ver] + end + rescue LoadError => e + raise unless e.message =~ %r{-- puppet/string/#{name}$} + # ...guess we didn't find the file; return a much better problem. + end + + # Now, either we have the version in our set of strings, or we didn't find + # the version they were looking for. In the future we will support + # loading versioned stuff from some look-aside part of the Ruby load path, + # but we don't need that right now. + # + # So, this comment is a place-holder for that. --daniel 2011-04-06 + return !! @strings[name].has_key?(version) + end + + def self.register(string) + @strings[underscorize(string.name)][string.version] = string + end + + def self.underscorize(name) + unless name.to_s =~ /^[-_a-z]+$/i then + raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" + end + + name.to_s.downcase.split(/[-_]/).join('_').to_sym + end +end diff --git a/lib/puppet/faces/facts.rb b/lib/puppet/faces/facts.rb new file mode 100644 index 000000000..6bd9904b0 --- /dev/null +++ b/lib/puppet/faces/facts.rb @@ -0,0 +1,18 @@ +require 'puppet/string/indirector' +require 'puppet/node/facts' + +Puppet::String::Indirector.define(:facts, '0.0.1') do + set_default_format :yaml + + # Upload our facts to the server + action(:upload) do + when_invoked do |options| + Puppet::Node::Facts.indirection.terminus_class = :facter + facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) + Puppet::Node::Facts.indirection.terminus_class = :rest + Puppet::Node::Facts.indirection.save(facts) + Puppet.notice "Uploaded facts for '#{Puppet[:certname]}'" + nil + end + end +end diff --git a/lib/puppet/faces/file.rb b/lib/puppet/faces/file.rb new file mode 100644 index 000000000..cc5737f28 --- /dev/null +++ b/lib/puppet/faces/file.rb @@ -0,0 +1,5 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:file, '0.0.1') do + set_indirection_name :file_bucket_file +end diff --git a/lib/puppet/faces/indirector.rb b/lib/puppet/faces/indirector.rb new file mode 100644 index 000000000..0c7d043bb --- /dev/null +++ b/lib/puppet/faces/indirector.rb @@ -0,0 +1,94 @@ +require 'puppet' +require 'puppet/string' + +class Puppet::String::Indirector < Puppet::String + option "--terminus TERMINUS" do + desc "REVISIT: You can select a terminus, which has some bigger effect +that we should describe in this file somehow." + end + + def self.indirections + Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort + end + + def self.terminus_classes(indirection) + Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort + end + + def call_indirection_method(method, *args) + options = args.pop + options.has_key?(:terminus) and set_terminus(options[:terminus]) + + begin + result = indirection.__send__(method, *args) + rescue => detail + puts detail.backtrace if Puppet[:trace] + raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" + end + + indirection.reset_terminus_class + return result + end + + action :destroy do + when_invoked { |*args| call_indirection_method(:destroy, *args) } + end + + action :find do + when_invoked { |*args| call_indirection_method(:find, *args) } + end + + action :save do + when_invoked { |*args| call_indirection_method(:save, *args) } + end + + action :search do + when_invoked { |*args| call_indirection_method(:search, *args) } + end + + # Print the configuration for the current terminus class + action :info do + when_invoked do |*args| + options = args.pop + options.has_key?(:terminus) and set_terminus(options[:terminus]) + + if t = indirection.terminus_class + puts "Run mode '#{Puppet.run_mode.name}': #{t}" + else + $stderr.puts "No default terminus class for run mode '#{Puppet.run_mode.name}'" + end + + indirection.reset_terminus_class + end + end + + attr_accessor :from + + def indirection_name + @indirection_name || name.to_sym + end + + # Here's your opportunity to override the indirection name. By default + # it will be the same name as the string. + def set_indirection_name(name) + @indirection_name = name + end + + # Return an indirection associated with an string, if one exists + # One usually does. + def indirection + unless @indirection + @indirection = Puppet::Indirector::Indirection.instance(indirection_name) + @indirection or raise "Could not find terminus for #{indirection_name}" + end + @indirection + end + + def set_terminus(from) + begin + indirection.terminus_class = from + rescue => detail + raise "Could not set '#{indirection.name}' terminus to '#{from}' (#{detail}); valid terminus types are #{self.class.terminus_classes(indirection.name).join(", ") }" + end + end +end diff --git a/lib/puppet/faces/key.rb b/lib/puppet/faces/key.rb new file mode 100644 index 000000000..95aceade5 --- /dev/null +++ b/lib/puppet/faces/key.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:key, '0.0.1') do +end diff --git a/lib/puppet/faces/node.rb b/lib/puppet/faces/node.rb new file mode 100644 index 000000000..bc31a2cf3 --- /dev/null +++ b/lib/puppet/faces/node.rb @@ -0,0 +1,5 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:node, '0.0.1') do + set_default_format :yaml +end diff --git a/lib/puppet/faces/option.rb b/lib/puppet/faces/option.rb new file mode 100644 index 000000000..352f7e5ef --- /dev/null +++ b/lib/puppet/faces/option.rb @@ -0,0 +1,82 @@ +require 'puppet/string' + +class Puppet::String::Option + attr_reader :parent + attr_reader :name + attr_reader :aliases + attr_reader :optparse + attr_accessor :desc + + def takes_argument? + !!@argument + end + def optional_argument? + !!@optional_argument + end + + def initialize(parent, *declaration, &block) + @parent = parent + @optparse = [] + + # Collect and sort the arguments in the declaration. + dups = {} + declaration.each do |item| + if item.is_a? String and item.to_s =~ /^-/ then + unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then + raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" + end + @optparse << item + + # Duplicate checking... + name = optparse_to_name(item) + if dup = dups[name] then + raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" + else + dups[name] = item + end + else + raise ArgumentError, "#{item.inspect} is not valid for an option argument" + end + end + + if @optparse.empty? then + raise ArgumentError, "No option declarations found while building" + end + + # Now, infer the name from the options; we prefer the first long option as + # the name, rather than just the first option. + @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) + @aliases = @optparse.map { |o| optparse_to_name(o) } + + # Do we take an argument? If so, are we consistent about it, because + # incoherence here makes our life super-difficult, and we can more easily + # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 + @argument = @optparse.any? { |o| o =~ /[ =]/ } + if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then + raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" + end + + # Is our argument optional? The rules about consistency apply here, also, + # just like they do to taking arguments at all. --daniel 2011-03-30 + @optional_argument = @optparse.any? { |o| o.include? "[" } + if @optional_argument and not @optparse.all? { |o| o.include? "[" } then + raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" + end + end + + # to_s and optparse_to_name are roughly mirrored, because they are used to + # transform strings to name symbols, and vice-versa. This isn't a full + # bidirectional transformation though. + def to_s + @name.to_s.tr('_', '-') + end + + def optparse_to_name(declaration) + unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then + raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" + end + name = found.captures.first.tr('-', '_') + raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ + name.to_sym + end +end diff --git a/lib/puppet/faces/option_builder.rb b/lib/puppet/faces/option_builder.rb new file mode 100644 index 000000000..da0d213fb --- /dev/null +++ b/lib/puppet/faces/option_builder.rb @@ -0,0 +1,25 @@ +require 'puppet/string/option' + +class Puppet::String::OptionBuilder + attr_reader :option + + def self.build(string, *declaration, &block) + new(string, *declaration, &block).option + end + + private + def initialize(string, *declaration, &block) + @string = string + @option = Puppet::String::Option.new(string, *declaration) + block and instance_eval(&block) + @option + end + + # Metaprogram the simple DSL from the option class. + Puppet::String::Option.instance_methods.grep(/=$/).each do |setter| + next if setter =~ /^=/ # special case, darn it... + + dsl = setter.sub(/=$/, '') + define_method(dsl) do |value| @option.send(setter, value) end + end +end diff --git a/lib/puppet/faces/option_manager.rb b/lib/puppet/faces/option_manager.rb new file mode 100644 index 000000000..f952ad4f0 --- /dev/null +++ b/lib/puppet/faces/option_manager.rb @@ -0,0 +1,56 @@ +require 'puppet/string/option_builder' + +module Puppet::String::OptionManager + # Declare that this app can take a specific option, and provide + # the code to do so. + def option(*declaration, &block) + add_option Puppet::String::OptionBuilder.build(self, *declaration, &block) + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + end + + actions.each do |action| + action = get_action(action) + if conflict = action.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" + end + end + end + + option.aliases.each { |name| @options[name] = option } + option + end + + def options + @options ||= {} + result = @options.keys + + if self.is_a?(Class) and superclass.respond_to?(:options) + result += superclass.options + elsif self.class.respond_to?(:options) + result += self.class.options + end + result.sort + end + + def get_option(name) + @options ||= {} + result = @options[name.to_sym] + unless result then + if self.is_a?(Class) and superclass.respond_to?(:get_option) + result = superclass.get_option(name) + elsif self.class.respond_to?(:get_option) + result = self.class.get_option(name) + end + end + return result + end + + def option?(name) + options.include? name.to_sym + end +end diff --git a/lib/puppet/faces/report.rb b/lib/puppet/faces/report.rb new file mode 100644 index 000000000..da3ca8504 --- /dev/null +++ b/lib/puppet/faces/report.rb @@ -0,0 +1,15 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:report, '0.0.1') do + action(:submit) do + when_invoked do |report, options| + begin + Puppet::Transaction::Report.terminus_class = :rest + report.save + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not send report: #{detail}" + end + end + end +end diff --git a/lib/puppet/faces/resource.rb b/lib/puppet/faces/resource.rb new file mode 100644 index 000000000..9838be0fa --- /dev/null +++ b/lib/puppet/faces/resource.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:resource, '0.0.1') do +end diff --git a/lib/puppet/faces/resource_type.rb b/lib/puppet/faces/resource_type.rb new file mode 100644 index 000000000..8ca31ea6c --- /dev/null +++ b/lib/puppet/faces/resource_type.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:resource_type, '0.0.1') do +end diff --git a/lib/puppet/faces/status.rb b/lib/puppet/faces/status.rb new file mode 100644 index 000000000..41de2bb99 --- /dev/null +++ b/lib/puppet/faces/status.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:status, '0.0.1') do +end diff --git a/lib/puppet/string.rb b/lib/puppet/string.rb deleted file mode 100644 index 517cf4506..000000000 --- a/lib/puppet/string.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'puppet' -require 'puppet/util/autoload' - -class Puppet::String - require 'puppet/string/string_collection' - - require 'puppet/string/action_manager' - include Puppet::String::ActionManager - extend Puppet::String::ActionManager - - require 'puppet/string/option_manager' - include Puppet::String::OptionManager - extend Puppet::String::OptionManager - - include Puppet::Util - - class << self - # This is just so we can search for actions. We only use its - # list of directories to search. - # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb - def autoloader - @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") - end - - def strings - Puppet::String::StringCollection.strings - end - - def string?(name, version) - Puppet::String::StringCollection.string?(name, version) - end - - def register(instance) - Puppet::String::StringCollection.register(instance) - end - - def define(name, version, &block) - if string?(name, version) - string = Puppet::String::StringCollection[name, version] - else - string = self.new(name, version) - Puppet::String::StringCollection.register(string) - string.load_actions - end - - string.instance_eval(&block) if block_given? - - return string - end - - alias :[] :define - end - - attr_accessor :default_format - - def set_default_format(format) - self.default_format = format.to_sym - end - - attr_accessor :type, :verb, :version, :arguments - attr_reader :name - - def initialize(name, version, &block) - unless Puppet::String::StringCollection.validate_version(version) - raise ArgumentError, "Cannot create string #{name.inspect} with invalid version number '#{version}'!" - end - - @name = Puppet::String::StringCollection.underscorize(name) - @version = version - @default_format = :pson - - instance_eval(&block) if block_given? - end - - # Try to find actions defined in other files. - def load_actions - path = "puppet/string/#{name}" - - loaded = [] - [path, "#{name}@#{version}/#{path}"].each do |path| - Puppet::String.autoloader.search_directories.each do |dir| - fdir = ::File.join(dir, path) - next unless FileTest.directory?(fdir) - - Dir.chdir(fdir) do - Dir.glob("*.rb").each do |file| - aname = file.sub(/\.rb/, '') - if loaded.include?(aname) - Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - next - end - loaded << aname - Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - require "#{Dir.pwd}/#{aname}" - end - end - end - end - end - - def to_s - "Puppet::String[#{name.inspect}, #{version.inspect}]" - end -end diff --git a/lib/puppet/string/action.rb b/lib/puppet/string/action.rb deleted file mode 100644 index 0f5032ffb..000000000 --- a/lib/puppet/string/action.rb +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/string' -require 'puppet/string/option' - -class Puppet::String::Action - attr_reader :name - - def to_s - "#{@string}##{@name}" - end - - def initialize(string, name, attrs = {}) - raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ - @string = string - @name = name.to_sym - @options = {} - attrs.each do |k, v| send("#{k}=", v) end - 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 string delegate to - # the action object for invocation and all. - # - # It turns out that we have a binding problem to solve: @string was bound to - # the parent class, not the subclass instance, and we don't pass the - # appropriate context or change the binding enough to make this work. - # - # We could hack around it, by either mandating that you pass the context in - # to invoke, or try to get the binding right, but that has probably got - # subtleties that we don't instantly think of – especially around threads. - # - # So, we are pulling this method for now, and will return it to life when we - # have the time to resolve the problem. For now, you should replace... - # - # @action = @string.get_action(name) - # @action.invoke(arg1, arg2, arg3) - # - # ...with... - # - # @action = @string.get_action(name) - # @string.send(@action.name, arg1, arg2, arg3) - # - # I understand that is somewhat cumbersome, but it functions as desired. - # --daniel 2011-03-31 - # - # PS: This code is left present, but commented, to support this chunk of - # documentation, for the benefit of the reader. - # - # def invoke(*args, &block) - # @string.send(name, *args, &block) - # end - - def when_invoked=(block) - # We need to build an instance method as a wrapper, using normal code, to - # be able to expose argument defaulting between the caller and definer in - # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. - # - # In future this also gives us a place to hook in additional behaviour - # such as calling out to the action instance to validate and coerce - # parameters, which avoids any exciting context switching and all. - # - # Hopefully we can improve this when we finally shuffle off the last of - # Ruby 1.8 support, but that looks to be a few "enterprise" release eras - # away, so we are pretty stuck with this for now. - # - # Patches to make this work more nicely with Ruby 1.9 using runtime - # version checking and all are welcome, but they can't actually help if - # the results are not totally hidden away in here. - # - # Incidentally, we though about vendoring evil-ruby and actually adjusting - # the internal C structure implementation details under the hood to make - # 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 - - internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym - file = __FILE__ + "+eval" - line = __LINE__ + 1 - wrapper = "def #{@name}(*args, &block) - args << {} unless args.last.is_a? Hash - args << block if block_given? - self.__send__(#{internal_name.inspect}, *args) - end" - - if @string.is_a?(Class) - @string.class_eval do eval wrapper, nil, file, line end - @string.define_method(internal_name, &block) - else - @string.instance_eval do eval wrapper, nil, file, line end - @string.meta_def(internal_name, &block) - end - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - elsif conflict = @string.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@string}" - end - end - - option.aliases.each do |name| - @options[name] = option - end - - option - end - - def option?(name) - @options.include? name.to_sym - end - - def options - (@options.keys + @string.options).sort - end - - def get_option(name) - @options[name.to_sym] || @string.get_option(name) - end -end diff --git a/lib/puppet/string/action_builder.rb b/lib/puppet/string/action_builder.rb deleted file mode 100644 index e7c03273b..000000000 --- a/lib/puppet/string/action_builder.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'puppet/string' -require 'puppet/string/action' - -class Puppet::String::ActionBuilder - attr_reader :action - - def self.build(string, name, &block) - raise "Action #{name.inspect} must specify a block" unless block - new(string, name, &block).action - end - - private - def initialize(string, name, &block) - @string = string - @action = Puppet::String::Action.new(string, name) - instance_eval(&block) - end - - # Ideally the method we're defining here would be added to the action, and a - # method on the string would defer to it, but we can't get scope correct, - # so we stick with this. --daniel 2011-03-24 - def when_invoked(&block) - raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action - @action.when_invoked = block - end - - def option(*declaration, &block) - option = Puppet::String::OptionBuilder.build(@action, *declaration, &block) - @action.add_option(option) - end -end diff --git a/lib/puppet/string/action_manager.rb b/lib/puppet/string/action_manager.rb deleted file mode 100644 index 9f0aa7582..000000000 --- a/lib/puppet/string/action_manager.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'puppet/string/action_builder' - -module Puppet::String::ActionManager - # Declare that this app can take a specific action, and provide - # the code to do so. - def action(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - action = Puppet::String::ActionBuilder.build(self, name, &block) - @actions[action.name] = action - end - - # This is the short-form of an action definition; it doesn't use the - # builder, just creates the action directly from the block. - def script(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - @actions[name] = Puppet::String::Action.new(self, name, :when_invoked => block) - end - - def actions - @actions ||= {} - result = @actions.keys - - if self.is_a?(Class) and superclass.respond_to?(:actions) - result += superclass.actions - elsif self.class.respond_to?(:actions) - result += self.class.actions - end - result.sort - end - - def get_action(name) - @actions ||= {} - result = @actions[name.to_sym] - if result.nil? - if self.is_a?(Class) and superclass.respond_to?(:get_action) - result = superclass.get_action(name) - elsif self.class.respond_to?(:get_action) - result = self.class.get_action(name) - end - end - return result - end - - def action?(name) - actions.include?(name.to_sym) - end -end diff --git a/lib/puppet/string/catalog.rb b/lib/puppet/string/catalog.rb deleted file mode 100644 index 441c7ee7d..000000000 --- a/lib/puppet/string/catalog.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:catalog, '0.0.1') do - action(:apply) do - when_invoked do |catalog, options| - report = Puppet::Transaction::Report.new("apply") - report.configuration_version = catalog.version - - Puppet::Util::Log.newdestination(report) - - begin - benchmark(:notice, "Finished catalog run") do - catalog.apply(:report => report) - end - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Failed to apply catalog: #{detail}" - end - - report.finalize_report - report - end - end - - action(:download) do - when_invoked do |certname, facts, options| - Puppet::Resource::Catalog.terminus_class = :rest - facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} - catalog = nil - retrieval_duration = thinmark do - catalog = Puppet::String[:catalog, '0.0.1'].find(certname, facts_to_upload) - end - catalog = catalog.to_ral - catalog.finalize - catalog.retrieval_duration = retrieval_duration - catalog.write_class_file - catalog - end - end -end diff --git a/lib/puppet/string/catalog/select.rb b/lib/puppet/string/catalog/select.rb deleted file mode 100644 index 11670e2e7..000000000 --- a/lib/puppet/string/catalog/select.rb +++ /dev/null @@ -1,10 +0,0 @@ -# Select and show a list of resources of a given type. -Puppet::String.define(:catalog, '0.0.1') do - action :select do - when_invoked do |host, type, options| - catalog = Puppet::Resource::Catalog.indirection.find(host) - - catalog.resources.reject { |res| res.type != type }.each { |res| puts res } - end - end -end diff --git a/lib/puppet/string/certificate.rb b/lib/puppet/string/certificate.rb deleted file mode 100644 index e8773ae2e..000000000 --- a/lib/puppet/string/certificate.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'puppet/string/indirector' -require 'puppet/ssl/host' - -Puppet::String::Indirector.define(:certificate, '0.0.1') do - # REVISIT: This should use a pre-invoke hook to run the common code that - # needs to happen before we invoke any action; that would be much nicer than - # the "please repeat yourself" stuff found in here right now. - # - # option "--ca-location LOCATION" do - # type [:whatever, :location, :symbols] - # hook :before do |value| - # Puppet::SSL::Host.ca_location = value - # end - # end - # - # ...but should I pass the arguments as well? - # --daniel 2011-04-05 - option "--ca-location LOCATION" - - action :generate do - when_invoked do |name, options| - Puppet::SSL::Host.ca_location = options[:ca_location].to_sym - host = Puppet::SSL::Host.new(name) - host.generate_certificate_request - host.certificate_request.class.indirection.save(host.certificate_request) - end - end - - action :list do - when_invoked do |options| - Puppet::SSL::Host.ca_location = options[:ca_location].to_sym - Puppet::SSL::Host.indirection.search("*", { - :for => :certificate_request, - }).map { |h| h.inspect } - end - end - - action :sign do - when_invoked do |name, options| - Puppet::SSL::Host.ca_location = options[:ca_location].to_sym - host = Puppet::SSL::Host.new(name) - host.desired_state = 'signed' - Puppet::SSL::Host.indirection.save(host) - end - end -end diff --git a/lib/puppet/string/certificate_request.rb b/lib/puppet/string/certificate_request.rb deleted file mode 100644 index 218b40b98..000000000 --- a/lib/puppet/string/certificate_request.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:certificate_request, '0.0.1') do -end diff --git a/lib/puppet/string/certificate_revocation_list.rb b/lib/puppet/string/certificate_revocation_list.rb deleted file mode 100644 index 9731b4f2d..000000000 --- a/lib/puppet/string/certificate_revocation_list.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:certificate_revocation_list, '0.0.1') do -end diff --git a/lib/puppet/string/config.rb b/lib/puppet/string/config.rb deleted file mode 100644 index 8a9417148..000000000 --- a/lib/puppet/string/config.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'puppet/string' - -Puppet::String.define(:config, '0.0.1') do - action(:print) do - when_invoked do |*args| - options = args.pop - Puppet.settings[:configprint] = args.join(",") - Puppet.settings.print_config_options - nil - end - end -end diff --git a/lib/puppet/string/configurer.rb b/lib/puppet/string/configurer.rb deleted file mode 100644 index 257f97e90..000000000 --- a/lib/puppet/string/configurer.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'puppet/string' - -Puppet::String.define(:configurer, '0.0.1') do - action(:synchronize) do - when_invoked do |certname, options| - facts = Puppet::String[:facts, '0.0.1'].find(certname) - catalog = Puppet::String[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::String[:catalog, '0.0.1'].apply(catalog) - report - end - end -end diff --git a/lib/puppet/string/facts.rb b/lib/puppet/string/facts.rb deleted file mode 100644 index 6bd9904b0..000000000 --- a/lib/puppet/string/facts.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'puppet/string/indirector' -require 'puppet/node/facts' - -Puppet::String::Indirector.define(:facts, '0.0.1') do - set_default_format :yaml - - # Upload our facts to the server - action(:upload) do - when_invoked do |options| - Puppet::Node::Facts.indirection.terminus_class = :facter - facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) - Puppet::Node::Facts.indirection.terminus_class = :rest - Puppet::Node::Facts.indirection.save(facts) - Puppet.notice "Uploaded facts for '#{Puppet[:certname]}'" - nil - end - end -end diff --git a/lib/puppet/string/file.rb b/lib/puppet/string/file.rb deleted file mode 100644 index cc5737f28..000000000 --- a/lib/puppet/string/file.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:file, '0.0.1') do - set_indirection_name :file_bucket_file -end diff --git a/lib/puppet/string/indirector.rb b/lib/puppet/string/indirector.rb deleted file mode 100644 index 0c7d043bb..000000000 --- a/lib/puppet/string/indirector.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'puppet' -require 'puppet/string' - -class Puppet::String::Indirector < Puppet::String - option "--terminus TERMINUS" do - desc "REVISIT: You can select a terminus, which has some bigger effect -that we should describe in this file somehow." - end - - def self.indirections - Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort - end - - def self.terminus_classes(indirection) - Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort - end - - def call_indirection_method(method, *args) - options = args.pop - options.has_key?(:terminus) and set_terminus(options[:terminus]) - - begin - result = indirection.__send__(method, *args) - rescue => detail - puts detail.backtrace if Puppet[:trace] - raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" - end - - indirection.reset_terminus_class - return result - end - - action :destroy do - when_invoked { |*args| call_indirection_method(:destroy, *args) } - end - - action :find do - when_invoked { |*args| call_indirection_method(:find, *args) } - end - - action :save do - when_invoked { |*args| call_indirection_method(:save, *args) } - end - - action :search do - when_invoked { |*args| call_indirection_method(:search, *args) } - end - - # Print the configuration for the current terminus class - action :info do - when_invoked do |*args| - options = args.pop - options.has_key?(:terminus) and set_terminus(options[:terminus]) - - if t = indirection.terminus_class - puts "Run mode '#{Puppet.run_mode.name}': #{t}" - else - $stderr.puts "No default terminus class for run mode '#{Puppet.run_mode.name}'" - end - - indirection.reset_terminus_class - end - end - - attr_accessor :from - - def indirection_name - @indirection_name || name.to_sym - end - - # Here's your opportunity to override the indirection name. By default - # it will be the same name as the string. - def set_indirection_name(name) - @indirection_name = name - end - - # Return an indirection associated with an string, if one exists - # One usually does. - def indirection - unless @indirection - @indirection = Puppet::Indirector::Indirection.instance(indirection_name) - @indirection or raise "Could not find terminus for #{indirection_name}" - end - @indirection - end - - def set_terminus(from) - begin - indirection.terminus_class = from - rescue => detail - raise "Could not set '#{indirection.name}' terminus to '#{from}' (#{detail}); valid terminus types are #{self.class.terminus_classes(indirection.name).join(", ") }" - end - end -end diff --git a/lib/puppet/string/key.rb b/lib/puppet/string/key.rb deleted file mode 100644 index 95aceade5..000000000 --- a/lib/puppet/string/key.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:key, '0.0.1') do -end diff --git a/lib/puppet/string/node.rb b/lib/puppet/string/node.rb deleted file mode 100644 index bc31a2cf3..000000000 --- a/lib/puppet/string/node.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:node, '0.0.1') do - set_default_format :yaml -end diff --git a/lib/puppet/string/option.rb b/lib/puppet/string/option.rb deleted file mode 100644 index 352f7e5ef..000000000 --- a/lib/puppet/string/option.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'puppet/string' - -class Puppet::String::Option - attr_reader :parent - attr_reader :name - attr_reader :aliases - attr_reader :optparse - attr_accessor :desc - - def takes_argument? - !!@argument - end - def optional_argument? - !!@optional_argument - end - - def initialize(parent, *declaration, &block) - @parent = parent - @optparse = [] - - # Collect and sort the arguments in the declaration. - dups = {} - declaration.each do |item| - if item.is_a? String and item.to_s =~ /^-/ then - unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then - raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" - end - @optparse << item - - # Duplicate checking... - name = optparse_to_name(item) - if dup = dups[name] then - raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" - else - dups[name] = item - end - else - raise ArgumentError, "#{item.inspect} is not valid for an option argument" - end - end - - if @optparse.empty? then - raise ArgumentError, "No option declarations found while building" - end - - # Now, infer the name from the options; we prefer the first long option as - # the name, rather than just the first option. - @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) - @aliases = @optparse.map { |o| optparse_to_name(o) } - - # Do we take an argument? If so, are we consistent about it, because - # incoherence here makes our life super-difficult, and we can more easily - # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 - @argument = @optparse.any? { |o| o =~ /[ =]/ } - if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then - raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" - end - - # Is our argument optional? The rules about consistency apply here, also, - # just like they do to taking arguments at all. --daniel 2011-03-30 - @optional_argument = @optparse.any? { |o| o.include? "[" } - if @optional_argument and not @optparse.all? { |o| o.include? "[" } then - raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" - end - end - - # to_s and optparse_to_name are roughly mirrored, because they are used to - # transform strings to name symbols, and vice-versa. This isn't a full - # bidirectional transformation though. - def to_s - @name.to_s.tr('_', '-') - end - - def optparse_to_name(declaration) - unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then - raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" - end - name = found.captures.first.tr('-', '_') - raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ - name.to_sym - end -end diff --git a/lib/puppet/string/option_builder.rb b/lib/puppet/string/option_builder.rb deleted file mode 100644 index da0d213fb..000000000 --- a/lib/puppet/string/option_builder.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'puppet/string/option' - -class Puppet::String::OptionBuilder - attr_reader :option - - def self.build(string, *declaration, &block) - new(string, *declaration, &block).option - end - - private - def initialize(string, *declaration, &block) - @string = string - @option = Puppet::String::Option.new(string, *declaration) - block and instance_eval(&block) - @option - end - - # Metaprogram the simple DSL from the option class. - Puppet::String::Option.instance_methods.grep(/=$/).each do |setter| - next if setter =~ /^=/ # special case, darn it... - - dsl = setter.sub(/=$/, '') - define_method(dsl) do |value| @option.send(setter, value) end - end -end diff --git a/lib/puppet/string/option_manager.rb b/lib/puppet/string/option_manager.rb deleted file mode 100644 index f952ad4f0..000000000 --- a/lib/puppet/string/option_manager.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'puppet/string/option_builder' - -module Puppet::String::OptionManager - # Declare that this app can take a specific option, and provide - # the code to do so. - def option(*declaration, &block) - add_option Puppet::String::OptionBuilder.build(self, *declaration, &block) - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - end - - actions.each do |action| - action = get_action(action) - if conflict = action.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" - end - end - end - - option.aliases.each { |name| @options[name] = option } - option - end - - def options - @options ||= {} - result = @options.keys - - if self.is_a?(Class) and superclass.respond_to?(:options) - result += superclass.options - elsif self.class.respond_to?(:options) - result += self.class.options - end - result.sort - end - - def get_option(name) - @options ||= {} - result = @options[name.to_sym] - unless result then - if self.is_a?(Class) and superclass.respond_to?(:get_option) - result = superclass.get_option(name) - elsif self.class.respond_to?(:get_option) - result = self.class.get_option(name) - end - end - return result - end - - def option?(name) - options.include? name.to_sym - end -end diff --git a/lib/puppet/string/report.rb b/lib/puppet/string/report.rb deleted file mode 100644 index da3ca8504..000000000 --- a/lib/puppet/string/report.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:report, '0.0.1') do - action(:submit) do - when_invoked do |report, options| - begin - Puppet::Transaction::Report.terminus_class = :rest - report.save - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not send report: #{detail}" - end - end - end -end diff --git a/lib/puppet/string/resource.rb b/lib/puppet/string/resource.rb deleted file mode 100644 index 9838be0fa..000000000 --- a/lib/puppet/string/resource.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:resource, '0.0.1') do -end diff --git a/lib/puppet/string/resource_type.rb b/lib/puppet/string/resource_type.rb deleted file mode 100644 index 8ca31ea6c..000000000 --- a/lib/puppet/string/resource_type.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:resource_type, '0.0.1') do -end diff --git a/lib/puppet/string/status.rb b/lib/puppet/string/status.rb deleted file mode 100644 index 41de2bb99..000000000 --- a/lib/puppet/string/status.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:status, '0.0.1') do -end diff --git a/lib/puppet/string/string_collection.rb b/lib/puppet/string/string_collection.rb deleted file mode 100644 index ecd99359d..000000000 --- a/lib/puppet/string/string_collection.rb +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/string' - -module Puppet::String::StringCollection - SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ - - @strings = Hash.new { |hash, key| hash[key] = {} } - - def self.strings - unless @loaded - @loaded = true - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - Dir.glob("puppet/string/v*/*.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 - end - end - return @strings.keys - end - - def self.validate_version(version) - !!(SEMVER_VERSION =~ version.to_s) - end - - def self.cmp_versions(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - - def self.[](name, version) - @strings[underscorize(name)][version] if string?(name, version) - end - - def self.string?(name, version) - name = underscorize(name) - return true if @strings[name].has_key?(version) - - # We always load the current version file; the common case is that we have - # the expected version and any compatibility versions in the same file, - # the default. Which means that this is almost always the case. - # - # We use require to avoid executing the code multiple times, like any - # other Ruby library that we might want to use. --daniel 2011-04-06 - begin - require "puppet/string/#{name}" - - # If we wanted :current, we need to index to find that; direct version - # requests just work™ as they go. --daniel 2011-04-06 - if version == :current then - # We need to find current out of this. This is the largest version - # number that doesn't have a dedicated on-disk file present; those - # represent "experimental" versions of strings, which we don't fully - # support yet. - # - # We walk the versions from highest to lowest and take the first version - # that is not defined in an explicitly versioned file on disk as the - # current version. - # - # This constrains us to only ship experimental versions with *one* - # version in the file, not multiple, but given you can't reliably load - # them except by side-effect when you ignore that rule this seems safe - # enough... - # - # Given those constraints, and that we are not going to ship a versioned - # interface that is not :current in this release, we are going to leave - # these thoughts in place, and just punt on the actual versioning. - # - # When we upgrade the core to support multiple versions we can solve the - # problems then; as lazy as possible. - # - # We do support multiple versions in the same file, though, so we sort - # versions here and return the last item in that set. - # - # --daniel 2011-04-06 - latest_ver = @strings[name].keys.sort {|a, b| cmp_versions(a, b) }.last - @strings[name][:current] = @strings[name][latest_ver] - end - rescue LoadError => e - raise unless e.message =~ %r{-- puppet/string/#{name}$} - # ...guess we didn't find the file; return a much better problem. - end - - # Now, either we have the version in our set of strings, or we didn't find - # the version they were looking for. In the future we will support - # loading versioned stuff from some look-aside part of the Ruby load path, - # but we don't need that right now. - # - # So, this comment is a place-holder for that. --daniel 2011-04-06 - return !! @strings[name].has_key?(version) - end - - def self.register(string) - @strings[underscorize(string.name)][string.version] = string - end - - def self.underscorize(name) - unless name.to_s =~ /^[-_a-z]+$/i then - raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" - end - - name.to_s.downcase.split(/[-_]/).join('_').to_sym - end -end -- cgit From 8d144d0bf5116c5f04522f2b4cd75699f6480f8e Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 7 Apr 2011 14:20:35 -0700 Subject: (#7012) Update references in code to use face(s) The codebase is now using the new name, faces, uniformly to reference the objects contained. All tests pass. --- lib/puppet/application/config.rb | 4 +- lib/puppet/application/configurer.rb | 6 +- lib/puppet/application/faces.rb | 18 ++-- lib/puppet/application/faces_base.rb | 24 ++--- lib/puppet/application/indirection_base.rb | 4 +- lib/puppet/faces.rb | 56 ++++++----- lib/puppet/faces/action.rb | 50 +++++----- lib/puppet/faces/action_builder.rb | 22 ++--- lib/puppet/faces/action_manager.rb | 8 +- lib/puppet/faces/catalog.rb | 6 +- lib/puppet/faces/catalog/select.rb | 2 +- lib/puppet/faces/certificate.rb | 4 +- lib/puppet/faces/certificate_request.rb | 4 +- lib/puppet/faces/certificate_revocation_list.rb | 4 +- lib/puppet/faces/config.rb | 4 +- lib/puppet/faces/configurer.rb | 10 +- lib/puppet/faces/face_collection.rb | 125 ++++++++++++++++++++++++ lib/puppet/faces/faces_collection.rb | 123 ----------------------- lib/puppet/faces/facts.rb | 4 +- lib/puppet/faces/file.rb | 4 +- lib/puppet/faces/indirector.rb | 10 +- lib/puppet/faces/key.rb | 4 +- lib/puppet/faces/node.rb | 4 +- lib/puppet/faces/option.rb | 8 +- lib/puppet/faces/option_builder.rb | 16 +-- lib/puppet/faces/option_manager.rb | 6 +- lib/puppet/faces/report.rb | 4 +- lib/puppet/faces/resource.rb | 4 +- lib/puppet/faces/resource_type.rb | 4 +- lib/puppet/faces/status.rb | 4 +- 30 files changed, 274 insertions(+), 272 deletions(-) create mode 100644 lib/puppet/faces/face_collection.rb delete mode 100644 lib/puppet/faces/faces_collection.rb (limited to 'lib/puppet') diff --git a/lib/puppet/application/config.rb b/lib/puppet/application/config.rb index f6559277b..41a46c339 100644 --- a/lib/puppet/application/config.rb +++ b/lib/puppet/application/config.rb @@ -1,4 +1,4 @@ -require 'puppet/application/string_base' +require 'puppet/application/faces_base' -class Puppet::Application::Config < Puppet::Application::StringBase +class Puppet::Application::Config < Puppet::Application::FacesBase end diff --git a/lib/puppet/application/configurer.rb b/lib/puppet/application/configurer.rb index be018338f..751e6b4d7 100644 --- a/lib/puppet/application/configurer.rb +++ b/lib/puppet/application/configurer.rb @@ -1,5 +1,5 @@ require 'puppet/application' -require 'puppet/string' +require 'puppet/faces' class Puppet::Application::Configurer < Puppet::Application should_parse_config @@ -17,7 +17,7 @@ class Puppet::Application::Configurer < Puppet::Application end def run_command - report = Puppet::String[:configurer, '0.0.1'].synchronize(Puppet[:certname]) - Puppet::String[:report, '0.0.1'].submit(report) + report = Puppet::Faces[:configurer, '0.0.1'].synchronize(Puppet[:certname]) + Puppet::Faces[:report, '0.0.1'].submit(report) end end diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb index 0a6a798ce..904a0cccc 100644 --- a/lib/puppet/application/faces.rb +++ b/lib/puppet/application/faces.rb @@ -1,7 +1,7 @@ require 'puppet/application' -require 'puppet/string' +require 'puppet/faces' -class Puppet::Application::String < Puppet::Application +class Puppet::Application::Faces < Puppet::Application should_parse_config run_mode :agent @@ -18,7 +18,7 @@ class Puppet::Application::String < Puppet::Application if arguments.empty? arguments = %w{terminuses actions} end - strings.each do |name| + faces.each do |name| str = "#{name}:\n" if arguments.include?("terminuses") begin @@ -68,12 +68,12 @@ class Puppet::Application::String < Puppet::Application end unless respond_to?(verb) - raise "Command '#{verb}' not found for 'string'" + raise "Command '#{verb}' not found for 'faces'" end end - def strings - Puppet::String.strings + def faces + Puppet::Faces.faces end def terminus_classes(indirection) @@ -81,9 +81,9 @@ class Puppet::Application::String < Puppet::Application end def actions(indirection) - return [] unless string = Puppet::String[indirection, '0.0.1'] - string.load_actions - return string.actions.sort { |a, b| a.to_s <=> b.to_s } + return [] unless faces = Puppet::Faces[indirection, '0.0.1'] + faces.load_actions + return faces.actions.sort { |a, b| a.to_s <=> b.to_s } end def load_applications diff --git a/lib/puppet/application/faces_base.rb b/lib/puppet/application/faces_base.rb index 09d02c079..6d66ee8a1 100644 --- a/lib/puppet/application/faces_base.rb +++ b/lib/puppet/application/faces_base.rb @@ -1,7 +1,7 @@ require 'puppet/application' -require 'puppet/string' +require 'puppet/faces' -class Puppet::Application::StringBase < Puppet::Application +class Puppet::Application::FacesBase < Puppet::Application should_parse_config run_mode :agent @@ -24,7 +24,7 @@ class Puppet::Application::StringBase < Puppet::Application end - attr_accessor :string, :action, :type, :arguments, :format + attr_accessor :face, :action, :type, :arguments, :format attr_writer :exit_code # This allows you to set the exit code if you don't want to just exit @@ -47,7 +47,7 @@ class Puppet::Application::StringBase < Puppet::Application def preinit super trap(:INT) do - $stderr.puts "Cancelling String" + $stderr.puts "Cancelling Face" exit(0) end @@ -57,8 +57,8 @@ class Puppet::Application::StringBase < Puppet::Application # TODO: These should be configurable versions, through a global # '--version' option, but we don't implement that yet... --daniel 2011-03-29 @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym - @string = Puppet::String[@type, :current] - @format = @string.default_format + @face = Puppet::Faces[@type, :current] + @format = @face.default_format # Now, walk the command line and identify the action. We skip over # arguments based on introspecting the action and all, and find the first @@ -68,11 +68,11 @@ class Puppet::Application::StringBase < Puppet::Application until @action or (index += 1) >= command_line.args.length do item = command_line.args[index] if item =~ /^-/ then - option = @string.options.find do |name| + option = @face.options.find do |name| item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ end if option then - option = @string.get_option(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 @@ -91,9 +91,9 @@ class Puppet::Application::StringBase < Puppet::Application raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}" end else - action = @string.get_action(item.to_sym) + action = @face.get_action(item.to_sym) if action.nil? then - raise ArgumentError, "#{@string} does not have an #{item.inspect} action!" + raise ArgumentError, "#{@face} does not have an #{item.inspect} action!" end @action = action end @@ -129,7 +129,7 @@ class Puppet::Application::StringBase < Puppet::Application # 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 string action. --daniel 2011-04-04 + # pass down to the face action. --daniel 2011-04-04 @arguments.delete_at(0) # We copy all of the app options to the end of the call; This allows each @@ -142,7 +142,7 @@ class Puppet::Application::StringBase < Puppet::Application def main # Call the method associated with the provided action (e.g., 'find'). - if result = @string.send(@action.name, *arguments) + if result = @face.send(@action.name, *arguments) puts render(result) end exit(exit_code) diff --git a/lib/puppet/application/indirection_base.rb b/lib/puppet/application/indirection_base.rb index cfa1ea529..7455ebedf 100644 --- a/lib/puppet/application/indirection_base.rb +++ b/lib/puppet/application/indirection_base.rb @@ -1,4 +1,4 @@ -require 'puppet/application/string_base' +require 'puppet/application/faces_base' -class Puppet::Application::IndirectionBase < Puppet::Application::StringBase +class Puppet::Application::IndirectionBase < Puppet::Application::FacesBase end diff --git a/lib/puppet/faces.rb b/lib/puppet/faces.rb index 517cf4506..07a745480 100644 --- a/lib/puppet/faces.rb +++ b/lib/puppet/faces.rb @@ -1,16 +1,16 @@ require 'puppet' require 'puppet/util/autoload' -class Puppet::String - require 'puppet/string/string_collection' +class Puppet::Faces + require 'puppet/faces/face_collection' - require 'puppet/string/action_manager' - include Puppet::String::ActionManager - extend Puppet::String::ActionManager + require 'puppet/faces/action_manager' + include Puppet::Faces::ActionManager + extend Puppet::Faces::ActionManager - require 'puppet/string/option_manager' - include Puppet::String::OptionManager - extend Puppet::String::OptionManager + require 'puppet/faces/option_manager' + include Puppet::Faces::OptionManager + extend Puppet::Faces::OptionManager include Puppet::Util @@ -19,33 +19,35 @@ class Puppet::String # list of directories to search. # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb def autoloader - @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") + @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/faces") end - def strings - Puppet::String::StringCollection.strings + def faces + Puppet::Faces::FaceCollection.faces end - def string?(name, version) - Puppet::String::StringCollection.string?(name, version) + def face?(name, version) + Puppet::Faces::FaceCollection.face?(name, version) end def register(instance) - Puppet::String::StringCollection.register(instance) + Puppet::Faces::FaceCollection.register(instance) end def define(name, version, &block) - if string?(name, version) - string = Puppet::String::StringCollection[name, version] + if face?(name, version) + face = Puppet::Faces::FaceCollection[name, version] else - string = self.new(name, version) - Puppet::String::StringCollection.register(string) - string.load_actions + face = self.new(name, version) + Puppet::Faces::FaceCollection.register(face) + # REVISIT: Shouldn't this be delayed until *after* we evaluate the + # current block, not done before? --daniel 2011-04-07 + face.load_actions end - string.instance_eval(&block) if block_given? + face.instance_eval(&block) if block_given? - return string + return face end alias :[] :define @@ -61,11 +63,11 @@ class Puppet::String attr_reader :name def initialize(name, version, &block) - unless Puppet::String::StringCollection.validate_version(version) - raise ArgumentError, "Cannot create string #{name.inspect} with invalid version number '#{version}'!" + unless Puppet::Faces::FaceCollection.validate_version(version) + raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" end - @name = Puppet::String::StringCollection.underscorize(name) + @name = Puppet::Faces::FaceCollection.underscorize(name) @version = version @default_format = :pson @@ -74,11 +76,11 @@ class Puppet::String # Try to find actions defined in other files. def load_actions - path = "puppet/string/#{name}" + path = "puppet/faces/#{name}" loaded = [] [path, "#{name}@#{version}/#{path}"].each do |path| - Puppet::String.autoloader.search_directories.each do |dir| + Puppet::Faces.autoloader.search_directories.each do |dir| fdir = ::File.join(dir, path) next unless FileTest.directory?(fdir) @@ -99,6 +101,6 @@ class Puppet::String end def to_s - "Puppet::String[#{name.inspect}, #{version.inspect}]" + "Puppet::Faces[#{name.inspect}, #{version.inspect}]" end end diff --git a/lib/puppet/faces/action.rb b/lib/puppet/faces/action.rb index 0f5032ffb..58d2c6003 100644 --- a/lib/puppet/faces/action.rb +++ b/lib/puppet/faces/action.rb @@ -1,28 +1,26 @@ # -*- coding: utf-8 -*- -require 'puppet/string' -require 'puppet/string/option' +require 'puppet/faces' +require 'puppet/faces/option' -class Puppet::String::Action - attr_reader :name - - def to_s - "#{@string}##{@name}" - end - - def initialize(string, name, attrs = {}) +class Puppet::Faces::Action + def initialize(face, name, attrs = {}) raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ - @string = string + @face = face @name = name.to_sym @options = {} attrs.each do |k, v| send("#{k}=", v) end end + attr_reader :name + def to_s() "#{@face}##{@name}" 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 string delegate to + # capabilities. Heck, our initial plan was to have the faces delegate to # the action object for invocation and all. # - # It turns out that we have a binding problem to solve: @string was bound to + # It turns out that we have a binding problem to solve: @face was bound to # the parent class, not the subclass instance, and we don't pass the # appropriate context or change the binding enough to make this work. # @@ -33,13 +31,13 @@ class Puppet::String::Action # So, we are pulling this method for now, and will return it to life when we # have the time to resolve the problem. For now, you should replace... # - # @action = @string.get_action(name) + # @action = @face.get_action(name) # @action.invoke(arg1, arg2, arg3) # # ...with... # - # @action = @string.get_action(name) - # @string.send(@action.name, arg1, arg2, arg3) + # @action = @face.get_action(name) + # @face.send(@action.name, arg1, arg2, arg3) # # I understand that is somewhat cumbersome, but it functions as desired. # --daniel 2011-03-31 @@ -48,7 +46,7 @@ class Puppet::String::Action # documentation, for the benefit of the reader. # # def invoke(*args, &block) - # @string.send(name, *args, &block) + # @face.send(name, *args, &block) # end def when_invoked=(block) @@ -82,12 +80,12 @@ class Puppet::String::Action self.__send__(#{internal_name.inspect}, *args) end" - if @string.is_a?(Class) - @string.class_eval do eval wrapper, nil, file, line end - @string.define_method(internal_name, &block) + if @face.is_a?(Class) + @face.class_eval do eval wrapper, nil, file, line end + @face.define_method(internal_name, &block) else - @string.instance_eval do eval wrapper, nil, file, line end - @string.meta_def(internal_name, &block) + @face.instance_eval do eval wrapper, nil, file, line end + @face.meta_def(internal_name, &block) end end @@ -95,8 +93,8 @@ class Puppet::String::Action option.aliases.each do |name| if conflict = get_option(name) then raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - elsif conflict = @string.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@string}" + elsif conflict = @face.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@face}" end end @@ -112,10 +110,10 @@ class Puppet::String::Action end def options - (@options.keys + @string.options).sort + (@options.keys + @face.options).sort end def get_option(name) - @options[name.to_sym] || @string.get_option(name) + @options[name.to_sym] || @face.get_option(name) end end diff --git a/lib/puppet/faces/action_builder.rb b/lib/puppet/faces/action_builder.rb index e7c03273b..a67068926 100644 --- a/lib/puppet/faces/action_builder.rb +++ b/lib/puppet/faces/action_builder.rb @@ -1,31 +1,31 @@ -require 'puppet/string' -require 'puppet/string/action' +require 'puppet/faces' +require 'puppet/faces/action' -class Puppet::String::ActionBuilder +class Puppet::Faces::ActionBuilder attr_reader :action - def self.build(string, name, &block) + def self.build(face, name, &block) raise "Action #{name.inspect} must specify a block" unless block - new(string, name, &block).action + new(face, name, &block).action end private - def initialize(string, name, &block) - @string = string - @action = Puppet::String::Action.new(string, name) + def initialize(face, name, &block) + @face = face + @action = Puppet::Faces::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 string would defer to it, but we can't get scope correct, - # so we stick with this. --daniel 2011-03-24 + # method on the face would defer to it, but we can't get scope correct, so + # we stick with this. --daniel 2011-03-24 def when_invoked(&block) raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action @action.when_invoked = block end def option(*declaration, &block) - option = Puppet::String::OptionBuilder.build(@action, *declaration, &block) + option = Puppet::Faces::OptionBuilder.build(@action, *declaration, &block) @action.add_option(option) end end diff --git a/lib/puppet/faces/action_manager.rb b/lib/puppet/faces/action_manager.rb index 9f0aa7582..6c0036bd8 100644 --- a/lib/puppet/faces/action_manager.rb +++ b/lib/puppet/faces/action_manager.rb @@ -1,12 +1,12 @@ -require 'puppet/string/action_builder' +require 'puppet/faces/action_builder' -module Puppet::String::ActionManager +module Puppet::Faces::ActionManager # Declare that this app can take a specific action, and provide # the code to do so. def action(name, &block) @actions ||= {} raise "Action #{name} already defined for #{self}" if action?(name) - action = Puppet::String::ActionBuilder.build(self, name, &block) + action = Puppet::Faces::ActionBuilder.build(self, name, &block) @actions[action.name] = action end @@ -15,7 +15,7 @@ module Puppet::String::ActionManager def script(name, &block) @actions ||= {} raise "Action #{name} already defined for #{self}" if action?(name) - @actions[name] = Puppet::String::Action.new(self, name, :when_invoked => block) + @actions[name] = Puppet::Faces::Action.new(self, name, :when_invoked => block) end def actions diff --git a/lib/puppet/faces/catalog.rb b/lib/puppet/faces/catalog.rb index 441c7ee7d..2e2168ac4 100644 --- a/lib/puppet/faces/catalog.rb +++ b/lib/puppet/faces/catalog.rb @@ -1,6 +1,6 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:catalog, '0.0.1') do +Puppet::Faces::Indirector.define(:catalog, '0.0.1') do action(:apply) do when_invoked do |catalog, options| report = Puppet::Transaction::Report.new("apply") @@ -28,7 +28,7 @@ Puppet::String::Indirector.define(:catalog, '0.0.1') do facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} catalog = nil retrieval_duration = thinmark do - catalog = Puppet::String[:catalog, '0.0.1'].find(certname, facts_to_upload) + catalog = Puppet::Faces[:catalog, '0.0.1'].find(certname, facts_to_upload) end catalog = catalog.to_ral catalog.finalize diff --git a/lib/puppet/faces/catalog/select.rb b/lib/puppet/faces/catalog/select.rb index 11670e2e7..e29d19970 100644 --- a/lib/puppet/faces/catalog/select.rb +++ b/lib/puppet/faces/catalog/select.rb @@ -1,5 +1,5 @@ # Select and show a list of resources of a given type. -Puppet::String.define(:catalog, '0.0.1') do +Puppet::Faces.define(:catalog, '0.0.1') do action :select do when_invoked do |host, type, options| catalog = Puppet::Resource::Catalog.indirection.find(host) diff --git a/lib/puppet/faces/certificate.rb b/lib/puppet/faces/certificate.rb index e8773ae2e..b10bee579 100644 --- a/lib/puppet/faces/certificate.rb +++ b/lib/puppet/faces/certificate.rb @@ -1,7 +1,7 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' require 'puppet/ssl/host' -Puppet::String::Indirector.define(:certificate, '0.0.1') do +Puppet::Faces::Indirector.define(:certificate, '0.0.1') do # REVISIT: This should use a pre-invoke hook to run the common code that # needs to happen before we invoke any action; that would be much nicer than # the "please repeat yourself" stuff found in here right now. diff --git a/lib/puppet/faces/certificate_request.rb b/lib/puppet/faces/certificate_request.rb index 218b40b98..5e91bdb7f 100644 --- a/lib/puppet/faces/certificate_request.rb +++ b/lib/puppet/faces/certificate_request.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:certificate_request, '0.0.1') do +Puppet::Faces::Indirector.define(:certificate_request, '0.0.1') do end diff --git a/lib/puppet/faces/certificate_revocation_list.rb b/lib/puppet/faces/certificate_revocation_list.rb index 9731b4f2d..2f2d72874 100644 --- a/lib/puppet/faces/certificate_revocation_list.rb +++ b/lib/puppet/faces/certificate_revocation_list.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:certificate_revocation_list, '0.0.1') do +Puppet::Faces::Indirector.define(:certificate_revocation_list, '0.0.1') do end diff --git a/lib/puppet/faces/config.rb b/lib/puppet/faces/config.rb index 8a9417148..647bf5052 100644 --- a/lib/puppet/faces/config.rb +++ b/lib/puppet/faces/config.rb @@ -1,6 +1,6 @@ -require 'puppet/string' +require 'puppet/faces' -Puppet::String.define(:config, '0.0.1') do +Puppet::Faces.define(:config, '0.0.1') do action(:print) do when_invoked do |*args| options = args.pop diff --git a/lib/puppet/faces/configurer.rb b/lib/puppet/faces/configurer.rb index 257f97e90..d40987697 100644 --- a/lib/puppet/faces/configurer.rb +++ b/lib/puppet/faces/configurer.rb @@ -1,11 +1,11 @@ -require 'puppet/string' +require 'puppet/faces' -Puppet::String.define(:configurer, '0.0.1') do +Puppet::Faces.define(:configurer, '0.0.1') do action(:synchronize) do when_invoked do |certname, options| - facts = Puppet::String[:facts, '0.0.1'].find(certname) - catalog = Puppet::String[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::String[:catalog, '0.0.1'].apply(catalog) + facts = Puppet::Faces[:facts, '0.0.1'].find(certname) + catalog = Puppet::Faces[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::Faces[:catalog, '0.0.1'].apply(catalog) report end end diff --git a/lib/puppet/faces/face_collection.rb b/lib/puppet/faces/face_collection.rb new file mode 100644 index 000000000..e6ee709d6 --- /dev/null +++ b/lib/puppet/faces/face_collection.rb @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +require 'puppet/faces' + +module Puppet::Faces::FaceCollection + SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ + + @faces = Hash.new { |hash, key| hash[key] = {} } + + def self.faces + unless @loaded + @loaded = true + $LOAD_PATH.each do |dir| + next unless FileTest.directory?(dir) + Dir.chdir(dir) do + # REVISIT: This is wrong!!!! We don't name files like that ever, + # so we should no longer match things like this. Damnit!!! --daniel 2011-04-07 + Dir.glob("puppet/faces/v*/*.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 + end + end + return @faces.keys + end + + def self.validate_version(version) + !!(SEMVER_VERSION =~ version.to_s) + end + + def self.cmp_semver(a, b) + a, b = [a, b].map do |x| + parts = SEMVER_VERSION.match(x).to_a[1..4] + parts[0..2] = parts[0..2].map { |e| e.to_i } + parts + end + + cmp = a[0..2] <=> b[0..2] + if cmp == 0 + cmp = a[3] <=> b[3] + cmp = +1 if a[3].empty? && !b[3].empty? + cmp = -1 if b[3].empty? && !a[3].empty? + end + cmp + end + + def self.[](name, version) + @faces[underscorize(name)][version] if face?(name, version) + end + + def self.face?(name, version) + name = underscorize(name) + return true if @faces[name].has_key?(version) + + # We always load the current version file; the common case is that we have + # the expected version and any compatibility versions in the same file, + # the default. Which means that this is almost always the case. + # + # We use require to avoid executing the code multiple times, like any + # other Ruby library that we might want to use. --daniel 2011-04-06 + begin + require "puppet/faces/#{name}" + + # If we wanted :current, we need to index to find that; direct version + # requests just work™ as they go. --daniel 2011-04-06 + if version == :current then + # We need to find current out of this. This is the largest version + # number that doesn't have a dedicated on-disk file present; those + # represent "experimental" versions of faces, which we don't fully + # support yet. + # + # We walk the versions from highest to lowest and take the first version + # that is not defined in an explicitly versioned file on disk as the + # current version. + # + # This constrains us to only ship experimental versions with *one* + # version in the file, not multiple, but given you can't reliably load + # them except by side-effect when you ignore that rule this seems safe + # enough... + # + # Given those constraints, and that we are not going to ship a versioned + # interface that is not :current in this release, we are going to leave + # these thoughts in place, and just punt on the actual versioning. + # + # When we upgrade the core to support multiple versions we can solve the + # problems then; as lazy as possible. + # + # We do support multiple versions in the same file, though, so we sort + # versions here and return the last item in that set. + # + # --daniel 2011-04-06 + latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last + @faces[name][:current] = @faces[name][latest_ver] + end + rescue LoadError => e + raise unless e.message =~ %r{-- puppet/faces/#{name}$} + # ...guess we didn't find the file; return a much better problem. + end + + # Now, either we have the version in our set of faces, or we didn't find + # the version they were looking for. In the future we will support + # loading versioned stuff from some look-aside part of the Ruby load path, + # but we don't need that right now. + # + # So, this comment is a place-holder for that. --daniel 2011-04-06 + return !! @faces[name].has_key?(version) + end + + def self.register(face) + @faces[underscorize(face.name)][face.version] = face + end + + def self.underscorize(name) + unless name.to_s =~ /^[-_a-z]+$/i then + raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" + end + + name.to_s.downcase.split(/[-_]/).join('_').to_sym + end +end diff --git a/lib/puppet/faces/faces_collection.rb b/lib/puppet/faces/faces_collection.rb deleted file mode 100644 index ecd99359d..000000000 --- a/lib/puppet/faces/faces_collection.rb +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/string' - -module Puppet::String::StringCollection - SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ - - @strings = Hash.new { |hash, key| hash[key] = {} } - - def self.strings - unless @loaded - @loaded = true - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - Dir.glob("puppet/string/v*/*.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 - end - end - return @strings.keys - end - - def self.validate_version(version) - !!(SEMVER_VERSION =~ version.to_s) - end - - def self.cmp_versions(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - - def self.[](name, version) - @strings[underscorize(name)][version] if string?(name, version) - end - - def self.string?(name, version) - name = underscorize(name) - return true if @strings[name].has_key?(version) - - # We always load the current version file; the common case is that we have - # the expected version and any compatibility versions in the same file, - # the default. Which means that this is almost always the case. - # - # We use require to avoid executing the code multiple times, like any - # other Ruby library that we might want to use. --daniel 2011-04-06 - begin - require "puppet/string/#{name}" - - # If we wanted :current, we need to index to find that; direct version - # requests just work™ as they go. --daniel 2011-04-06 - if version == :current then - # We need to find current out of this. This is the largest version - # number that doesn't have a dedicated on-disk file present; those - # represent "experimental" versions of strings, which we don't fully - # support yet. - # - # We walk the versions from highest to lowest and take the first version - # that is not defined in an explicitly versioned file on disk as the - # current version. - # - # This constrains us to only ship experimental versions with *one* - # version in the file, not multiple, but given you can't reliably load - # them except by side-effect when you ignore that rule this seems safe - # enough... - # - # Given those constraints, and that we are not going to ship a versioned - # interface that is not :current in this release, we are going to leave - # these thoughts in place, and just punt on the actual versioning. - # - # When we upgrade the core to support multiple versions we can solve the - # problems then; as lazy as possible. - # - # We do support multiple versions in the same file, though, so we sort - # versions here and return the last item in that set. - # - # --daniel 2011-04-06 - latest_ver = @strings[name].keys.sort {|a, b| cmp_versions(a, b) }.last - @strings[name][:current] = @strings[name][latest_ver] - end - rescue LoadError => e - raise unless e.message =~ %r{-- puppet/string/#{name}$} - # ...guess we didn't find the file; return a much better problem. - end - - # Now, either we have the version in our set of strings, or we didn't find - # the version they were looking for. In the future we will support - # loading versioned stuff from some look-aside part of the Ruby load path, - # but we don't need that right now. - # - # So, this comment is a place-holder for that. --daniel 2011-04-06 - return !! @strings[name].has_key?(version) - end - - def self.register(string) - @strings[underscorize(string.name)][string.version] = string - end - - def self.underscorize(name) - unless name.to_s =~ /^[-_a-z]+$/i then - raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" - end - - name.to_s.downcase.split(/[-_]/).join('_').to_sym - end -end diff --git a/lib/puppet/faces/facts.rb b/lib/puppet/faces/facts.rb index 6bd9904b0..33eacef38 100644 --- a/lib/puppet/faces/facts.rb +++ b/lib/puppet/faces/facts.rb @@ -1,7 +1,7 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' require 'puppet/node/facts' -Puppet::String::Indirector.define(:facts, '0.0.1') do +Puppet::Faces::Indirector.define(:facts, '0.0.1') do set_default_format :yaml # Upload our facts to the server diff --git a/lib/puppet/faces/file.rb b/lib/puppet/faces/file.rb index cc5737f28..e8ad18c17 100644 --- a/lib/puppet/faces/file.rb +++ b/lib/puppet/faces/file.rb @@ -1,5 +1,5 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:file, '0.0.1') do +Puppet::Faces::Indirector.define(:file, '0.0.1') do set_indirection_name :file_bucket_file end diff --git a/lib/puppet/faces/indirector.rb b/lib/puppet/faces/indirector.rb index 0c7d043bb..f72260017 100644 --- a/lib/puppet/faces/indirector.rb +++ b/lib/puppet/faces/indirector.rb @@ -1,7 +1,7 @@ require 'puppet' -require 'puppet/string' +require 'puppet/faces' -class Puppet::String::Indirector < Puppet::String +class Puppet::Faces::Indirector < Puppet::Faces option "--terminus TERMINUS" do desc "REVISIT: You can select a terminus, which has some bigger effect that we should describe in this file somehow." @@ -68,13 +68,13 @@ that we should describe in this file somehow." @indirection_name || name.to_sym end - # Here's your opportunity to override the indirection name. By default - # it will be the same name as the string. + # Here's your opportunity to override the indirection name. By default it + # will be the same name as the face. def set_indirection_name(name) @indirection_name = name end - # Return an indirection associated with an string, if one exists + # Return an indirection associated with a face, if one exists; # One usually does. def indirection unless @indirection diff --git a/lib/puppet/faces/key.rb b/lib/puppet/faces/key.rb index 95aceade5..7b6ad52ac 100644 --- a/lib/puppet/faces/key.rb +++ b/lib/puppet/faces/key.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:key, '0.0.1') do +Puppet::Faces::Indirector.define(:key, '0.0.1') do end diff --git a/lib/puppet/faces/node.rb b/lib/puppet/faces/node.rb index bc31a2cf3..7eed0df91 100644 --- a/lib/puppet/faces/node.rb +++ b/lib/puppet/faces/node.rb @@ -1,5 +1,5 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:node, '0.0.1') do +Puppet::Faces::Indirector.define(:node, '0.0.1') do set_default_format :yaml end diff --git a/lib/puppet/faces/option.rb b/lib/puppet/faces/option.rb index 352f7e5ef..7d3ed37ca 100644 --- a/lib/puppet/faces/option.rb +++ b/lib/puppet/faces/option.rb @@ -1,6 +1,6 @@ -require 'puppet/string' +require 'puppet/faces' -class Puppet::String::Option +class Puppet::Faces::Option attr_reader :parent attr_reader :name attr_reader :aliases @@ -65,8 +65,8 @@ class Puppet::String::Option end # to_s and optparse_to_name are roughly mirrored, because they are used to - # transform strings to name symbols, and vice-versa. This isn't a full - # bidirectional transformation though. + # transform options to name symbols, and vice-versa. This isn't a full + # bidirectional transformation though. --daniel 2011-04-07 def to_s @name.to_s.tr('_', '-') end diff --git a/lib/puppet/faces/option_builder.rb b/lib/puppet/faces/option_builder.rb index da0d213fb..0b6667546 100644 --- a/lib/puppet/faces/option_builder.rb +++ b/lib/puppet/faces/option_builder.rb @@ -1,22 +1,22 @@ -require 'puppet/string/option' +require 'puppet/faces/option' -class Puppet::String::OptionBuilder +class Puppet::Faces::OptionBuilder attr_reader :option - def self.build(string, *declaration, &block) - new(string, *declaration, &block).option + def self.build(face, *declaration, &block) + new(face, *declaration, &block).option end private - def initialize(string, *declaration, &block) - @string = string - @option = Puppet::String::Option.new(string, *declaration) + def initialize(face, *declaration, &block) + @face = face + @option = Puppet::Faces::Option.new(face, *declaration) block and instance_eval(&block) @option end # Metaprogram the simple DSL from the option class. - Puppet::String::Option.instance_methods.grep(/=$/).each do |setter| + Puppet::Faces::Option.instance_methods.grep(/=$/).each do |setter| next if setter =~ /^=/ # special case, darn it... dsl = setter.sub(/=$/, '') diff --git a/lib/puppet/faces/option_manager.rb b/lib/puppet/faces/option_manager.rb index f952ad4f0..02a73afc3 100644 --- a/lib/puppet/faces/option_manager.rb +++ b/lib/puppet/faces/option_manager.rb @@ -1,10 +1,10 @@ -require 'puppet/string/option_builder' +require 'puppet/faces/option_builder' -module Puppet::String::OptionManager +module Puppet::Faces::OptionManager # Declare that this app can take a specific option, and provide # the code to do so. def option(*declaration, &block) - add_option Puppet::String::OptionBuilder.build(self, *declaration, &block) + add_option Puppet::Faces::OptionBuilder.build(self, *declaration, &block) end def add_option(option) diff --git a/lib/puppet/faces/report.rb b/lib/puppet/faces/report.rb index da3ca8504..23a518981 100644 --- a/lib/puppet/faces/report.rb +++ b/lib/puppet/faces/report.rb @@ -1,6 +1,6 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:report, '0.0.1') do +Puppet::Faces::Indirector.define(:report, '0.0.1') do action(:submit) do when_invoked do |report, options| begin diff --git a/lib/puppet/faces/resource.rb b/lib/puppet/faces/resource.rb index 9838be0fa..60b0d94db 100644 --- a/lib/puppet/faces/resource.rb +++ b/lib/puppet/faces/resource.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:resource, '0.0.1') do +Puppet::Faces::Indirector.define(:resource, '0.0.1') do end diff --git a/lib/puppet/faces/resource_type.rb b/lib/puppet/faces/resource_type.rb index 8ca31ea6c..4321d65e7 100644 --- a/lib/puppet/faces/resource_type.rb +++ b/lib/puppet/faces/resource_type.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:resource_type, '0.0.1') do +Puppet::Faces::Indirector.define(:resource_type, '0.0.1') do end diff --git a/lib/puppet/faces/status.rb b/lib/puppet/faces/status.rb index 41de2bb99..e035f281f 100644 --- a/lib/puppet/faces/status.rb +++ b/lib/puppet/faces/status.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:status, '0.0.1') do +Puppet::Faces::Indirector.define(:status, '0.0.1') do end -- cgit From 87ed3188e65d3f5f9c2c32a409b271d1b39684b9 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 7 Apr 2011 15:44:28 -0700 Subject: (#7012) Split plumbing into Puppet::Interface This splits out the plumbing into the Puppet::Interface namespace, and uses Puppet::Faces for all the public-facing code. The fault line is "what you care about if you are using or writing a face", which is public, against "what you care about to enable either of those two", which is the plumbing. --- lib/puppet/faces.rb | 118 +++--------------------------- lib/puppet/faces/action.rb | 119 ------------------------------ lib/puppet/faces/action_builder.rb | 31 -------- lib/puppet/faces/action_manager.rb | 49 ------------- lib/puppet/faces/face_collection.rb | 125 -------------------------------- lib/puppet/faces/option.rb | 82 --------------------- lib/puppet/faces/option_builder.rb | 25 ------- lib/puppet/faces/option_manager.rb | 56 -------------- lib/puppet/interface.rb | 106 +++++++++++++++++++++++++++ lib/puppet/interface/action.rb | 119 ++++++++++++++++++++++++++++++ lib/puppet/interface/action_builder.rb | 31 ++++++++ lib/puppet/interface/action_manager.rb | 49 +++++++++++++ lib/puppet/interface/face_collection.rb | 125 ++++++++++++++++++++++++++++++++ lib/puppet/interface/option.rb | 82 +++++++++++++++++++++ lib/puppet/interface/option_builder.rb | 25 +++++++ lib/puppet/interface/option_manager.rb | 56 ++++++++++++++ 16 files changed, 605 insertions(+), 593 deletions(-) delete mode 100644 lib/puppet/faces/action.rb delete mode 100644 lib/puppet/faces/action_builder.rb delete mode 100644 lib/puppet/faces/action_manager.rb delete mode 100644 lib/puppet/faces/face_collection.rb delete mode 100644 lib/puppet/faces/option.rb delete mode 100644 lib/puppet/faces/option_builder.rb delete mode 100644 lib/puppet/faces/option_manager.rb create mode 100644 lib/puppet/interface.rb create mode 100644 lib/puppet/interface/action.rb create mode 100644 lib/puppet/interface/action_builder.rb create mode 100644 lib/puppet/interface/action_manager.rb create mode 100644 lib/puppet/interface/face_collection.rb create mode 100644 lib/puppet/interface/option.rb create mode 100644 lib/puppet/interface/option_builder.rb create mode 100644 lib/puppet/interface/option_manager.rb (limited to 'lib/puppet') diff --git a/lib/puppet/faces.rb b/lib/puppet/faces.rb index 07a745480..947eecf24 100644 --- a/lib/puppet/faces.rb +++ b/lib/puppet/faces.rb @@ -1,106 +1,12 @@ -require 'puppet' -require 'puppet/util/autoload' - -class Puppet::Faces - require 'puppet/faces/face_collection' - - require 'puppet/faces/action_manager' - include Puppet::Faces::ActionManager - extend Puppet::Faces::ActionManager - - require 'puppet/faces/option_manager' - include Puppet::Faces::OptionManager - extend Puppet::Faces::OptionManager - - include Puppet::Util - - class << self - # This is just so we can search for actions. We only use its - # list of directories to search. - # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb - def autoloader - @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/faces") - end - - def faces - Puppet::Faces::FaceCollection.faces - end - - def face?(name, version) - Puppet::Faces::FaceCollection.face?(name, version) - end - - def register(instance) - Puppet::Faces::FaceCollection.register(instance) - end - - def define(name, version, &block) - if face?(name, version) - face = Puppet::Faces::FaceCollection[name, version] - else - face = self.new(name, version) - Puppet::Faces::FaceCollection.register(face) - # REVISIT: Shouldn't this be delayed until *after* we evaluate the - # current block, not done before? --daniel 2011-04-07 - face.load_actions - end - - face.instance_eval(&block) if block_given? - - return face - end - - alias :[] :define - end - - attr_accessor :default_format - - def set_default_format(format) - self.default_format = format.to_sym - end - - attr_accessor :type, :verb, :version, :arguments - attr_reader :name - - def initialize(name, version, &block) - unless Puppet::Faces::FaceCollection.validate_version(version) - raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" - end - - @name = Puppet::Faces::FaceCollection.underscorize(name) - @version = version - @default_format = :pson - - instance_eval(&block) if block_given? - end - - # Try to find actions defined in other files. - def load_actions - path = "puppet/faces/#{name}" - - loaded = [] - [path, "#{name}@#{version}/#{path}"].each do |path| - Puppet::Faces.autoloader.search_directories.each do |dir| - fdir = ::File.join(dir, path) - next unless FileTest.directory?(fdir) - - Dir.chdir(fdir) do - Dir.glob("*.rb").each do |file| - aname = file.sub(/\.rb/, '') - if loaded.include?(aname) - Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - next - end - loaded << aname - Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - require "#{Dir.pwd}/#{aname}" - end - end - end - end - end - - def to_s - "Puppet::Faces[#{name.inspect}, #{version.inspect}]" - end -end +# The public name of this feature is 'faces', but we have hidden all the +# plumbing over in the 'interfaces' namespace to make clear the distinction +# between the two. +# +# This file exists to ensure that the public name is usable without revealing +# the details of the implementation; you really only need go look at anything +# under Interfaces if you are looking to extend the implementation. +# +# It isn't hidden to gratuitously hide things, just to make it easier to +# separate out the interests people will have. --daniel 2011-04-07 +require 'puppet/interface' +Puppet::Faces = Puppet::Interface diff --git a/lib/puppet/faces/action.rb b/lib/puppet/faces/action.rb deleted file mode 100644 index 58d2c6003..000000000 --- a/lib/puppet/faces/action.rb +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/faces' -require 'puppet/faces/option' - -class Puppet::Faces::Action - 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 - @options = {} - attrs.each do |k, v| send("#{k}=", v) end - end - - attr_reader :name - def to_s() "#{@face}##{@name}" 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 - # the action object for invocation and all. - # - # It turns out that we have a binding problem to solve: @face was bound to - # the parent class, not the subclass instance, and we don't pass the - # appropriate context or change the binding enough to make this work. - # - # We could hack around it, by either mandating that you pass the context in - # to invoke, or try to get the binding right, but that has probably got - # subtleties that we don't instantly think of – especially around threads. - # - # So, we are pulling this method for now, and will return it to life when we - # have the time to resolve the problem. For now, you should replace... - # - # @action = @face.get_action(name) - # @action.invoke(arg1, arg2, arg3) - # - # ...with... - # - # @action = @face.get_action(name) - # @face.send(@action.name, arg1, arg2, arg3) - # - # I understand that is somewhat cumbersome, but it functions as desired. - # --daniel 2011-03-31 - # - # PS: This code is left present, but commented, to support this chunk of - # documentation, for the benefit of the reader. - # - # def invoke(*args, &block) - # @face.send(name, *args, &block) - # end - - def when_invoked=(block) - # We need to build an instance method as a wrapper, using normal code, to - # be able to expose argument defaulting between the caller and definer in - # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. - # - # In future this also gives us a place to hook in additional behaviour - # such as calling out to the action instance to validate and coerce - # parameters, which avoids any exciting context switching and all. - # - # Hopefully we can improve this when we finally shuffle off the last of - # Ruby 1.8 support, but that looks to be a few "enterprise" release eras - # away, so we are pretty stuck with this for now. - # - # Patches to make this work more nicely with Ruby 1.9 using runtime - # version checking and all are welcome, but they can't actually help if - # the results are not totally hidden away in here. - # - # Incidentally, we though about vendoring evil-ruby and actually adjusting - # the internal C structure implementation details under the hood to make - # 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 - - internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym - file = __FILE__ + "+eval" - line = __LINE__ + 1 - wrapper = "def #{@name}(*args, &block) - args << {} unless args.last.is_a? Hash - args << block if block_given? - self.__send__(#{internal_name.inspect}, *args) - end" - - if @face.is_a?(Class) - @face.class_eval do eval wrapper, nil, file, line end - @face.define_method(internal_name, &block) - else - @face.instance_eval do eval wrapper, nil, file, line end - @face.meta_def(internal_name, &block) - end - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - elsif conflict = @face.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@face}" - end - end - - option.aliases.each do |name| - @options[name] = option - end - - option - end - - def option?(name) - @options.include? name.to_sym - end - - def options - (@options.keys + @face.options).sort - end - - def get_option(name) - @options[name.to_sym] || @face.get_option(name) - end -end diff --git a/lib/puppet/faces/action_builder.rb b/lib/puppet/faces/action_builder.rb deleted file mode 100644 index a67068926..000000000 --- a/lib/puppet/faces/action_builder.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'puppet/faces' -require 'puppet/faces/action' - -class Puppet::Faces::ActionBuilder - attr_reader :action - - def self.build(face, name, &block) - raise "Action #{name.inspect} must specify a block" unless block - new(face, name, &block).action - end - - private - def initialize(face, name, &block) - @face = face - @action = Puppet::Faces::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 - def when_invoked(&block) - raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action - @action.when_invoked = block - end - - def option(*declaration, &block) - option = Puppet::Faces::OptionBuilder.build(@action, *declaration, &block) - @action.add_option(option) - end -end diff --git a/lib/puppet/faces/action_manager.rb b/lib/puppet/faces/action_manager.rb deleted file mode 100644 index 6c0036bd8..000000000 --- a/lib/puppet/faces/action_manager.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'puppet/faces/action_builder' - -module Puppet::Faces::ActionManager - # Declare that this app can take a specific action, and provide - # the code to do so. - def action(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - action = Puppet::Faces::ActionBuilder.build(self, name, &block) - @actions[action.name] = action - end - - # This is the short-form of an action definition; it doesn't use the - # builder, just creates the action directly from the block. - def script(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - @actions[name] = Puppet::Faces::Action.new(self, name, :when_invoked => block) - end - - def actions - @actions ||= {} - result = @actions.keys - - if self.is_a?(Class) and superclass.respond_to?(:actions) - result += superclass.actions - elsif self.class.respond_to?(:actions) - result += self.class.actions - end - result.sort - end - - def get_action(name) - @actions ||= {} - result = @actions[name.to_sym] - if result.nil? - if self.is_a?(Class) and superclass.respond_to?(:get_action) - result = superclass.get_action(name) - elsif self.class.respond_to?(:get_action) - result = self.class.get_action(name) - end - end - return result - end - - def action?(name) - actions.include?(name.to_sym) - end -end diff --git a/lib/puppet/faces/face_collection.rb b/lib/puppet/faces/face_collection.rb deleted file mode 100644 index e6ee709d6..000000000 --- a/lib/puppet/faces/face_collection.rb +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/faces' - -module Puppet::Faces::FaceCollection - SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ - - @faces = Hash.new { |hash, key| hash[key] = {} } - - def self.faces - unless @loaded - @loaded = true - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - # REVISIT: This is wrong!!!! We don't name files like that ever, - # so we should no longer match things like this. Damnit!!! --daniel 2011-04-07 - Dir.glob("puppet/faces/v*/*.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 - end - end - return @faces.keys - end - - def self.validate_version(version) - !!(SEMVER_VERSION =~ version.to_s) - end - - def self.cmp_semver(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - - def self.[](name, version) - @faces[underscorize(name)][version] if face?(name, version) - end - - def self.face?(name, version) - name = underscorize(name) - return true if @faces[name].has_key?(version) - - # We always load the current version file; the common case is that we have - # the expected version and any compatibility versions in the same file, - # the default. Which means that this is almost always the case. - # - # We use require to avoid executing the code multiple times, like any - # other Ruby library that we might want to use. --daniel 2011-04-06 - begin - require "puppet/faces/#{name}" - - # If we wanted :current, we need to index to find that; direct version - # requests just work™ as they go. --daniel 2011-04-06 - if version == :current then - # We need to find current out of this. This is the largest version - # number that doesn't have a dedicated on-disk file present; those - # represent "experimental" versions of faces, which we don't fully - # support yet. - # - # We walk the versions from highest to lowest and take the first version - # that is not defined in an explicitly versioned file on disk as the - # current version. - # - # This constrains us to only ship experimental versions with *one* - # version in the file, not multiple, but given you can't reliably load - # them except by side-effect when you ignore that rule this seems safe - # enough... - # - # Given those constraints, and that we are not going to ship a versioned - # interface that is not :current in this release, we are going to leave - # these thoughts in place, and just punt on the actual versioning. - # - # When we upgrade the core to support multiple versions we can solve the - # problems then; as lazy as possible. - # - # We do support multiple versions in the same file, though, so we sort - # versions here and return the last item in that set. - # - # --daniel 2011-04-06 - latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last - @faces[name][:current] = @faces[name][latest_ver] - end - rescue LoadError => e - raise unless e.message =~ %r{-- puppet/faces/#{name}$} - # ...guess we didn't find the file; return a much better problem. - end - - # Now, either we have the version in our set of faces, or we didn't find - # the version they were looking for. In the future we will support - # loading versioned stuff from some look-aside part of the Ruby load path, - # but we don't need that right now. - # - # So, this comment is a place-holder for that. --daniel 2011-04-06 - return !! @faces[name].has_key?(version) - end - - def self.register(face) - @faces[underscorize(face.name)][face.version] = face - end - - def self.underscorize(name) - unless name.to_s =~ /^[-_a-z]+$/i then - raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" - end - - name.to_s.downcase.split(/[-_]/).join('_').to_sym - end -end diff --git a/lib/puppet/faces/option.rb b/lib/puppet/faces/option.rb deleted file mode 100644 index 7d3ed37ca..000000000 --- a/lib/puppet/faces/option.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'puppet/faces' - -class Puppet::Faces::Option - attr_reader :parent - attr_reader :name - attr_reader :aliases - attr_reader :optparse - attr_accessor :desc - - def takes_argument? - !!@argument - end - def optional_argument? - !!@optional_argument - end - - def initialize(parent, *declaration, &block) - @parent = parent - @optparse = [] - - # Collect and sort the arguments in the declaration. - dups = {} - declaration.each do |item| - if item.is_a? String and item.to_s =~ /^-/ then - unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then - raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" - end - @optparse << item - - # Duplicate checking... - name = optparse_to_name(item) - if dup = dups[name] then - raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" - else - dups[name] = item - end - else - raise ArgumentError, "#{item.inspect} is not valid for an option argument" - end - end - - if @optparse.empty? then - raise ArgumentError, "No option declarations found while building" - end - - # Now, infer the name from the options; we prefer the first long option as - # the name, rather than just the first option. - @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) - @aliases = @optparse.map { |o| optparse_to_name(o) } - - # Do we take an argument? If so, are we consistent about it, because - # incoherence here makes our life super-difficult, and we can more easily - # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 - @argument = @optparse.any? { |o| o =~ /[ =]/ } - if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then - raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" - end - - # Is our argument optional? The rules about consistency apply here, also, - # just like they do to taking arguments at all. --daniel 2011-03-30 - @optional_argument = @optparse.any? { |o| o.include? "[" } - if @optional_argument and not @optparse.all? { |o| o.include? "[" } then - raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" - end - end - - # to_s and optparse_to_name are roughly mirrored, because they are used to - # transform options to name symbols, and vice-versa. This isn't a full - # bidirectional transformation though. --daniel 2011-04-07 - def to_s - @name.to_s.tr('_', '-') - end - - def optparse_to_name(declaration) - unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then - raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" - end - name = found.captures.first.tr('-', '_') - raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ - name.to_sym - end -end diff --git a/lib/puppet/faces/option_builder.rb b/lib/puppet/faces/option_builder.rb deleted file mode 100644 index 0b6667546..000000000 --- a/lib/puppet/faces/option_builder.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'puppet/faces/option' - -class Puppet::Faces::OptionBuilder - attr_reader :option - - def self.build(face, *declaration, &block) - new(face, *declaration, &block).option - end - - private - def initialize(face, *declaration, &block) - @face = face - @option = Puppet::Faces::Option.new(face, *declaration) - block and instance_eval(&block) - @option - end - - # Metaprogram the simple DSL from the option class. - Puppet::Faces::Option.instance_methods.grep(/=$/).each do |setter| - next if setter =~ /^=/ # special case, darn it... - - dsl = setter.sub(/=$/, '') - define_method(dsl) do |value| @option.send(setter, value) end - end -end diff --git a/lib/puppet/faces/option_manager.rb b/lib/puppet/faces/option_manager.rb deleted file mode 100644 index 02a73afc3..000000000 --- a/lib/puppet/faces/option_manager.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'puppet/faces/option_builder' - -module Puppet::Faces::OptionManager - # Declare that this app can take a specific option, and provide - # the code to do so. - def option(*declaration, &block) - add_option Puppet::Faces::OptionBuilder.build(self, *declaration, &block) - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - end - - actions.each do |action| - action = get_action(action) - if conflict = action.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" - end - end - end - - option.aliases.each { |name| @options[name] = option } - option - end - - def options - @options ||= {} - result = @options.keys - - if self.is_a?(Class) and superclass.respond_to?(:options) - result += superclass.options - elsif self.class.respond_to?(:options) - result += self.class.options - end - result.sort - end - - def get_option(name) - @options ||= {} - result = @options[name.to_sym] - unless result then - if self.is_a?(Class) and superclass.respond_to?(:get_option) - result = superclass.get_option(name) - elsif self.class.respond_to?(:get_option) - result = self.class.get_option(name) - end - end - return result - end - - def option?(name) - options.include? name.to_sym - end -end diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb new file mode 100644 index 000000000..70484adfc --- /dev/null +++ b/lib/puppet/interface.rb @@ -0,0 +1,106 @@ +require 'puppet' +require 'puppet/util/autoload' + +class Puppet::Interface + require 'puppet/interface/face_collection' + + require 'puppet/interface/action_manager' + include Puppet::Interface::ActionManager + extend Puppet::Interface::ActionManager + + require 'puppet/interface/option_manager' + include Puppet::Interface::OptionManager + extend Puppet::Interface::OptionManager + + include Puppet::Util + + class << self + # This is just so we can search for actions. We only use its + # list of directories to search. + # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb + def autoloader + @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/faces") + end + + def faces + Puppet::Interface::FaceCollection.faces + end + + def face?(name, version) + Puppet::Interface::FaceCollection.face?(name, version) + end + + def register(instance) + Puppet::Interface::FaceCollection.register(instance) + end + + def define(name, version, &block) + if face?(name, version) + face = Puppet::Interface::FaceCollection[name, version] + else + face = self.new(name, version) + Puppet::Interface::FaceCollection.register(face) + # REVISIT: Shouldn't this be delayed until *after* we evaluate the + # current block, not done before? --daniel 2011-04-07 + face.load_actions + end + + face.instance_eval(&block) if block_given? + + return face + end + + alias :[] :define + end + + attr_accessor :default_format + + def set_default_format(format) + self.default_format = format.to_sym + end + + attr_accessor :type, :verb, :version, :arguments + attr_reader :name + + def initialize(name, version, &block) + unless Puppet::Interface::FaceCollection.validate_version(version) + raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" + end + + @name = Puppet::Interface::FaceCollection.underscorize(name) + @version = version + @default_format = :pson + + instance_eval(&block) if block_given? + end + + # Try to find actions defined in other files. + def load_actions + path = "puppet/faces/#{name}" + + loaded = [] + [path, "#{name}@#{version}/#{path}"].each do |path| + Puppet::Interface.autoloader.search_directories.each do |dir| + fdir = ::File.join(dir, path) + next unless FileTest.directory?(fdir) + + Dir.chdir(fdir) do + Dir.glob("*.rb").each do |file| + aname = file.sub(/\.rb/, '') + if loaded.include?(aname) + Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + next + end + loaded << aname + Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + require "#{Dir.pwd}/#{aname}" + end + end + end + end + end + + def to_s + "Puppet::Faces[#{name.inspect}, #{version.inspect}]" + end +end diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb new file mode 100644 index 000000000..e4a37a1f7 --- /dev/null +++ b/lib/puppet/interface/action.rb @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +require 'puppet/interface' +require 'puppet/interface/option' + +class Puppet::Interface::Action + 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 + @options = {} + attrs.each do |k, v| send("#{k}=", v) end + end + + attr_reader :name + def to_s() "#{@face}##{@name}" 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 + # the action object for invocation and all. + # + # It turns out that we have a binding problem to solve: @face was bound to + # the parent class, not the subclass instance, and we don't pass the + # appropriate context or change the binding enough to make this work. + # + # We could hack around it, by either mandating that you pass the context in + # to invoke, or try to get the binding right, but that has probably got + # subtleties that we don't instantly think of – especially around threads. + # + # So, we are pulling this method for now, and will return it to life when we + # have the time to resolve the problem. For now, you should replace... + # + # @action = @face.get_action(name) + # @action.invoke(arg1, arg2, arg3) + # + # ...with... + # + # @action = @face.get_action(name) + # @face.send(@action.name, arg1, arg2, arg3) + # + # I understand that is somewhat cumbersome, but it functions as desired. + # --daniel 2011-03-31 + # + # PS: This code is left present, but commented, to support this chunk of + # documentation, for the benefit of the reader. + # + # def invoke(*args, &block) + # @face.send(name, *args, &block) + # end + + def when_invoked=(block) + # We need to build an instance method as a wrapper, using normal code, to + # be able to expose argument defaulting between the caller and definer in + # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. + # + # In future this also gives us a place to hook in additional behaviour + # such as calling out to the action instance to validate and coerce + # parameters, which avoids any exciting context switching and all. + # + # Hopefully we can improve this when we finally shuffle off the last of + # Ruby 1.8 support, but that looks to be a few "enterprise" release eras + # away, so we are pretty stuck with this for now. + # + # Patches to make this work more nicely with Ruby 1.9 using runtime + # version checking and all are welcome, but they can't actually help if + # the results are not totally hidden away in here. + # + # Incidentally, we though about vendoring evil-ruby and actually adjusting + # the internal C structure implementation details under the hood to make + # 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 + + internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym + file = __FILE__ + "+eval" + line = __LINE__ + 1 + wrapper = "def #{@name}(*args, &block) + args << {} unless args.last.is_a? Hash + args << block if block_given? + self.__send__(#{internal_name.inspect}, *args) + end" + + if @face.is_a?(Class) + @face.class_eval do eval wrapper, nil, file, line end + @face.define_method(internal_name, &block) + else + @face.instance_eval do eval wrapper, nil, file, line end + @face.meta_def(internal_name, &block) + end + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + elsif conflict = @face.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@face}" + end + end + + option.aliases.each do |name| + @options[name] = option + end + + option + end + + def option?(name) + @options.include? name.to_sym + end + + def options + (@options.keys + @face.options).sort + end + + def get_option(name) + @options[name.to_sym] || @face.get_option(name) + end +end diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb new file mode 100644 index 000000000..b08c3d023 --- /dev/null +++ b/lib/puppet/interface/action_builder.rb @@ -0,0 +1,31 @@ +require 'puppet/interface' +require 'puppet/interface/action' + +class Puppet::Interface::ActionBuilder + attr_reader :action + + def self.build(face, name, &block) + raise "Action #{name.inspect} must specify a block" unless block + 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 + def when_invoked(&block) + raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action + @action.when_invoked = block + end + + def option(*declaration, &block) + option = Puppet::Interface::OptionBuilder.build(@action, *declaration, &block) + @action.add_option(option) + end +end diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb new file mode 100644 index 000000000..bb0e5bf57 --- /dev/null +++ b/lib/puppet/interface/action_manager.rb @@ -0,0 +1,49 @@ +require 'puppet/interface/action_builder' + +module Puppet::Interface::ActionManager + # Declare that this app can take a specific action, and provide + # the code to do so. + def action(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + action = Puppet::Interface::ActionBuilder.build(self, name, &block) + @actions[action.name] = action + end + + # This is the short-form of an action definition; it doesn't use the + # builder, just creates the action directly from the block. + def script(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + @actions[name] = Puppet::Interface::Action.new(self, name, :when_invoked => block) + end + + def actions + @actions ||= {} + result = @actions.keys + + if self.is_a?(Class) and superclass.respond_to?(:actions) + result += superclass.actions + elsif self.class.respond_to?(:actions) + result += self.class.actions + end + result.sort + end + + def get_action(name) + @actions ||= {} + result = @actions[name.to_sym] + if result.nil? + if self.is_a?(Class) and superclass.respond_to?(:get_action) + result = superclass.get_action(name) + elsif self.class.respond_to?(:get_action) + result = self.class.get_action(name) + end + end + return result + end + + def action?(name) + actions.include?(name.to_sym) + end +end diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb new file mode 100644 index 000000000..9f7a499c2 --- /dev/null +++ b/lib/puppet/interface/face_collection.rb @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +require 'puppet/interface' + +module Puppet::Interface::FaceCollection + SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ + + @faces = Hash.new { |hash, key| hash[key] = {} } + + def self.faces + unless @loaded + @loaded = true + $LOAD_PATH.each do |dir| + next unless FileTest.directory?(dir) + Dir.chdir(dir) do + # REVISIT: This is wrong!!!! We don't name files like that ever, + # so we should no longer match things like this. Damnit!!! --daniel 2011-04-07 + Dir.glob("puppet/faces/v*/*.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 + end + end + return @faces.keys + end + + def self.validate_version(version) + !!(SEMVER_VERSION =~ version.to_s) + end + + def self.cmp_semver(a, b) + a, b = [a, b].map do |x| + parts = SEMVER_VERSION.match(x).to_a[1..4] + parts[0..2] = parts[0..2].map { |e| e.to_i } + parts + end + + cmp = a[0..2] <=> b[0..2] + if cmp == 0 + cmp = a[3] <=> b[3] + cmp = +1 if a[3].empty? && !b[3].empty? + cmp = -1 if b[3].empty? && !a[3].empty? + end + cmp + end + + def self.[](name, version) + @faces[underscorize(name)][version] if face?(name, version) + end + + def self.face?(name, version) + name = underscorize(name) + return true if @faces[name].has_key?(version) + + # We always load the current version file; the common case is that we have + # the expected version and any compatibility versions in the same file, + # the default. Which means that this is almost always the case. + # + # We use require to avoid executing the code multiple times, like any + # other Ruby library that we might want to use. --daniel 2011-04-06 + begin + require "puppet/faces/#{name}" + + # If we wanted :current, we need to index to find that; direct version + # requests just work™ as they go. --daniel 2011-04-06 + if version == :current then + # We need to find current out of this. This is the largest version + # number that doesn't have a dedicated on-disk file present; those + # represent "experimental" versions of faces, which we don't fully + # support yet. + # + # We walk the versions from highest to lowest and take the first version + # that is not defined in an explicitly versioned file on disk as the + # current version. + # + # This constrains us to only ship experimental versions with *one* + # version in the file, not multiple, but given you can't reliably load + # them except by side-effect when you ignore that rule this seems safe + # enough... + # + # Given those constraints, and that we are not going to ship a versioned + # interface that is not :current in this release, we are going to leave + # these thoughts in place, and just punt on the actual versioning. + # + # When we upgrade the core to support multiple versions we can solve the + # problems then; as lazy as possible. + # + # We do support multiple versions in the same file, though, so we sort + # versions here and return the last item in that set. + # + # --daniel 2011-04-06 + latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last + @faces[name][:current] = @faces[name][latest_ver] + end + rescue LoadError => e + raise unless e.message =~ %r{-- puppet/faces/#{name}$} + # ...guess we didn't find the file; return a much better problem. + end + + # Now, either we have the version in our set of faces, or we didn't find + # the version they were looking for. In the future we will support + # loading versioned stuff from some look-aside part of the Ruby load path, + # but we don't need that right now. + # + # So, this comment is a place-holder for that. --daniel 2011-04-06 + return !! @faces[name].has_key?(version) + end + + def self.register(face) + @faces[underscorize(face.name)][face.version] = face + end + + def self.underscorize(name) + unless name.to_s =~ /^[-_a-z]+$/i then + raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" + end + + name.to_s.downcase.split(/[-_]/).join('_').to_sym + end +end diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb new file mode 100644 index 000000000..ccc2fbba7 --- /dev/null +++ b/lib/puppet/interface/option.rb @@ -0,0 +1,82 @@ +require 'puppet/interface' + +class Puppet::Interface::Option + attr_reader :parent + attr_reader :name + attr_reader :aliases + attr_reader :optparse + attr_accessor :desc + + def takes_argument? + !!@argument + end + def optional_argument? + !!@optional_argument + end + + def initialize(parent, *declaration, &block) + @parent = parent + @optparse = [] + + # Collect and sort the arguments in the declaration. + dups = {} + declaration.each do |item| + if item.is_a? String and item.to_s =~ /^-/ then + unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then + raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" + end + @optparse << item + + # Duplicate checking... + name = optparse_to_name(item) + if dup = dups[name] then + raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" + else + dups[name] = item + end + else + raise ArgumentError, "#{item.inspect} is not valid for an option argument" + end + end + + if @optparse.empty? then + raise ArgumentError, "No option declarations found while building" + end + + # Now, infer the name from the options; we prefer the first long option as + # the name, rather than just the first option. + @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) + @aliases = @optparse.map { |o| optparse_to_name(o) } + + # Do we take an argument? If so, are we consistent about it, because + # incoherence here makes our life super-difficult, and we can more easily + # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 + @argument = @optparse.any? { |o| o =~ /[ =]/ } + if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then + raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" + end + + # Is our argument optional? The rules about consistency apply here, also, + # just like they do to taking arguments at all. --daniel 2011-03-30 + @optional_argument = @optparse.any? { |o| o.include? "[" } + if @optional_argument and not @optparse.all? { |o| o.include? "[" } then + raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" + end + end + + # to_s and optparse_to_name are roughly mirrored, because they are used to + # transform options to name symbols, and vice-versa. This isn't a full + # bidirectional transformation though. --daniel 2011-04-07 + def to_s + @name.to_s.tr('_', '-') + end + + def optparse_to_name(declaration) + unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then + raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" + end + name = found.captures.first.tr('-', '_') + raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ + name.to_sym + end +end diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb new file mode 100644 index 000000000..83a1906b0 --- /dev/null +++ b/lib/puppet/interface/option_builder.rb @@ -0,0 +1,25 @@ +require 'puppet/interface/option' + +class Puppet::Interface::OptionBuilder + attr_reader :option + + def self.build(face, *declaration, &block) + new(face, *declaration, &block).option + end + + private + def initialize(face, *declaration, &block) + @face = face + @option = Puppet::Interface::Option.new(face, *declaration) + block and instance_eval(&block) + @option + end + + # Metaprogram the simple DSL from the option class. + Puppet::Interface::Option.instance_methods.grep(/=$/).each do |setter| + next if setter =~ /^=/ # special case, darn it... + + dsl = setter.sub(/=$/, '') + define_method(dsl) do |value| @option.send(setter, value) end + end +end diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb new file mode 100644 index 000000000..56df9760f --- /dev/null +++ b/lib/puppet/interface/option_manager.rb @@ -0,0 +1,56 @@ +require 'puppet/interface/option_builder' + +module Puppet::Interface::OptionManager + # Declare that this app can take a specific option, and provide + # the code to do so. + def option(*declaration, &block) + add_option Puppet::Interface::OptionBuilder.build(self, *declaration, &block) + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + end + + actions.each do |action| + action = get_action(action) + if conflict = action.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" + end + end + end + + option.aliases.each { |name| @options[name] = option } + option + end + + def options + @options ||= {} + result = @options.keys + + if self.is_a?(Class) and superclass.respond_to?(:options) + result += superclass.options + elsif self.class.respond_to?(:options) + result += self.class.options + end + result.sort + end + + def get_option(name) + @options ||= {} + result = @options[name.to_sym] + unless result then + if self.is_a?(Class) and superclass.respond_to?(:get_option) + result = superclass.get_option(name) + elsif self.class.respond_to?(:get_option) + result = self.class.get_option(name) + end + end + return result + end + + def option?(name) + options.include? name.to_sym + end +end -- cgit