diff options
| author | Max Martin <max@puppetlabs.com> | 2011-04-13 17:30:44 -0700 |
|---|---|---|
| committer | Max Martin <max@puppetlabs.com> | 2011-04-13 17:30:44 -0700 |
| commit | 3dde838ac992571e13262ea29ba3a0eb8152e753 (patch) | |
| tree | 90520cf62bfa2f1bb9c992bbfe1bc47ae10471f2 /lib | |
| parent | fe45c2417af580597cd39adec96a30a05a7cd66a (diff) | |
| parent | 3ab44c7ce01ab86a995deb66228f5be95239c92a (diff) | |
| download | puppet-3dde838ac992571e13262ea29ba3a0eb8152e753.tar.gz puppet-3dde838ac992571e13262ea29ba3a0eb8152e753.tar.xz puppet-3dde838ac992571e13262ea29ba3a0eb8152e753.zip | |
Merge branch 'next'
* next: (204 commits)
Revert "(#6928) Removed --ignoreimport"
Updated CHANGELOG for 2.6.8rc1
(#6928) Removed --ignoreimport
(#6928) Remove --parseonly
(#6928) Add a Parser face with Validate action
(#6830) Fix sha1 to digest/sha1 require issue for Ruby 1.9
(#6830) Fix UTF-8 encoding issue for Ruby 1.9
(#6830) Fix string method sub call on a symbol for Ruby 1.9
(#2331) Remove darwinports pkg provider, replace with rewritten macports provider
(#7059) handle inherited action binding scope
maint: ensure we handle '-foo=' options correctly in faces.
(#2150) Fix File const lookup when configuring routes
Fixed #7082 - Added system support for groups
maint: install erb templates under lib/
maint: clean up the spec test headers in bulk.
(#7056) Use 'face' rather than 'faces' in the production code.
maint: eliminate deprecated since 2008 code from Puppet.
(#6117) Add POST support to indirector requests
(#6962) Move option handling into #parse_options, not #preinit.
maint: whitespace cleanup for puppet/util/command_line.
...
Diffstat (limited to 'lib')
127 files changed, 2739 insertions, 499 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index ef5fb7f06..64236576a 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -24,7 +24,7 @@ require 'puppet/util/run_mode' # it's also a place to find top-level commands like 'debug' module Puppet - PUPPETVERSION = '2.6.7' + PUPPETVERSION = '2.6.8' def Puppet.version PUPPETVERSION @@ -141,17 +141,12 @@ module Puppet end end - # Create a new type. Just proxy to the Type class. + # Create a new type. Just proxy to the Type class. The mirroring query + # code was deprecated in 2008, but this is still in heavy use. I suppose + # this can count as a soft deprecation for the next dev. --daniel 2011-04-12 def self.newtype(name, options = {}, &block) Puppet::Type.newtype(name, options, &block) end - - # Retrieve a type by name. Just proxy to the Type class. - def self.type(name) - # LAK:DEP Deprecation notice added 12/17/2008 - Puppet.warning "Puppet.type is deprecated; use Puppet::Type.type" - Puppet::Type.type(name) - end end require 'puppet/type' diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index 57bd88877..374dc850b 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -299,11 +299,12 @@ class Application # This is the main application entry point def run - exit_on_fail("initialize") { hook('preinit') { preinit } } - exit_on_fail("parse options") { hook('parse_options') { parse_options } } - exit_on_fail("parse configuration file") { Puppet.settings.parse } if should_parse_config? - exit_on_fail("prepare for execution") { hook('setup') { setup } } - exit_on_fail("run") { hook('run_command') { run_command } } + exit_on_fail("initialize") { hook('preinit') { preinit } } + exit_on_fail("parse options") { hook('parse_options') { parse_options } } + exit_on_fail("parse configuration file") { Puppet.settings.parse } if should_parse_config? + exit_on_fail("prepare for execution") { hook('setup') { setup } } + exit_on_fail("configure routes from #{Puppet[:route_file]}") { configure_indirector_routes } + exit_on_fail("run") { hook('run_command') { run_command } } end def main @@ -328,6 +329,15 @@ class Application Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] end + def configure_indirector_routes + route_file = Puppet[:route_file] + if ::File.exists?(route_file) + routes = YAML.load_file(route_file) + application_routes = routes[name.to_s] + Puppet::Indirector.configure_routes(application_routes) if application_routes + end + end + def parse_options # Create an option parser option_parser = OptionParser.new(self.class.banner) @@ -349,14 +359,10 @@ class Application end end - # scan command line. - begin - option_parser.parse!(self.command_line.args) - rescue OptionParser::ParseError => detail - $stderr.puts detail - $stderr.puts "Try 'puppet #{command_line.subcommand_name} --help'" - exit(1) - end + # Scan command line. We just hand any exceptions to our upper levels, + # rather than printing help and exiting, so that we can meaningfully + # respond with context-sensitive help if we want to. --daniel 2011-04-12 + option_parser.parse!(self.command_line.args) end def handlearg(opt, arg) @@ -394,7 +400,7 @@ class Application def exit_on_fail(message, code = 1) yield - rescue RuntimeError, NotImplementedError => detail + rescue ArgumentError, RuntimeError, NotImplementedError => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Could not #{message}: #{detail}" exit(code) diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index 2ee40227e..fc8616817 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -288,8 +288,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005, 2006 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index 2b7c9f8fb..5779e799c 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -26,6 +26,11 @@ class Puppet::Application::Apply < Puppet::Application end end + option("--parseonly") do + puts "--parseonly has been removed. Please use 'puppet parser validate <manifest>'" + exit 1 + end + def help <<-HELP @@ -117,8 +122,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end @@ -126,8 +130,6 @@ License def run_command if options[:catalog] apply - elsif Puppet[:parseonly] - parseonly else main end @@ -154,22 +156,6 @@ License configurer.run :catalog => catalog end - def parseonly - # Set our code or file to use. - if options[:code] or command_line.args.length == 0 - Puppet[:code] = options[:code] || STDIN.read - else - Puppet[:manifest] = command_line.args.shift - end - begin - Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types - rescue => detail - Puppet.err detail - exit 1 - end - exit 0 - end - def main # Set our code or file to use. if options[:code] or command_line.args.length == 0 diff --git a/lib/puppet/application/catalog.rb b/lib/puppet/application/catalog.rb new file mode 100644 index 000000000..10ce05be7 --- /dev/null +++ b/lib/puppet/application/catalog.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Catalog < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/cert.rb b/lib/puppet/application/cert.rb index f02fc893c..c08775380 100644 --- a/lib/puppet/application/cert.rb +++ b/lib/puppet/application/cert.rb @@ -48,7 +48,7 @@ class Puppet::Application::Cert < Puppet::Application end def help - puts <<-HELP + <<-HELP puppet-cert(8) -- Manage certificates and requests ======== @@ -163,11 +163,9 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP - exit end def main diff --git a/lib/puppet/application/certificate.rb b/lib/puppet/application/certificate.rb new file mode 100644 index 000000000..eacb830b2 --- /dev/null +++ b/lib/puppet/application/certificate.rb @@ -0,0 +1,18 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Certificate < Puppet::Application::IndirectionBase + def setup + unless options[:ca_location] + raise ArgumentError, "You must have a CA location specified;\n" + + "use --ca-location to specify the location (remote, local, only)" + end + + location = Puppet::SSL::Host.ca_location + if location == :local && !Puppet::SSL::CertificateAuthority.ca? + self.class.run_mode("master") + self.set_run_mode self.class.run_mode + end + + super + end +end diff --git a/lib/puppet/application/certificate_request.rb b/lib/puppet/application/certificate_request.rb new file mode 100644 index 000000000..1b1b0830c --- /dev/null +++ b/lib/puppet/application/certificate_request.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Certificate_request < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/certificate_revocation_list.rb b/lib/puppet/application/certificate_revocation_list.rb new file mode 100644 index 000000000..60b9d97d6 --- /dev/null +++ b/lib/puppet/application/certificate_revocation_list.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Certificate_revocation_list < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/config.rb b/lib/puppet/application/config.rb new file mode 100644 index 000000000..a94441e7f --- /dev/null +++ b/lib/puppet/application/config.rb @@ -0,0 +1,4 @@ +require 'puppet/application/face_base' + +class Puppet::Application::Config < Puppet::Application::FaceBase +end diff --git a/lib/puppet/application/configurer.rb b/lib/puppet/application/configurer.rb new file mode 100644 index 000000000..6e86cd2d4 --- /dev/null +++ b/lib/puppet/application/configurer.rb @@ -0,0 +1,23 @@ +require 'puppet/application' +require 'puppet/face' + +class Puppet::Application::Configurer < Puppet::Application + should_parse_config + run_mode :agent + + option("--debug", "-d") + option("--verbose", "-v") + + def setup + if options[:debug] or options[:verbose] + Puppet::Util::Log.level = options[:debug] ? :debug : :info + end + + Puppet::Util::Log.newdestination(:console) + end + + def run_command + report = Puppet::Face[:configurer, '0.0.1'].synchronize(Puppet[:certname]) + Puppet::Face[:report, '0.0.1'].submit(report) + end +end diff --git a/lib/puppet/application/describe.rb b/lib/puppet/application/describe.rb index 79643159e..8ce20b652 100644 --- a/lib/puppet/application/describe.rb +++ b/lib/puppet/application/describe.rb @@ -228,8 +228,7 @@ David Lutterkort COPYRIGHT --------- -Copyright (c) 2005 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/doc.rb b/lib/puppet/application/doc.rb index 74811919e..a88f27c78 100644 --- a/lib/puppet/application/doc.rb +++ b/lib/puppet/application/doc.rb @@ -136,8 +136,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005-2007 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb new file mode 100644 index 000000000..2a048a532 --- /dev/null +++ b/lib/puppet/application/face_base.rb @@ -0,0 +1,158 @@ +require 'puppet/application' +require 'puppet/face' +require 'optparse' + +class Puppet::Application::FaceBase < 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 :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 + # 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 + Signal.trap(:INT) do + $stderr.puts "Cancelling Face" + exit(0) + end + end + + def parse_options + # We need to parse enough of the command line out early, to identify what + # the action is, so that we can obtain the full set of options to parse. + + # REVISIT: These should be configurable versions, through a global + # '--version' option, but we don't implement that yet... --daniel 2011-03-29 + @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym + @face = Puppet::Face[@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 + # 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 = @face.options.find do |name| + item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ + end + if option then + option = @face.get_option(option) + # If we have an inline argument, just carry on. We don't need to + # care about optional vs mandatory in that case because we do a real + # parse later, and that will totally take care of raising the error + # when we get there. --daniel 2011-04-04 + if option.takes_argument? and !item.index('=') 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 OptionParser::InvalidOption.new(item.sub(/=.*$/, '')) + end + else + action = @face.get_action(item.to_sym) + if action.nil? then + raise OptionParser::InvalidArgument.new("#{@face} does not have an #{item} action") + end + @action = action + end + end + + unless @action + raise OptionParser::MissingArgument.new("No action given on the command line") + end + + # Now we can interact with the default option code to build behaviour + # around the full set of options we now know we support. + @action.options.each do |option| + option = @action.get_option(option) # make it the object. + self.class.option(*option.optparse) # ...and make the CLI parse it. + end + + # ...and invoke our parent to parse all the command line options. + super + end + + def find_global_settings_argument(item) + Puppet.settings.each do |name, object| + object.optparse_args.each do |arg| + next unless arg =~ /^-/ + # sadly, we have to emulate some of optparse here... + pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ + pattern.match item and return object + end + end + 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 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 + # 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 = @face.send(@action.name, *arguments) + puts render(result) + end + exit(exit_code) + end +end diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb new file mode 100644 index 000000000..3dd3f0312 --- /dev/null +++ b/lib/puppet/application/faces.rb @@ -0,0 +1,88 @@ +require 'puppet/application' +require 'puppet/face' + +class Puppet::Application::Faces < Puppet::Application + + should_parse_config + run_mode :agent + + option("--debug", "-d") do |arg| + Puppet::Util::Log.level = :debug + end + + option("--help", "-h") do |arg| + puts "Usage: puppet faces [actions|terminuses] +Lists all available faces, and by default includes all available terminuses and actions. +" + end + + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end + + def list(*arguments) + if arguments.empty? + arguments = %w{terminuses actions} + end + faces.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 :name, :arguments + + def main + list(*arguments) + end + + def setup + Puppet::Util::Log.newdestination :console + + load_applications # Call this to load all of the apps + + @arguments = command_line.args + @arguments ||= [] + end + + def faces + Puppet::Face.faces + end + + def terminus_classes(indirection) + Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort + end + + def actions(indirection) + return [] unless face = Puppet::Face[indirection, '0.0.1'] + face.load_actions + return face.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/facts.rb b/lib/puppet/application/facts.rb new file mode 100644 index 000000000..d18b21ea7 --- /dev/null +++ b/lib/puppet/application/facts.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Facts < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/file.rb b/lib/puppet/application/file.rb new file mode 100644 index 000000000..32a81c7c6 --- /dev/null +++ b/lib/puppet/application/file.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::File < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/filebucket.rb b/lib/puppet/application/filebucket.rb index 063d97db8..6d59ae40b 100644 --- a/lib/puppet/application/filebucket.rb +++ b/lib/puppet/application/filebucket.rb @@ -108,8 +108,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/help.rb b/lib/puppet/application/help.rb new file mode 100644 index 000000000..0d7767632 --- /dev/null +++ b/lib/puppet/application/help.rb @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +require 'puppet/application/face_base' + +class Puppet::Application::Help < Puppet::Application::FaceBase + # Meh. Disable the default behaviour, which is to inspect the + # string and return that – not so helpful. --daniel 2011-04-11 + def render(result) result end +end diff --git a/lib/puppet/application/indirection_base.rb b/lib/puppet/application/indirection_base.rb new file mode 100644 index 000000000..580a0999a --- /dev/null +++ b/lib/puppet/application/indirection_base.rb @@ -0,0 +1,4 @@ +require 'puppet/application/face_base' + +class Puppet::Application::IndirectionBase < Puppet::Application::FaceBase +end diff --git a/lib/puppet/application/inspect.rb b/lib/puppet/application/inspect.rb index e448cb9e8..30865cfc1 100644 --- a/lib/puppet/application/inspect.rb +++ b/lib/puppet/application/inspect.rb @@ -68,9 +68,7 @@ Puppet Labs COPYRIGHT --------- - -Copyright (c) 2011 Puppet Labs, LLC -Licensed under the GNU General Public License version 2 +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/key.rb b/lib/puppet/application/key.rb new file mode 100644 index 000000000..57835b627 --- /dev/null +++ b/lib/puppet/application/key.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Key < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/kick.rb b/lib/puppet/application/kick.rb index da93c0182..536699442 100644 --- a/lib/puppet/application/kick.rb +++ b/lib/puppet/application/kick.rb @@ -172,8 +172,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index 78499a92a..a90829ae0 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -25,6 +25,11 @@ class Puppet::Application::Master < Puppet::Application end end + option("--parseonly") do + puts "--parseonly has been removed. Please use 'puppet parser validate <manifest>'" + exit 1 + end + def help <<-HELP @@ -105,8 +110,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end @@ -126,8 +130,6 @@ License def run_command if options[:node] compile - elsif Puppet[:parseonly] - parseonly else main end @@ -149,16 +151,6 @@ License exit(0) end - def parseonly - begin - Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types - rescue => detail - Puppet.err detail - exit 1 - end - exit(0) - end - def main require 'etc' require 'puppet/file_serving/content' diff --git a/lib/puppet/application/node.rb b/lib/puppet/application/node.rb new file mode 100644 index 000000000..38c1f8610 --- /dev/null +++ b/lib/puppet/application/node.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Node < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/parser.rb b/lib/puppet/application/parser.rb new file mode 100644 index 000000000..b6ec3c185 --- /dev/null +++ b/lib/puppet/application/parser.rb @@ -0,0 +1,5 @@ +require 'puppet/application/face_base' +require 'puppet/face' + +class Puppet::Application::Parser < Puppet::Application::FaceBase +end diff --git a/lib/puppet/application/queue.rb b/lib/puppet/application/queue.rb index de8aea32a..e56fde281 100644 --- a/lib/puppet/application/queue.rb +++ b/lib/puppet/application/queue.rb @@ -104,8 +104,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2009 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/report.rb b/lib/puppet/application/report.rb new file mode 100644 index 000000000..f7f961edd --- /dev/null +++ b/lib/puppet/application/report.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Report < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/resource.rb b/lib/puppet/application/resource.rb index 3995c285b..6ef87d68f 100644 --- a/lib/puppet/application/resource.rb +++ b/lib/puppet/application/resource.rb @@ -132,8 +132,7 @@ Luke Kanies COPYRIGHT --------- -Copyright (c) 2005-2007 Puppet Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end diff --git a/lib/puppet/application/resource_type.rb b/lib/puppet/application/resource_type.rb new file mode 100644 index 000000000..59594262c --- /dev/null +++ b/lib/puppet/application/resource_type.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Resource_type < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/application/status.rb b/lib/puppet/application/status.rb new file mode 100644 index 000000000..1c3ca054e --- /dev/null +++ b/lib/puppet/application/status.rb @@ -0,0 +1,4 @@ +require 'puppet/application/indirection_base' + +class Puppet::Application::Status < Puppet::Application::IndirectionBase +end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 989ef3f35..680762b94 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -116,6 +116,7 @@ module Puppet but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens."], + :route_file => ["$confdir/routes.yaml", "The YAML file containing indirector route configuration."], :node_terminus => ["plain", "Where to find information about nodes."], :catalog_terminus => ["compiler", "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."], @@ -353,6 +354,8 @@ module Puppet autosigns any key request, and is a very bad idea), false (which never autosigns any key request), and the path to a file, which uses that configuration file to determine which keys to sign."}, + :allow_duplicate_certs => [false, "Whether to allow a new certificate + request to overwrite an existing certificate."], :ca_days => ["", "How long a certificate should be valid. This parameter is deprecated, use ca_ttl instead"], :ca_ttl => ["5y", "The default TTL for new certificates; valid values @@ -415,7 +418,6 @@ module Puppet :desc => "Where the puppet master web server logs." }, :masterport => [8140, "Which port puppet master listens on."], - :parseonly => [false, "Just check the syntax of the manifests."], :node_name => ["cert", "How the puppetmaster determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. diff --git a/lib/puppet/face.rb b/lib/puppet/face.rb new file mode 100644 index 000000000..f73b2fc3b --- /dev/null +++ b/lib/puppet/face.rb @@ -0,0 +1,12 @@ +# The public name of this feature is 'face', 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::Face = Puppet::Interface diff --git a/lib/puppet/face/catalog.rb b/lib/puppet/face/catalog.rb new file mode 100644 index 000000000..0dcde3591 --- /dev/null +++ b/lib/puppet/face/catalog.rb @@ -0,0 +1,40 @@ +require 'puppet/face/indirector' + +Puppet::Face::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.indirection.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::Face[: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/face/catalog/select.rb b/lib/puppet/face/catalog/select.rb new file mode 100644 index 000000000..ba27117bc --- /dev/null +++ b/lib/puppet/face/catalog/select.rb @@ -0,0 +1,10 @@ +# Select and show a list of resources of a given type. +Puppet::Face.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/face/certificate.rb b/lib/puppet/face/certificate.rb new file mode 100644 index 000000000..77e80f099 --- /dev/null +++ b/lib/puppet/face/certificate.rb @@ -0,0 +1,46 @@ +require 'puppet/face/indirector' +require 'puppet/ssl/host' + +Puppet::Face::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/face/certificate_request.rb b/lib/puppet/face/certificate_request.rb new file mode 100644 index 000000000..1feba25ab --- /dev/null +++ b/lib/puppet/face/certificate_request.rb @@ -0,0 +1,4 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:certificate_request, '0.0.1') do +end diff --git a/lib/puppet/face/certificate_revocation_list.rb b/lib/puppet/face/certificate_revocation_list.rb new file mode 100644 index 000000000..6a75aa578 --- /dev/null +++ b/lib/puppet/face/certificate_revocation_list.rb @@ -0,0 +1,4 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:certificate_revocation_list, '0.0.1') do +end diff --git a/lib/puppet/face/config.rb b/lib/puppet/face/config.rb new file mode 100644 index 000000000..45cb6b156 --- /dev/null +++ b/lib/puppet/face/config.rb @@ -0,0 +1,12 @@ +require 'puppet/face' + +Puppet::Face.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/face/configurer.rb b/lib/puppet/face/configurer.rb new file mode 100644 index 000000000..74dfb854e --- /dev/null +++ b/lib/puppet/face/configurer.rb @@ -0,0 +1,12 @@ +require 'puppet/face' + +Puppet::Face.define(:configurer, '0.0.1') do + action(:synchronize) do + when_invoked do |certname, options| + facts = Puppet::Face[:facts, '0.0.1'].find(certname) + catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) + report + end + end +end diff --git a/lib/puppet/face/facts.rb b/lib/puppet/face/facts.rb new file mode 100644 index 000000000..8668b2531 --- /dev/null +++ b/lib/puppet/face/facts.rb @@ -0,0 +1,18 @@ +require 'puppet/face/indirector' +require 'puppet/node/facts' + +Puppet::Face::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/face/file.rb b/lib/puppet/face/file.rb new file mode 100644 index 000000000..1aa9462dd --- /dev/null +++ b/lib/puppet/face/file.rb @@ -0,0 +1,5 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:file, '0.0.1') do + set_indirection_name :file_bucket_file +end diff --git a/lib/puppet/face/help.rb b/lib/puppet/face/help.rb new file mode 100644 index 000000000..1c2da9e83 --- /dev/null +++ b/lib/puppet/face/help.rb @@ -0,0 +1,104 @@ +require 'puppet/face' +require 'puppet/util/command_line' +require 'pathname' +require 'erb' + +Puppet::Face.define(:help, '0.0.1') do + summary "Displays help about puppet subcommands" + + action(:help) do + summary "Display help about faces and their actions." + + option "--version VERSION" do + desc "Which version of the interface to show help for" + end + + when_invoked do |*args| + # Check our invocation, because we want varargs and can't do defaults + # yet. REVISIT: when we do option defaults, and positional options, we + # should rewrite this to use those. --daniel 2011-04-04 + options = args.pop + if options.nil? or args.length > 2 then + raise ArgumentError, "help only takes two (optional) arguments, a face name, and an action" + end + + version = :current + if options.has_key? :version then + if options[:version].to_s !~ /^current$/i then + version = options[:version] + else + if args.length == 0 then + raise ArgumentError, "version only makes sense when a face is given" + end + end + end + + # Name those parameters... + facename, actionname = args + + if facename then + if legacy_applications.include? facename then + actionname and raise ArgumentError, "Legacy subcommands don't take actions" + return Puppet::Application[facename].help + else + face = Puppet::Face[facename.to_sym, version] + actionname and action = face.get_action(actionname.to_sym) + end + end + + case args.length + when 0 then + template = erb 'global.erb' + when 1 then + face or fail ArgumentError, "Unable to load face #{facename}" + template = erb 'face.erb' + when 2 then + face or fail ArgumentError, "Unable to load face #{facename}" + action or fail ArgumentError, "Unable to load action #{actionname} from #{face}" + template = erb 'action.erb' + else + fail ArgumentError, "Too many arguments to help action" + end + + # Run the ERB template in our current binding, including all the local + # variables we established just above. --daniel 2011-04-11 + return template.result(binding) + end + end + + def erb(name) + template = (Pathname(__FILE__).dirname + "help" + name) + erb = ERB.new(template.read, nil, '%') + erb.filename = template.to_s + return erb + end + + def legacy_applications + # The list of applications, less those that are duplicated as a face. + Puppet::Util::CommandLine.available_subcommands.reject do |appname| + Puppet::Face.face? appname.to_sym, :current or + # ...this is a nasty way to exclude non-applications. :( + %w{face_base indirection_base}.include? appname + end.sort + end + + def horribly_extract_summary_from(appname) + begin + require "puppet/application/#{appname}" + help = Puppet::Application[appname].help.split("\n") + # Now we find the line with our summary, extract it, and return it. This + # depends on the implementation coincidence of how our pages are + # formatted. If we can't match the pattern we expect we return the empty + # string to ensure we don't blow up in the summary. --daniel 2011-04-11 + while line = help.shift do + if md = /^puppet-#{appname}\([^\)]+\) -- (.*)$/.match(line) then + return md[1] + end + end + rescue Exception + # Damn, but I hate this: we just ignore errors here, no matter what + # class they are. Meh. + end + return '' + end +end diff --git a/lib/puppet/face/help/action.erb b/lib/puppet/face/help/action.erb new file mode 100644 index 000000000..eaf131464 --- /dev/null +++ b/lib/puppet/face/help/action.erb @@ -0,0 +1,3 @@ +Use: puppet <%= face.name %> [options] <%= action.name %> [options] + +Summary: <%= action.summary %> diff --git a/lib/puppet/face/help/face.erb b/lib/puppet/face/help/face.erb new file mode 100644 index 000000000..efe5fd809 --- /dev/null +++ b/lib/puppet/face/help/face.erb @@ -0,0 +1,7 @@ +Use: puppet <%= face.name %> [options] <action> [options] + +Available actions: +% face.actions.each do |actionname| +% action = face.get_action(actionname) + <%= action.name.to_s.ljust(16) %> <%= action.summary %> +% end diff --git a/lib/puppet/face/help/global.erb b/lib/puppet/face/help/global.erb new file mode 100644 index 000000000..f4c761b2b --- /dev/null +++ b/lib/puppet/face/help/global.erb @@ -0,0 +1,20 @@ +puppet <subcommand> [options] <action> [options] + +Available subcommands, from Puppet Faces: +% Puppet::Face.faces.sort.each do |name| +% face = Puppet::Face[name, :current] + <%= face.name.to_s.ljust(16) %> <%= face.summary %> +% end + +% unless legacy_applications.empty? then # great victory when this is true! +Available applications, soon to be ported to Faces: +% legacy_applications.each do |appname| +% summary = horribly_extract_summary_from appname + <%= appname.to_s.ljust(16) %> <%= summary %> +% end +% end + +See 'puppet help <subcommand> <action>' for help on a specific subcommand action. +See 'puppet help <subcommand>' for help on a specific subcommand. +See 'puppet man <subcommand>' for the full man page. +Puppet v<%= Puppet::PUPPETVERSION %> diff --git a/lib/puppet/face/indirector.rb b/lib/puppet/face/indirector.rb new file mode 100644 index 000000000..f48611e4b --- /dev/null +++ b/lib/puppet/face/indirector.rb @@ -0,0 +1,94 @@ +require 'puppet' +require 'puppet/face' + +class Puppet::Face::Indirector < Puppet::Face + option "--terminus TERMINUS" do + desc "REVISIT: You can select a terminus, which has some bigger effect +that we should describe in this file somehow." + 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.last + 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 face. + def set_indirection_name(name) + @indirection_name = name + end + + # Return an indirection associated with a face, 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/face/key.rb b/lib/puppet/face/key.rb new file mode 100644 index 000000000..3a11ddb03 --- /dev/null +++ b/lib/puppet/face/key.rb @@ -0,0 +1,4 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:key, '0.0.1') do +end diff --git a/lib/puppet/face/node.rb b/lib/puppet/face/node.rb new file mode 100644 index 000000000..fd1a548d6 --- /dev/null +++ b/lib/puppet/face/node.rb @@ -0,0 +1,5 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:node, '0.0.1') do + set_default_format :yaml +end diff --git a/lib/puppet/face/parser.rb b/lib/puppet/face/parser.rb new file mode 100644 index 000000000..c44810b99 --- /dev/null +++ b/lib/puppet/face/parser.rb @@ -0,0 +1,17 @@ +require 'puppet/face' +require 'puppet/parser' + +Puppet::Face.define(:parser, '0.0.1') do + action :validate do + when_invoked do |*args| + args.pop + files = args + files << Puppet[:manifest] if files.empty? + files.each do |file| + Puppet[:manifest] = file + Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear + end + nil + end + end +end diff --git a/lib/puppet/face/report.rb b/lib/puppet/face/report.rb new file mode 100644 index 000000000..6e6f0b335 --- /dev/null +++ b/lib/puppet/face/report.rb @@ -0,0 +1,15 @@ +require 'puppet/face/indirector' + +Puppet::Face::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/face/resource.rb b/lib/puppet/face/resource.rb new file mode 100644 index 000000000..d162f728a --- /dev/null +++ b/lib/puppet/face/resource.rb @@ -0,0 +1,4 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:resource, '0.0.1') do +end diff --git a/lib/puppet/face/resource_type.rb b/lib/puppet/face/resource_type.rb new file mode 100644 index 000000000..0cdbd719f --- /dev/null +++ b/lib/puppet/face/resource_type.rb @@ -0,0 +1,4 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:resource_type, '0.0.1') do +end diff --git a/lib/puppet/face/status.rb b/lib/puppet/face/status.rb new file mode 100644 index 000000000..7085e7cd7 --- /dev/null +++ b/lib/puppet/face/status.rb @@ -0,0 +1,4 @@ +require 'puppet/face/indirector' + +Puppet::Face::Indirector.define(:status, '0.0.1') do +end diff --git a/lib/puppet/faces/help/action.erb b/lib/puppet/faces/help/action.erb new file mode 100644 index 000000000..eaf131464 --- /dev/null +++ b/lib/puppet/faces/help/action.erb @@ -0,0 +1,3 @@ +Use: puppet <%= face.name %> [options] <%= action.name %> [options] + +Summary: <%= action.summary %> diff --git a/lib/puppet/faces/help/face.erb b/lib/puppet/faces/help/face.erb new file mode 100644 index 000000000..efe5fd809 --- /dev/null +++ b/lib/puppet/faces/help/face.erb @@ -0,0 +1,7 @@ +Use: puppet <%= face.name %> [options] <action> [options] + +Available actions: +% face.actions.each do |actionname| +% action = face.get_action(actionname) + <%= action.name.to_s.ljust(16) %> <%= action.summary %> +% end diff --git a/lib/puppet/faces/help/global.erb b/lib/puppet/faces/help/global.erb new file mode 100644 index 000000000..e123367a2 --- /dev/null +++ b/lib/puppet/faces/help/global.erb @@ -0,0 +1,20 @@ +puppet <subcommand> [options] <action> [options] + +Available subcommands, from Puppet Faces: +% Puppet::Faces.faces.sort.each do |name| +% face = Puppet::Faces[name, :current] + <%= face.name.to_s.ljust(16) %> <%= face.summary %> +% end + +% unless legacy_applications.empty? then # great victory when this is true! +Available applications, soon to be ported to Faces: +% legacy_applications.each do |appname| +% summary = horribly_extract_summary_from appname + <%= appname.to_s.ljust(16) %> <%= summary %> +% end +% end + +See 'puppet help <subcommand> <action>' for help on a specific subcommand action. +See 'puppet help <subcommand>' for help on a specific subcommand. +See 'puppet man <subcommand>' for the full man page. +Puppet v<%= Puppet::PUPPETVERSION %> diff --git a/lib/puppet/feature/ssh.rb b/lib/puppet/feature/ssh.rb new file mode 100644 index 000000000..82fe19882 --- /dev/null +++ b/lib/puppet/feature/ssh.rb @@ -0,0 +1,4 @@ +require 'puppet/util/feature' + +Puppet.features.rubygems? +Puppet.features.add(:ssh, :libs => %{net/ssh}) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 9effc5cdd..7267ac7f3 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -12,6 +12,22 @@ module Puppet::Indirector require 'puppet/indirector/envelope' require 'puppet/network/format_handler' + def self.configure_routes(application_routes) + application_routes.each do |indirection_name, termini| + indirection_name = indirection_name.to_sym + terminus_name = termini["terminus"] + cache_name = termini["cache"] + + Puppet::Indirector::Terminus.terminus_classes(indirection_name) + + indirection = Puppet::Indirector::Indirection.instance(indirection_name) + raise "Indirection #{indirection_name} does not exist" unless indirection + + indirection.terminus_class = terminus_name if terminus_name + indirection.cache_class = cache_name if cache_name + end + end + # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets diff --git a/lib/puppet/indirector/certificate_request/ca.rb b/lib/puppet/indirector/certificate_request/ca.rb index f4c924fe1..5d76ee52a 100644 --- a/lib/puppet/indirector/certificate_request/ca.rb +++ b/lib/puppet/indirector/certificate_request/ca.rb @@ -7,6 +7,14 @@ class Puppet::SSL::CertificateRequest::Ca < Puppet::Indirector::SslFile store_in :csrdir def save(request) + if host = Puppet::SSL::Host.indirection.find(request.key) + if Puppet[:allow_duplicate_certs] + Puppet.notice "#{request.key} already has a #{host.state} certificate; new certificate will overwrite it" + else + raise "#{request.key} already has a #{host.state} certificate; ignoring certificate request" + end + end + result = super Puppet.notice "#{request.key} has a waiting certificate request" result diff --git a/lib/puppet/indirector/couch.rb b/lib/puppet/indirector/couch.rb index fae934fd8..243d33dd4 100644 --- a/lib/puppet/indirector/couch.rb +++ b/lib/puppet/indirector/couch.rb @@ -1,6 +1,3 @@ -raise "Couch terminus not supported without couchrest gem" unless Puppet.features.couchdb? - -require 'couchrest' class Puppet::Indirector::Couch < Puppet::Indirector::Terminus # The CouchRest database instance. One database instance per Puppet runtime @@ -13,6 +10,11 @@ class Puppet::Indirector::Couch < Puppet::Indirector::Terminus attributes_of get(request) end + def initialize(*args) + raise "Couch terminus not supported without couchrest gem" unless Puppet.features.couchdb? + super + end + # Create or update the couchdb document with the request's data hash. # def save(request) diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index e50dc68ae..0d3997221 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -72,7 +72,17 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus end def find(request) - return nil unless result = deserialize(network(request).get(indirection2uri(request), headers)) + uri, body = request_to_uri_and_body(request) + uri_with_query_string = "#{uri}?#{body}" + http_connection = network(request) + # WEBrick in Ruby 1.9.1 only supports up to 1024 character lines in an HTTP request + # http://redmine.ruby-lang.org/issues/show/3991 + response = if "GET #{uri_with_query_string} HTTP/1.1\r\n".length > 1024 + http_connection.post(uri, body, headers) + else + http_connection.get(uri_with_query_string, headers) + end + result = deserialize response result.name = request.key if result.respond_to?(:name=) result end diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb new file mode 100644 index 000000000..6570ebe46 --- /dev/null +++ b/lib/puppet/interface.rb @@ -0,0 +1,120 @@ +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/face") + 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 + + def [](name, version) + unless face = Puppet::Interface::FaceCollection[name, version] + if current = Puppet::Interface::FaceCollection[name, :current] + raise Puppet::Error, "Could not find version #{version} of #{current}" + else + raise Puppet::Error, "Could not find Puppet Face #{name.inspect}" + end + end + face + end + end + + attr_accessor :default_format + + def set_default_format(format) + self.default_format = format.to_sym + end + + attr_accessor :summary + def summary(value = nil) + @summary = value unless value.nil? + @summary + end + + attr_reader :name, :version + + 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/face/#{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::Face[#{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..db338e39e --- /dev/null +++ b/lib/puppet/interface/action.rb @@ -0,0 +1,129 @@ +# -*- 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 + + # This is not nice, but it is the easiest way to make us behave like the + # Ruby Method object rather than UnboundMethod. Duplication is vaguely + # annoying, but at least we are a shallow clone. --daniel 2011-04-12 + def __dup_and_rebind_to(to) + bound_version = self.dup + bound_version.instance_variable_set(:@face, to) + return bound_version + end + + attr_reader :name + def to_s() "#{@face}##{@name}" end + + attr_accessor :summary + + # 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..34bb3fa44 --- /dev/null +++ b/lib/puppet/interface/action_builder.rb @@ -0,0 +1,35 @@ +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 + + def summary(text) + @action.summary = text + end +end diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb new file mode 100644 index 000000000..d75697afa --- /dev/null +++ b/lib/puppet/interface/action_manager.rb @@ -0,0 +1,56 @@ +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) + found = superclass.get_action(name) + elsif self.class.respond_to?(:get_action) + found = self.class.get_action(name) + end + + if found then + # This is not the nicest way to make action equivalent to the Ruby + # Method object, rather than UnboundMethod, but it will do for now, + # and we only have to make this change in *one* place. --daniel 2011-04-12 + result = @actions[name.to_sym] = found.__dup_and_rebind_to(self) + 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..591471d4b --- /dev/null +++ b/lib/puppet/interface/face_collection.rb @@ -0,0 +1,131 @@ +# -*- 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 + Dir.glob("puppet/face/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| + iname = file.sub(/\.rb/, '') + begin + require iname + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" + end + end + end + 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) + + # Note: be careful not to accidentally create the top level key, either, + # because it will result in confusion when people try to enumerate the + # list of valid faces later. --daniel 2011-04-11 + return true if @faces.has_key?(name) and @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/face/#{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/face/#{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 + # + # Note: be careful not to accidentally create the top level key, either, + # because it will result in confusion when people try to enumerate the + # list of valid faces later. --daniel 2011-04-11 + return !! (@faces.has_key?(name) and @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..2240b3e4a --- /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.to_s.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 diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb index 5fe143979..61307f01e 100644 --- a/lib/puppet/network/http/api/v1.rb +++ b/lib/puppet/network/http/api/v1.rb @@ -8,6 +8,9 @@ module Puppet::Network::HTTP::API::V1 :plural => :search, :singular => :find }, + "POST" => { + :singular => :find, + }, "PUT" => { :singular => :save }, @@ -41,6 +44,11 @@ module Puppet::Network::HTTP::API::V1 "/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}#{request.query_string}" end + def request_to_uri_and_body(request) + indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s + ["/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}", request.query_string.sub(/^\?/,'')] + end + def indirection_method(http_method, indirection) raise ArgumentError, "No support for http method #{http_method}" unless METHOD_MAP[http_method] diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb index 9bd949a08..ce0401ad2 100644 --- a/lib/puppet/network/http_server/mongrel.rb +++ b/lib/puppet/network/http_server/mongrel.rb @@ -4,26 +4,6 @@ # # Copyright (c) 2006 Manuel Holtgrewe, 2007 Luke Kanies # -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - # This file is based heavily on a file retrieved from # http://ttt.ggnore.net/2006/11/15/xmlrpc-with-mongrel-and-ruby-off-rails/ diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index 77617e992..c8ebc9483 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -124,7 +124,7 @@ class Puppet::Parser::AST # not include syntactical constructs, like '$' and '{}'). def evaluate(scope) parsewrap do - if (var = scope.lookupvar(@value, false)) == :undefined + if (var = scope.lookupvar(@value, :file => file, :line => line)) == :undefined var = :undef end var @@ -141,7 +141,7 @@ class Puppet::Parser::AST def evaluate_container(scope) container = variable.respond_to?(:evaluate) ? variable.safeevaluate(scope) : variable - (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope.lookupvar(container) + (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope.lookupvar(container, :file => file, :line => line) end def evaluate_key(scope) diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb index 6de1860c8..b766311dd 100644 --- a/lib/puppet/parser/ast/vardef.rb +++ b/lib/puppet/parser/ast/vardef.rb @@ -20,7 +20,7 @@ class Puppet::Parser::AST name = @name.safeevaluate(scope) parsewrap do - scope.setvar(name,value, :file => @file, :line => @line, :append => @append) + scope.setvar(name,value, :file => file, :line => line, :append => @append) end end end diff --git a/lib/puppet/parser/functions/extlookup.rb b/lib/puppet/parser/functions/extlookup.rb index bc55410b9..5fbf26cec 100644 --- a/lib/puppet/parser/functions/extlookup.rb +++ b/lib/puppet/parser/functions/extlookup.rb @@ -91,14 +91,9 @@ This is for back compatibility to interpolate variables with %. % interpolation raise Puppet::ParseError, ("extlookup(): wrong number of arguments (#{args.length}; must be <= 3)") if args.length > 3 - extlookup_datadir = lookupvar('extlookup_datadir') - extlookup_precedence = Array.new + extlookup_datadir = undef_as('',lookupvar('::extlookup_datadir')) - extlookup_precedence = lookupvar('extlookup_precedence').collect do |var| - var.gsub(/%\{(.+?)\}/) do |capture| - lookupvar($1) - end - end + extlookup_precedence = undef_as([],lookupvar('::extlookup_precedence')).collect { |var| var.gsub(/%\{(.+?)\}/) { lookupvar("::#{$1}") } } datafiles = Array.new diff --git a/lib/puppet/parser/functions/fqdn_rand.rb b/lib/puppet/parser/functions/fqdn_rand.rb index 91157a148..93ab98bcd 100644 --- a/lib/puppet/parser/functions/fqdn_rand.rb +++ b/lib/puppet/parser/functions/fqdn_rand.rb @@ -7,6 +7,6 @@ Puppet::Parser::Functions::newfunction(:fqdn_rand, :type => :rvalue, :doc => $random_number_seed = fqdn_rand(30,30)") do |args| require 'digest/md5' max = args.shift - srand(Digest::MD5.hexdigest([lookupvar('fqdn'),args].join(':')).hex) + srand(Digest::MD5.hexdigest([lookupvar('::fqdn'),args].join(':')).hex) rand(max).to_s end diff --git a/lib/puppet/parser/functions/sha1.rb b/lib/puppet/parser/functions/sha1.rb index 10cc55cfe..1e7d5abe4 100644 --- a/lib/puppet/parser/functions/sha1.rb +++ b/lib/puppet/parser/functions/sha1.rb @@ -1,5 +1,5 @@ Puppet::Parser::Functions::newfunction(:sha1, :type => :rvalue, :doc => "Returns a SHA1 hash value from a provided string.") do |args| - require 'sha1' + require 'digest/sha1' Digest::SHA1.hexdigest(args[0]) end diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index 8339c51b7..d2bd06e94 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -156,7 +156,7 @@ resourceoverride: resourceref LBRACE anyparams endcomma RBRACE { virtualresource: at resource { type = val[0] - if (type == :exported and ! Puppet[:storeconfigs]) and ! Puppet[:parseonly] + if (type == :exported and ! Puppet[:storeconfigs]) Puppet.warning addcontext("You cannot collect without storeconfigs being set") end @@ -188,7 +188,7 @@ collection: classref collectrhand LBRACE anyparams endcomma RBRACE { else args[:form] = val[1] end - if args[:form] == :exported and ! Puppet[:storeconfigs] and ! Puppet[:parseonly] + if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end args[:override] = val[3] @@ -208,7 +208,7 @@ collection: classref collectrhand LBRACE anyparams endcomma RBRACE { else args[:form] = val[1] end - if args[:form] == :exported and ! Puppet[:storeconfigs] and ! Puppet[:parseonly] + if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index 300ddddd6..611398d14 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -1366,7 +1366,7 @@ module_eval(<<'.,.,', 'grammar.ra', 156) def _reduce_50(val, _values, result) type = val[0] - if (type == :exported and ! Puppet[:storeconfigs]) and ! Puppet[:parseonly] + if (type == :exported and ! Puppet[:storeconfigs]) Puppet.warning addcontext("You cannot collect without storeconfigs being set") end @@ -1411,7 +1411,7 @@ module_eval(<<'.,.,', 'grammar.ra', 178) else args[:form] = val[1] end - if args[:form] == :exported and ! Puppet[:storeconfigs] and ! Puppet[:parseonly] + if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end args[:override] = val[3] @@ -1436,7 +1436,7 @@ module_eval(<<'.,.,', 'grammar.ra', 197) else args[:form] = val[1] end - if args[:form] == :exported and ! Puppet[:storeconfigs] and ! Puppet[:parseonly] + if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index c007d4dbe..ace01bb4b 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -241,7 +241,7 @@ class Puppet::Parser::Resource < Puppet::Resource def add_backward_compatible_relationship_param(name) # Skip metaparams for which we get no value. - return unless val = scope.lookupvar(name.to_s, false) and val != :undefined + return unless val = scope.lookupvar(name.to_s) and val != :undefined # The default case: just set the value set_parameter(name, val) and return unless @parameters[name] diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 24f1d01f7..8de9d60b1 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -18,10 +18,10 @@ class Puppet::Parser::Scope include Enumerable include Puppet::Util::Errors - attr_accessor :level, :source, :resource + attr_accessor :source, :resource attr_accessor :base, :keyword attr_accessor :top, :translated, :compiler - attr_accessor :parent + attr_accessor :parent, :dynamic attr_reader :namespaces # thin wrapper around an ephemeral @@ -104,11 +104,6 @@ class Puppet::Parser::Scope compiler.environment end - # Are we the top scope? - def topscope? - @level == 1 - end - def find_hostclass(name) known_resource_types.find_hostclass(namespaces, name) end @@ -215,45 +210,41 @@ class Puppet::Parser::Scope find_definition(name) || find_hostclass(name) end - def lookup_qualified_var(name, usestring) - parts = name.split(/::/) - shortname = parts.pop - klassname = parts.join("::") - klass = find_hostclass(klassname) - unless klass - warning "Could not look up qualified variable '#{name}'; class #{klassname} could not be found" - return usestring ? "" : :undefined - end - unless kscope = class_scope(klass) - warning "Could not look up qualified variable '#{name}'; class #{klassname} has not been evaluated" - return usestring ? "" : :undefined - end - kscope.lookupvar(shortname, usestring) + def undef_as(x,v) + (v == :undefined) ? x : (v == :undef) ? x : v end - private :lookup_qualified_var + def qualified_scope(classname) + raise "class #{classname} could not be found" unless klass = find_hostclass(classname) + raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) + kscope + end + + private :qualified_scope - # Look up a variable. The simplest value search we do. Default to returning - # an empty string for missing values, but support returning a constant. - def lookupvar(name, usestring = true) + # Look up a variable. The simplest value search we do. + def lookupvar(name, options = {}) table = ephemeral?(name) ? @ephemeral.last : @symtable # If the variable is qualified, then find the specified scope and look the variable up there instead. - if name =~ /::/ - return lookup_qualified_var(name, usestring) - end - # We can't use "if table[name]" here because the value might be false - if ephemeral_include?(name) or table.include?(name) - if usestring and table[name] == :undef - return "" - else - return table[name] + if name =~ /^(.*)::(.+)$/ + begin + qualified_scope($1).lookupvar($2,options) + rescue RuntimeError => e + location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' + warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}" + :undefined end - elsif self.parent - return parent.lookupvar(name, usestring) - elsif usestring - return "" + elsif ephemeral_include?(name) or table.include?(name) + # We can't use "if table[name]" here because the value might be false + if options[:dynamic] and self != compiler.topscope + location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' + Puppet.deprecation_warning "Dynamic lookup of $#{name}#{location} will not be supported in future versions. Use a fully-qualified variable name or parameterized classes." + end + table[name] + elsif parent + parent.lookupvar(name,options.merge(:dynamic => (dynamic || options[:dynamic]))) else - return :undefined + :undefined end end @@ -323,8 +314,6 @@ class Puppet::Parser::Scope # to be reassigned. def setvar(name,value, options = {}) table = options[:ephemeral] ? @ephemeral.last : @symtable - #Puppet.debug "Setting %s to '%s' at level %s mode append %s" % - # [name.inspect,value,self.level, append] if table.include?(name) unless options[:append] error = Puppet::ParseError.new("Cannot reassign variable #{name}") @@ -340,7 +329,7 @@ class Puppet::Parser::Scope table[name] = value else # append case # lookup the value in the scope if it exists and insert the var - table[name] = lookupvar(name) + table[name] = undef_as('',lookupvar(name)) # concatenate if string, append if array, nothing for other types case value when Array @@ -354,65 +343,6 @@ class Puppet::Parser::Scope end end - # Return an interpolated string. - def strinterp(string, file = nil, line = nil) - # Most strings won't have variables in them. - ss = StringScanner.new(string) - out = "" - while not ss.eos? - if ss.scan(/^\$\{((\w*::)*\w+|[0-9]+)\}|^\$([0-9])|^\$((\w*::)*\w+)/) - # If it matches the backslash, then just retun the dollar sign. - if ss.matched == '\\$' - out << '$' - else # look the variable up - # make sure $0-$9 are lookupable only if ephemeral - var = ss[1] || ss[3] || ss[4] - if var and var =~ /^[0-9]+$/ and not ephemeral_include?(var) - next - end - out << lookupvar(var).to_s || "" - end - elsif ss.scan(/^\\(.)/) - # Puppet.debug("Got escape: pos:%d; m:%s" % [ss.pos, ss.matched]) - case ss[1] - when 'n' - out << "\n" - when 't' - out << "\t" - when 's' - out << " " - when '\\' - out << '\\' - when '$' - out << '$' - else - str = "Unrecognised escape sequence '#{ss.matched}'" - str += " in file #{file}" if file - str += " at line #{line}" if line - Puppet.warning str - out << ss.matched - end - elsif ss.scan(/^\$/) - out << '$' - elsif ss.scan(/^\\\n/) # an escaped carriage return - next - else - tmp = ss.scan(/[^\\$]+/) - # Puppet.debug("Got other: pos:%d; m:%s" % [ss.pos, tmp]) - unless tmp - error = Puppet::ParseError.new("Could not parse string #{string.inspect}") - {:file= => file, :line= => line}.each do |m,v| - error.send(m, v) if v - end - raise error - end - out << tmp - end - end - - out - end - # Return the tags associated with this scope. It's basically # just our parents' tags, plus our type. We don't cache this value # because our parent tags might change between calls. diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 6864aa1a9..180a03dc9 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -18,13 +18,14 @@ class Puppet::Parser::TemplateWrapper @__scope__ end + def script_line + # find which line in the template (if any) we were called from + caller.find { |l| l =~ /#{file}:/ }.first[/:(\d+):/,1] + end + # Should return true if a variable is defined, false if it is not def has_variable?(name) - if scope.lookupvar(name.to_s, false) != :undefined - true - else - false - end + scope.lookupvar(name.to_s, :file => file, :line => script_line) != :undefined end # Allow templates to access the defined classes @@ -55,15 +56,13 @@ class Puppet::Parser::TemplateWrapper # the missing_method definition here until we declare the syntax finally # dead. def method_missing(name, *args) - # We have to tell lookupvar to return :undefined to us when - # appropriate; otherwise it converts to "". - value = scope.lookupvar(name.to_s, false) + value = scope.lookupvar(name.to_s,:file => file,:line => script_line) if value != :undefined return value else # Just throw an error immediately, instead of searching for # other missingmethod things or whatever. - raise Puppet::ParseError, "Could not find value for '#{name}'" + raise Puppet::ParseError.new("Could not find value for '#{name}'",@file,script_line) end end @@ -103,6 +102,7 @@ class Puppet::Parser::TemplateWrapper result = nil benchmark(:debug, "Interpolated template #{template_source}") do template = ERB.new(self.string, 0, "-") + template.filename = file result = template.result(binding) end diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index 5488c6674..a16f54bd7 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -1,21 +1,17 @@ -#-- -# Copyright (C) 2008 Red Hat Inc. # -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. +# Copyright 2011 Bryan Kearney <bkearney@redhat.com> # -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# You should have received a copy of the GNU General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# http://www.apache.org/licenses/LICENSE-2.0 # -# Author: Bryan Kearney <bkearney@redhat.com> +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. require 'augeas' if Puppet.features.augeas? require 'strscan' diff --git a/lib/puppet/provider/cron/crontab.rb b/lib/puppet/provider/cron/crontab.rb index 8a347b331..a554363c8 100755 --- a/lib/puppet/provider/cron/crontab.rb +++ b/lib/puppet/provider/cron/crontab.rb @@ -11,13 +11,7 @@ tab = case Facter.value(:operatingsystem) - Puppet::Type.type(:cron).provide( - :crontab, - :parent => Puppet::Provider::ParsedFile, - :default_target => ENV["USER"] || "root", - - :filetype => tab -) do +Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFile, :default_target => ENV["USER"] || "root", :filetype => tab) do commands :crontab => "crontab" text_line :comment, :match => %r{^#}, :post_parse => proc { |record| diff --git a/lib/puppet/provider/group/directoryservice.rb b/lib/puppet/provider/group/directoryservice.rb index 97fee883d..e11284898 100644 --- a/lib/puppet/provider/group/directoryservice.rb +++ b/lib/puppet/provider/group/directoryservice.rb @@ -1,17 +1,3 @@ -# Created by Jeff McCune on 2007-07-22 -# Copyright (c) 2007. All rights reserved. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA - require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:group).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do diff --git a/lib/puppet/provider/group/groupadd.rb b/lib/puppet/provider/group/groupadd.rb index 82ed4c0c7..bcc08d9f7 100644 --- a/lib/puppet/provider/group/groupadd.rb +++ b/lib/puppet/provider/group/groupadd.rb @@ -9,6 +9,8 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe commands :add => "groupadd", :delete => "groupdel", :modify => "groupmod" + has_feature :system_groups + verify :gid, "GID must be an integer" do |value| value.is_a? Integer end @@ -21,6 +23,7 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe end end cmd << "-o" if @resource.allowdupe? + cmd << "-r" if @resource.system? cmd << @resource[:name] cmd diff --git a/lib/puppet/provider/interface/base.rb b/lib/puppet/provider/interface/base.rb new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/lib/puppet/provider/interface/base.rb diff --git a/lib/puppet/provider/interface/cisco.rb b/lib/puppet/provider/interface/cisco.rb new file mode 100644 index 000000000..f3bd202e9 --- /dev/null +++ b/lib/puppet/provider/interface/cisco.rb @@ -0,0 +1,33 @@ +require 'puppet/util/network_device/cisco/device' +require 'puppet/provider/network_device' + +Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::NetworkDevice do + + desc "Cisco switch/router provider for interface." + + mk_resource_methods + + def self.lookup(url, name) + interface = nil + network_gear = Puppet::Util::NetworkDevice::Cisco::Device.new(url) + network_gear.command do |ng| + interface = network_gear.interface(name) + end + interface + end + + def initialize(*args) + super + end + + def flush + device.command do |device| + device.new_interface(name).update(former_properties, properties) + end + super + end + + def device + @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url]) + end +end diff --git a/lib/puppet/provider/mcx/mcxcontent.rb b/lib/puppet/provider/mcx/mcxcontent.rb index 3ad437b53..0c0061278 100644 --- a/lib/puppet/provider/mcx/mcxcontent.rb +++ b/lib/puppet/provider/mcx/mcxcontent.rb @@ -1,22 +1,3 @@ -#-- -# Copyright (C) 2008 Jeffrey J McCune. - -# This program and entire repository is free software; you can -# redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software -# Foundation; either version 2 of the License, or any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# Author: Jeff McCune <mccune.jeff@gmail.com> - require 'tempfile' Puppet::Type.type(:mcx).provide :mcxcontent, :parent => Puppet::Provider do diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index 2e3480985..c1139a679 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -1,17 +1,3 @@ -# Created by Jeff McCune on 2007-07-22 -# Copyright (c) 2007. All rights reserved. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA - require 'puppet' require 'puppet/provider/nameservice' require 'facter/util/plist' diff --git a/lib/puppet/provider/network_device.rb b/lib/puppet/provider/network_device.rb new file mode 100644 index 000000000..58865fddc --- /dev/null +++ b/lib/puppet/provider/network_device.rb @@ -0,0 +1,59 @@ + +# This is the base class of all prefetched network device provider +class Puppet::Provider::NetworkDevice < Puppet::Provider + + def self.lookup(url, name) + raise "This provider doesn't implement the necessary lookup method" + end + + def self.prefetch(resources) + resources.each do |name, resource| + if result = lookup(resource[:device_url], name) + result[:ensure] = :present + resource.provider = new(result) + else + resource.provider = new(:ensure => :absent) + end + end + end + + def exists? + @property_hash[:ensure] != :absent + end + + def initialize(*args) + super + + # Make a duplicate, so that we have a copy for comparison + # at the end. + @properties = @property_hash.dup + end + + def create + @property_hash[:ensure] = :present + self.class.resource_type.validproperties.each do |property| + if val = resource.should(property) + @property_hash[property] = val + end + end + end + + def destroy + @property_hash[:ensure] = :absent + end + + def flush + @property_hash.clear + end + + def self.instances + end + + def former_properties + @properties.dup + end + + def properties + @property_hash.dup + end +end
\ No newline at end of file diff --git a/lib/puppet/provider/package/darwinport.rb b/lib/puppet/provider/package/darwinport.rb deleted file mode 100755 index c5f9ba28f..000000000 --- a/lib/puppet/provider/package/darwinport.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'puppet/provider/package' - -Puppet::Type.type(:package).provide :darwinport, :parent => Puppet::Provider::Package do - desc "Package management using DarwinPorts on OS X." - - confine :operatingsystem => :darwin - commands :port => "/opt/local/bin/port" - - def self.eachpkgashash - # list out all of the packages - open("| #{command(:port)} list installed") { |process| - regex = %r{(\S+)\s+@(\S+)\s+(\S+)} - fields = [:name, :ensure, :location] - hash = {} - - # now turn each returned line into a package object - process.each { |line| - hash.clear - - if match = regex.match(line) - fields.zip(match.captures) { |field,value| - hash[field] = value - } - - hash.delete :location - hash[:provider] = self.name - yield hash.dup - else - raise Puppet::DevError, - "Failed to match dpkg line #{line}" - end - } - } - end - - def self.instances - packages = [] - - eachpkgashash do |hash| - packages << new(hash) - end - - packages - end - - def install - should = @resource.should(:ensure) - - # Seems like you can always say 'upgrade' - output = port "upgrade", @resource[:name] - if output =~ /^Error: No port/ - raise Puppet::ExecutionFailure, "Could not find package #{@resource[:name]}" - end - end - - def query - version = nil - self.class.eachpkgashash do |hash| - return hash if hash[:name] == @resource[:name] - end - - nil - end - - def latest - info = port :search, "^#{@resource[:name]}$" - - if $CHILD_STATUS != 0 or info =~ /^Error/ - return nil - end - - ary = info.split(/\s+/) - version = ary[2].sub(/^@/, '') - - version - end - - def uninstall - port :uninstall, @resource[:name] - end - - def update - install - end -end - diff --git a/lib/puppet/provider/package/macports.rb b/lib/puppet/provider/package/macports.rb new file mode 100755 index 000000000..c43eb72f3 --- /dev/null +++ b/lib/puppet/provider/package/macports.rb @@ -0,0 +1,106 @@ +require 'puppet/provider/package' + +Puppet::Type.type(:package).provide :macports, :parent => Puppet::Provider::Package do + desc "Package management using MacPorts on OS X. + + Supports MacPorts versions and revisions, but not variants. + Variant preferences may be specified using the MacPorts variants.conf file + http://guide.macports.org/chunked/internals.configuration-files.html#internals.configuration-files.variants-conf + + When specifying a version in the Puppet DSL, only specify the version, not the revision + Revisions are only used internally for ensuring the latest version/revision of a port. + " + + confine :operatingsystem => :darwin + commands :port => "/opt/local/bin/port" + + has_feature :installable + has_feature :uninstallable + has_feature :upgradeable + has_feature :versionable + + + def self.parse_installed_query_line(line) + regex = /(\S+)\s+@(\S+)_(\S+)\s+\(active\)/ + fields = [:name, :ensure, :revision] + hash_from_line(line, regex, fields) + end + + def self.parse_info_query_line(line) + regex = /(\S+)\s+(\S+)/ + fields = [:version, :revision] + hash_from_line(line, regex, fields) + end + + def self.hash_from_line(line, regex, fields) + hash = {} + if match = regex.match(line) + fields.zip(match.captures) { |field, value| + hash[field] = value + } + hash[:provider] = self.name + return hash + end + nil + end + + def self.instances + packages = [] + port("-q", :installed).each do |line| + if hash = parse_installed_query_line(line) + packages << new(hash) + end + end + packages + end + + def install + should = @resource.should(:ensure) + if [:latest, :installed, :present].include?(should) + output = port("-q", :install, @resource[:name]) + else + output = port("-q", :install, @resource[:name], "@#{should}") + end + # MacPorts now correctly exits non-zero with appropriate errors in + # situations where a port cannot be found or installed. + end + + def query + return self.class.parse_installed_query_line(port("-q", :installed, @resource[:name])) + end + + def latest + # We need both the version and the revision to be confident + # we've got the latest revision of a specific version + # Note we're still not doing anything with variants here. + info_line = port("-q", :info, "--line", "--version", "--revision", @resource[:name]) + return nil if info_line == "" + + if newest = self.class.parse_info_query_line(info_line) + current = query + # We're doing some fiddling behind the scenes here to cope with updated revisions. + # If we're already at the latest version/revision, then just return the version + # so the current and desired values match. Otherwise return version and revision + # to trigger an upgrade to the latest revision. + if newest[:version] == current[:ensure] and newest[:revision] == current[:revision] + return current[:ensure] + else + return "#{newest[:version]}_#{newest[:revision]}" + end + end + nil + end + + def uninstall + port("-q", :uninstall, @resource[:name]) + end + + def update + if query[:name] == @resource[:name] # 'port upgrade' cannot install new ports + port("-q", :upgrade, @resource[:name]) + else + install + end + end +end + diff --git a/lib/puppet/provider/package/pkgdmg.rb b/lib/puppet/provider/package/pkgdmg.rb index 39e377d66..662d05c8f 100644 --- a/lib/puppet/provider/package/pkgdmg.rb +++ b/lib/puppet/provider/package/pkgdmg.rb @@ -1,21 +1,4 @@ # -# pkgdmg.rb -# -# Install Installer.app packages wrapped up inside a DMG image file. -# -# Copyright (C) 2007 Jeff McCune Jeff McCune <jeff@northstarlabs.net> -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -# # Motivation: DMG files provide a true HFS file system # and are easier to manage and .pkg bundles. # diff --git a/lib/puppet/provider/user/directoryservice.rb b/lib/puppet/provider/user/directoryservice.rb index 4b62a6ae7..a2c561039 100644 --- a/lib/puppet/provider/user/directoryservice.rb +++ b/lib/puppet/provider/user/directoryservice.rb @@ -1,17 +1,3 @@ -# Created by Jeff McCune on 2007-07-22 -# Copyright (c) 2007. All rights reserved. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA - require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:user).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb new file mode 100644 index 000000000..46e172c73 --- /dev/null +++ b/lib/puppet/provider/vlan/cisco.rb @@ -0,0 +1,34 @@ +require 'puppet/util/network_device/cisco/device' +require 'puppet/provider/network_device' + +Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::NetworkDevice do + + desc "Cisco switch/router provider for vlans." + + mk_resource_methods + + def self.lookup(url, id) + vlans = {} + device = Puppet::Util::NetworkDevice::Cisco::Device.new(url) + device.command do |d| + vlans = d.parse_vlans || {} + end + vlans[id] + end + + def initialize(*args) + super + end + + # Clear out the cached values. + def flush + device.command do |device| + device.update_vlan(resource[:name], former_properties, properties) + end + super + end + + def device + @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url]) + end +end diff --git a/lib/puppet/rails/fact_name.rb b/lib/puppet/rails/fact_name.rb index 4273399e5..08301d9f0 100644 --- a/lib/puppet/rails/fact_name.rb +++ b/lib/puppet/rails/fact_name.rb @@ -1,3 +1,4 @@ +require 'puppet/rails' require 'puppet/rails/fact_value' class Puppet::Rails::FactName < ActiveRecord::Base diff --git a/lib/puppet/rails/inventory_node.rb b/lib/puppet/rails/inventory_node.rb index 52f8621a4..da7e61040 100644 --- a/lib/puppet/rails/inventory_node.rb +++ b/lib/puppet/rails/inventory_node.rb @@ -3,6 +3,11 @@ require 'puppet/rails/inventory_fact' class Puppet::Rails::InventoryNode < ::ActiveRecord::Base has_many :facts, :class_name => "Puppet::Rails::InventoryFact", :foreign_key => :node_id, :dependent => :delete_all + if Puppet::Util.activerecord_version >= 3.0 + # Prevents "DEPRECATION WARNING: Base.named_scope has been deprecated, please use Base.scope instead" + ActiveRecord::NamedScope::ClassMethods.module_eval { alias :named_scope :scope } + end + named_scope :has_fact_with_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value = ?", name, value], diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb index 48d8c1f48..f8d820b77 100644 --- a/lib/puppet/resource/type.rb +++ b/lib/puppet/resource/type.rb @@ -62,13 +62,11 @@ class Puppet::Resource::Type # Now evaluate the code associated with this class or definition. def evaluate_code(resource) - scope = resource.scope - if tmp = evaluate_parent_type(resource) - scope = tmp - end + static_parent = evaluate_parent_type(resource) + scope = static_parent || resource.scope - scope = subscope(scope, resource) unless resource.title == :main + scope = scope.newscope(:namespace => namespace, :source => self, :resource => resource, :dynamic => !static_parent) unless resource.title == :main scope.compiler.add_class(name) unless definition? set_resource_parameters(resource, scope) @@ -263,11 +261,6 @@ class Puppet::Resource::Type end - # Create a new subscope in which to evaluate our code. - def subscope(scope, resource) - scope.newscope :resource => resource, :namespace => self.namespace, :source => self - end - # Check whether a given argument is valid. def valid_parameter?(param) param = param.to_s diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb index 9fe7cdd06..89b0a16ed 100644 --- a/lib/puppet/resource/type_collection.rb +++ b/lib/puppet/resource/type_collection.rb @@ -6,6 +6,7 @@ class Puppet::Resource::TypeCollection @hostclasses.clear @definitions.clear @nodes.clear + @watched_files.clear end def initialize(env) diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 0533273d9..d7845fbc9 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -4,7 +4,7 @@ require 'puppet' require 'puppet/util/tagging' require 'puppet/application' -require 'sha1' +require 'digest/sha1' class Puppet::Transaction require 'puppet/transaction/event' @@ -255,13 +255,13 @@ class Puppet::Transaction # We want to monitor changes in the relationship graph of our # catalog but this is complicated by the fact that the catalog - # both is_a graph and has_a graph, by the fact that changes to + # both is_a graph and has_a graph, by the fact that changes to # the structure of the object can have adverse serialization # effects, by threading issues, by order-of-initialization issues, - # etc. + # etc. # # Since the proper lifetime/scope of the monitoring is a transaction - # and the transaction is already commiting a mild law-of-demeter + # and the transaction is already commiting a mild law-of-demeter # transgression, we cut the Gordian knot here by simply wrapping the # transaction's view of the resource graph to capture and maintain # the information we need. Nothing outside the transaction needs diff --git a/lib/puppet/transaction/event_manager.rb b/lib/puppet/transaction/event_manager.rb index a21bbf892..f5da870ed 100644 --- a/lib/puppet/transaction/event_manager.rb +++ b/lib/puppet/transaction/event_manager.rb @@ -49,7 +49,7 @@ class Puppet::Transaction::EventManager # since eval_generated children can't have direct relationships. received = (event.name != :restarted) relationship_graph.matching_edges(event, resource).each do |edge| - received ||= true unless edge.target.is_a?(Puppet::Type::Whit) + received ||= true unless edge.target.is_a?(Puppet::Type.type(:whit)) next unless method = edge.callback next unless edge.target.respond_to?(method) diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index 16fee42ae..652b3874a 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -53,7 +53,13 @@ class Puppet::Transaction::Report end end + def prune_internal_data + resource_statuses.delete_if {|name,res| res.resource_type == 'Whit'} + end + def finalize_report + prune_internal_data + resource_metrics = add_metric(:resources, calculate_resource_metrics) add_metric(:time, calculate_time_metrics) change_metric = calculate_change_metric diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 5ecc430d4..c0e5d390b 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -1901,4 +1901,4 @@ end require 'puppet/provider' # Always load these types. -require 'puppet/type/component' +Puppet::Type.type(:component) diff --git a/lib/puppet/type/augeas.rb b/lib/puppet/type/augeas.rb index a8fb1f15f..f4d3c43e1 100644 --- a/lib/puppet/type/augeas.rb +++ b/lib/puppet/type/augeas.rb @@ -1,21 +1,17 @@ -#-- -# Copyright (C) 2008 Red Hat Inc. # -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. +# Copyright 2011 Bryan Kearney <bkearney@redhat.com> # -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# You should have received a copy of the GNU General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# http://www.apache.org/licenses/LICENSE-2.0 # -# Author: Bryan Kearney <bkearney@redhat.com> +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. Puppet::Type.newtype(:augeas) do include Puppet::Util diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index 4f6ea733c..5083ca556 100755 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb @@ -395,7 +395,7 @@ Puppet::Type.newtype(:cron) do unless ret case name when :command - devfail "No command, somehow" + devfail "No command, somehow" unless @parameters[:ensure].value == :absent when :special # nothing else diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb index d3b3a48eb..76c646baf 100755 --- a/lib/puppet/type/file/source.rb +++ b/lib/puppet/type/file/source.rb @@ -114,6 +114,7 @@ module Puppet param_name = (metadata_method == :checksum) ? :content : metadata_method next if metadata_method == :owner and !Puppet.features.root? next if metadata_method == :checksum and metadata.ftype == "directory" + next if metadata_method == :checksum and metadata.ftype == "link" and metadata.links == :manage if resource[param_name].nil? or resource[param_name] == :absent resource[param_name] = metadata.send(metadata_method) diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index 2573633f9..b534ef275 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -1,4 +1,3 @@ - require 'etc' require 'facter' require 'puppet/property/keyvalue' @@ -19,6 +18,9 @@ module Puppet feature :manages_aix_lam, "The provider can manage AIX Loadable Authentication Module (LAM) system." + feature :system_groups, + "The provider allows you to create system groups with lower GIDs." + ensurable do desc "Create or remove the group." @@ -112,7 +114,7 @@ module Puppet def membership :attribute_membership end - + def delimiter " " end @@ -132,5 +134,12 @@ module Puppet defaultto :minimum end + newparam(:system, :boolean => true) do + desc "Whether the group is a system group with lower GID." + + newvalues(:true, :false) + + defaultto false + end end end diff --git a/lib/puppet/type/interface.rb b/lib/puppet/type/interface.rb new file mode 100644 index 000000000..7560a0552 --- /dev/null +++ b/lib/puppet/type/interface.rb @@ -0,0 +1,107 @@ +# +# Manages an interface on a given router or switch +# + +require 'puppet/util/network_device/ipcalc' + +Puppet::Type.newtype(:interface) do + + @doc = "This represents a router or switch interface. It is possible to manage + interface mode (access or trunking, native vlan and encapsulation), + switchport characteristics (speed, duplex)." + + ensurable do + defaultvalues + + aliasvalue :shutdown, :absent + aliasvalue :no_shutdown, :present + + defaultto { :no_shutdown } + end + + newparam(:name) do + desc "Interface name" + end + + newparam(:device_url) do + desc "Url to connect to a router or switch." + end + + newproperty(:description) do + desc "Interface description." + + defaultto { @resource[:name] } + end + + newproperty(:speed) do + desc "Interface speed." + newvalues(:auto, /^\d+/) + end + + newproperty(:duplex) do + desc "Interface duplex." + newvalues(:auto, :full, :half) + end + + newproperty(:native_vlan) do + desc "Interface native vlan (for access mode only)." + newvalues(/^\d+/) + end + + newproperty(:encapsulation) do + desc "Interface switchport encapsulation." + newvalues(:none, :dot1q, :isl ) + end + + newproperty(:mode) do + desc "Interface switchport mode." + newvalues(:access, :trunk) + end + + newproperty(:allowed_trunk_vlans) do + desc "Allowed list of Vlans that this trunk can forward." + newvalues(:all, /./) + end + + newproperty(:etherchannel) do + desc "Channel group this interface is part of." + newvalues(/^\d+/) + end + + newproperty(:ipaddress, :array_matching => :all) do + include Puppet::Util::NetworkDevice::IPCalc + + desc "IP Address of this interface (it might not be possible to set an interface IP address + it depends on the interface type and device type). + Valid format of ip addresses are: + * IPV4, like 127.0.0.1 + * IPV4/prefixlength like 127.0.1.1/24 + * IPV6/prefixlength like FE80::21A:2FFF:FE30:ECF0/128 + * an optional suffix for IPV6 addresses from this list: eui-64, link-local + It is also possible to use an array of values. + " + + validate do |values| + values = [values] unless values.is_a?(Array) + values.each do |value| + self.fail "Invalid interface ip address" unless parse(value.gsub(/\s*(eui-64|link-local)\s*$/,'')) + end + end + + munge do |value| + option = value =~ /eui-64|link-local/i ? value.gsub(/^.*?\s*(eui-64|link-local)\s*$/,'\1') : nil + [parse(value.gsub(/\s*(eui-64|link-local)\s*$/,'')), option].flatten + end + + def value_to_s(value) + value = [value] unless value.is_a?(Array) + value.map{ |v| "#{v[1].to_s}/#{v[0]} #{v[2]}"}.join(",") + end + + def change_to_s(currentvalue, newvalue) + currentvalue = value_to_s(currentvalue) if currentvalue != :absent + newvalue = value_to_s(newvalue) + super(currentvalue, newvalue) + end + end +end diff --git a/lib/puppet/type/mcx.rb b/lib/puppet/type/mcx.rb index 07c9348dd..d0306ca46 100644 --- a/lib/puppet/type/mcx.rb +++ b/lib/puppet/type/mcx.rb @@ -1,22 +1,3 @@ -#-- -# Copyright (C) 2008 Jeffrey J McCune. - -# This program and entire repository is free software; you can -# redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software -# Foundation; either version 2 of the License, or any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# Author: Jeff McCune <mccune.jeff@gmail.com> - Puppet::Type.newtype(:mcx) do @doc = "MCX object management using DirectoryService on OS X. @@ -81,9 +62,9 @@ MCX settings refer to, the MCX resource will autorequire that user, group, or co newproperty(:content, :required_features => :manages_content) do desc "The XML Plist. The value of MCXSettings in DirectoryService. This is the standard output from the system command: - + dscl localhost -mcxexport /Local/Default/<ds_type>/ds_name - + Note that `ds_type` is capitalized and plural in the dscl command." end diff --git a/lib/puppet/type/router.rb b/lib/puppet/type/router.rb new file mode 100644 index 000000000..648389d39 --- /dev/null +++ b/lib/puppet/type/router.rb @@ -0,0 +1,14 @@ +# +# Manage a router abstraction +# + +module Puppet + newtype(:router) do + @doc = "Manages connected router." + + newparam(:url) do + desc "An URL to access the router of the form (ssh|telnet)://user:pass:enable@host/." + isnamevar + end + end +end diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index 3ef044932..5a2c69b87 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -8,19 +8,24 @@ module Puppet newtype(:service) do @doc = "Manage running services. Service support unfortunately varies - widely by platform -- some platforms have very little if any + widely by platform --- some platforms have very little if any concept of a running service, and some have a very codified and powerful concept. Puppet's service support will generally be able - to make up for any inherent shortcomings (e.g., if there is no + to do the right thing regardless (e.g., if there is no 'status' command, then Puppet will look in the process table for a command matching the service name), but the more information you - can provide the better behaviour you will get. Or, you can just - use a platform that has very good service support. + can provide, the better behaviour you will get. In particular, any + virtual services that don't have a predictable entry in the process table + (for example, `network` on Red Hat/CentOS systems) will manifest odd + behavior on restarts if you don't specify `hasstatus` or a `status` + command. Note that if a `service` receives an event from another resource, the service will get restarted. The actual command to restart the - service depends on the platform. You can provide a special command - for restarting with the `restart` attribute." + service depends on the platform. You can provide an explicit command + for restarting with the `restart` attribute, or use the init script's + restart command with the `hasrestart` attribute; if you do neither, + the service's stop and start commands will be used." feature :refreshable, "The provider can restart the service.", :methods => [:restart] @@ -93,11 +98,14 @@ module Puppet that a large number of init scripts on different platforms do not support any kind of status command; thus, you must specify manually whether the service you are running has such a - command (or you can specify a specific command using the - `status` parameter). - - If you do not specify anything, then the service name will be - looked for in the process table." + command. Alternately, you can provide a specific command using the + `status` attribute. + + If you specify neither of these, then Puppet will look for the + service name in the process table. Be aware that 'virtual' init + scripts such as networking will respond poorly to refresh events + (via notify and subscribe relationships) if you don't override + this default behavior." newvalues(:true, :false) diff --git a/lib/puppet/type/vlan.rb b/lib/puppet/type/vlan.rb new file mode 100644 index 000000000..6708ea4f5 --- /dev/null +++ b/lib/puppet/type/vlan.rb @@ -0,0 +1,24 @@ +# +# Manages a Vlan on a given router or switch +# + +Puppet::Type.newtype(:vlan) do + @doc = "This represents a router or switch vlan." + + ensurable + + newparam(:name) do + desc "Vlan id. It must be a number" + isnamevar + + newvalues(/^\d+/) + end + + newproperty(:description) do + desc "Vlan name" + end + + newparam(:device_url) do + desc "Url to connect to a router or switch." + end +end
\ No newline at end of file diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index 52b5f81ef..a884b8658 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -17,12 +17,12 @@ module Puppet 'master' => 'puppetmasterd' ) - def initialize( zero = $0, argv = ARGV, stdin = STDIN ) + def initialize(zero = $0, argv = ARGV, stdin = STDIN) @zero = zero @argv = argv.dup @stdin = stdin - @subcommand_name, @args = subcommand_and_args( @zero, @argv, @stdin ) + @subcommand_name, @args = subcommand_and_args(@zero, @argv, @stdin) Puppet::Plugins.on_commandline_initialization(:command_line_object => self) end @@ -33,19 +33,20 @@ module Puppet File.join('puppet', 'application') end - def available_subcommands - absolute_appdirs = $LOAD_PATH.collect do |x| + def self.available_subcommands + absolute_appdirs = $LOAD_PATH.collect do |x| File.join(x,'puppet','application') end.select{ |x| File.directory?(x) } absolute_appdirs.inject([]) do |commands, dir| commands + Dir[File.join(dir, '*.rb')].map{|fn| File.basename(fn, '.rb')} end.uniq end - - def usage_message - usage = "Usage: puppet command <space separated arguments>" - available = "Available commands are: #{available_subcommands.sort.join(', ')}" - [usage, available].join("\n") + # available_subcommands was previously an instance method, not a class + # method, and we have an unknown number of user-implemented applications + # that depend on that behaviour. Forwarding allows us to preserve a + # backward compatible API. --daniel 2011-04-11 + def available_subcommands + self.class.available_subcommands end def require_application(application) @@ -53,15 +54,24 @@ module Puppet end def execute - if subcommand_name.nil? - puts usage_message - elsif available_subcommands.include?(subcommand_name) #subcommand + if subcommand_name and available_subcommands.include?(subcommand_name) then require_application subcommand_name app = Puppet::Application.find(subcommand_name).new(self) Puppet::Plugins.on_application_initialization(:appliation_object => self) app.run + elsif execute_external_subcommand then + # Logically, we shouldn't get here, but we do, so whatever. We just + # return to the caller. How strange we are. --daniel 2011-04-11 else - abort "Error: Unknown command #{subcommand_name}.\n#{usage_message}" unless execute_external_subcommand + unless subcommand_name.nil? then + puts "Error: Unknown Puppet subcommand #{subcommand_name}.\n" + end + + # Doing this at the top of the file is natural, but causes puppet.rb + # to load too early, which causes things to break. This is a nasty + # thing, found in #7065. --daniel 2011-04-11 + require 'puppet/face' + puts Puppet::Face[:help, :current].help end end @@ -69,10 +79,10 @@ module Puppet external_command = "puppet-#{subcommand_name}" require 'puppet/util' - path_to_subcommand = Puppet::Util.which( external_command ) + path_to_subcommand = Puppet::Util.which(external_command) return false unless path_to_subcommand - system( path_to_subcommand, *args ) + system(path_to_subcommand, *args) true end @@ -82,7 +92,7 @@ module Puppet private - def subcommand_and_args( zero, argv, stdin ) + def subcommand_and_args(zero, argv, stdin) zero = File.basename(zero, '.rb') if zero == 'puppet' diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb index bc52b17f0..4e76ae414 100644 --- a/lib/puppet/util/logging.rb +++ b/lib/puppet/util/logging.rb @@ -15,6 +15,17 @@ module Puppet::Util::Logging end end + def deprecation_warning(message) + $deprecation_warnings ||= Hash.new(0) + if $deprecation_warnings.length < 100 and ($deprecation_warnings[message] += 1) == 1 + warning message + end + end + + def clear_deprecation_warnings + $deprecation_warnings.clear if $deprecation_warnings + end + private def is_resource? diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb index a93c66b07..10a268409 100644 --- a/lib/puppet/util/monkey_patches.rb +++ b/lib/puppet/util/monkey_patches.rb @@ -90,3 +90,17 @@ if RUBY_VERSION == '1.8.1' || RUBY_VERSION == '1.8.2' r } end + +class Array + # Ruby < 1.8.7 doesn't have this method but we use it in tests + def combination(num) + return [] if num < 0 || num > size + return [[]] if num == 0 + return map{|e| [e] } if num == 1 + tmp = self.dup + self[0, size - (num - 1)].inject([]) do |ret, e| + tmp.shift + ret += tmp.combination(num - 1).map{|a| a.unshift(e) } + end + end unless method_defined? :combination +end diff --git a/lib/puppet/util/network_device.rb b/lib/puppet/util/network_device.rb new file mode 100644 index 000000000..bca66016b --- /dev/null +++ b/lib/puppet/util/network_device.rb @@ -0,0 +1,2 @@ +module Puppet::Util::NetworkDevice +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/base.rb b/lib/puppet/util/network_device/base.rb new file mode 100644 index 000000000..ff96c8693 --- /dev/null +++ b/lib/puppet/util/network_device/base.rb @@ -0,0 +1,29 @@ +require 'puppet/util/autoload' +require 'uri' +require 'puppet/util/network_device/transport' +require 'puppet/util/network_device/transport/base' + +module Puppet::Util::NetworkDevice + class Base + + attr_accessor :url, :transport + + def initialize(url) + @url = URI.parse(url) + + @autoloader = Puppet::Util::Autoload.new( + self, + "puppet/util/network_device/transport", + :wrap => false + ) + + if @autoloader.load(@url.scheme) + @transport = Puppet::Util::NetworkDevice::Transport.const_get(@url.scheme.capitalize).new + @transport.host = @url.host + @transport.port = @url.port || case @url.scheme ; when "ssh" ; 22 ; when "telnet" ; 23 ; end + @transport.user = @url.user + @transport.password = @url.password + end + end + end +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/cisco.rb b/lib/puppet/util/network_device/cisco.rb new file mode 100644 index 000000000..c03a00104 --- /dev/null +++ b/lib/puppet/util/network_device/cisco.rb @@ -0,0 +1,4 @@ + +module Puppet::Util::NetworkDevice::Cisco + +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/cisco/device.rb b/lib/puppet/util/network_device/cisco/device.rb new file mode 100644 index 000000000..1f350991a --- /dev/null +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -0,0 +1,246 @@ +require 'puppet' +require 'puppet/util' +require 'puppet/util/network_device/base' +require 'puppet/util/network_device/ipcalc' +require 'puppet/util/network_device/cisco/interface' +require 'ipaddr' + +class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice::Base + + include Puppet::Util::NetworkDevice::IPCalc + + attr_accessor :enable_password + + def initialize(url, options = {}) + super(url) + @enable_password = options[:enable_password] || parse_enable(@url.query) + transport.default_prompt = /[#>]\s?\z/n + end + + def parse_enable(query) + return $1 if query =~ /enable=(.*)/ + end + + def command(cmd=nil) + Puppet.debug("command #{cmd}") + transport.connect + login + transport.command("terminal length 0") do |out| + enable if out =~ />\s?\z/n + end + find_capabilities + out = execute(cmd) if cmd + yield self if block_given? + transport.close + out + end + + def execute(cmd) + transport.command(cmd) + end + + def login + return if transport.handles_login? + if @url.user != '' + transport.command(@url.user, :prompt => /^Password:/) + else + transport.expect(/^Password:/) + end + transport.command(@url.password) + end + + def enable + raise "Can't issue \"enable\" to enter privileged, no enable password set" unless enable_password + transport.command("enable", :prompt => /^Password:/) + transport.command(enable_password) + end + + def support_vlan_brief? + !! @support_vlan_brief + end + + def find_capabilities + out = transport.command("sh vlan brief") + lines = out.split("\n") + lines.shift; lines.pop + + @support_vlan_brief = ! (lines.first =~ /^%/) + end + + IF={ + :FastEthernet => %w{FastEthernet FastEth Fast FE Fa F}, + :GigEthernet => %w{GigabitEthernet GigEthernet GigEth GE Gi G}, + :Ethernet => %w{Ethernet Eth E}, + :Serial => %w{Serial Se S}, + :PortChannel => %w{PortChannel Port-Channel Po}, + :POS => %w{POS P}, + :VLAN => %w{VLAN VL V}, + :Loopback => %w{Loopback Loop Lo}, + :ATM => %w{ATM AT A}, + :Dialer => %w{Dialer Dial Di D}, + :VirtualAccess => %w{Virtual-Access Virtual-A Virtual Virt} + } + + def canonalize_ifname(interface) + IF.each do |k,ifnames| + if found = ifnames.find { |ifname| interface =~ /^#{ifname}\s*\d/i } + interface =~ /^#{found}(.+)\b/i + return "#{k.to_s}#{$1}".gsub(/\s+/,'') + end + end + interface + end + + def interface(name) + ifname = canonalize_ifname(name) + interface = parse_interface(ifname) + return { :ensure => :absent } if interface.empty? + interface.merge!(parse_trunking(ifname)) + interface.merge!(parse_interface_config(ifname)) + end + + def new_interface(name) + Puppet::Util::NetworkDevice::Cisco::Interface.new(canonalize_ifname(name), transport) + end + + def parse_interface(name) + resource = {} + out = transport.command("sh interface #{name}") + lines = out.split("\n") + lines.shift; lines.pop + lines.each do |l| + if l =~ /#{name} is (.+), line protocol is / + resource[:ensure] = ($1 == 'up' ? :present : :absent); + end + if l =~ /Auto Speed \(.+\),/ or l =~ /Auto Speed ,/ or l =~ /Auto-speed/ + resource[:speed] = :auto + end + if l =~ /, (.+)Mb\/s/ + resource[:speed] = $1 + end + if l =~ /\s+Auto-duplex \((.{4})\),/ + resource[:duplex] = :auto + end + if l =~ /\s+(.+)-duplex/ + resource[:duplex] = $1 == "Auto" ? :auto : $1.downcase.to_sym + end + if l =~ /Description: (.+)/ + resource[:description] = $1 + end + end + resource + end + + def parse_interface_config(name) + resource = Hash.new { |hash, key| hash[key] = Array.new ; } + out = transport.command("sh running-config interface #{name} | begin interface") + lines = out.split("\n") + lines.shift; lines.pop + lines.each do |l| + if l =~ /ip address (#{IP}) (#{IP})\s+secondary\s*$/ + resource[:ipaddress] << [prefix_length(IPAddr.new($2)), IPAddr.new($1), 'secondary'] + end + if l =~ /ip address (#{IP}) (#{IP})\s*$/ + resource[:ipaddress] << [prefix_length(IPAddr.new($2)), IPAddr.new($1), nil] + end + if l =~ /ipv6 address (#{IP})\/(\d+) (eui-64|link-local)/ + resource[:ipaddress] << [$2.to_i, IPAddr.new($1), $3] + end + if l =~ /channel-group\s+(\d+)/ + resource[:etherchannel] = $1 + end + end + resource + end + + def parse_vlans + vlans = {} + out = transport.command(support_vlan_brief? ? "sh vlan brief" : "sh vlan-switch brief") + lines = out.split("\n") + lines.shift; lines.shift; lines.shift; lines.pop + vlan = nil + lines.each do |l| + case l + # vlan name status + when /^(\d+)\s+(\w+)\s+(\w+)\s+([a-zA-Z0-9,\/. ]+)\s*$/ + vlan = { :name => $1, :description => $2, :status => $3, :interfaces => [] } + if $4.strip.length > 0 + vlan[:interfaces] = $4.strip.split(/\s*,\s*/).map{ |ifn| canonalize_ifname(ifn) } + end + vlans[vlan[:name]] = vlan + when /^\s+([a-zA-Z0-9,\/. ]+)\s*$/ + raise "invalid sh vlan summary output" unless vlan + if $1.strip.length > 0 + vlan[:interfaces] += $1.strip.split(/\s*,\s*/).map{ |ifn| canonalize_ifname(ifn) } + end + else + end + end + vlans + end + + def update_vlan(id, is = {}, should = {}) + if should[:ensure] == :absent + Puppet.info "Removing #{id} from device vlan" + transport.command("conf t") + transport.command("no vlan #{id}") + transport.command("exit") + return + end + + # We're creating or updating an entry + transport.command("conf t") + transport.command("vlan #{id}") + [is.keys, should.keys].flatten.uniq.each do |property| + Puppet.debug("trying property: #{property}: #{should[property]}") + next if property != :description + transport.command("name #{should[property]}") + end + transport.command("exit") + transport.command("exit") + end + + def parse_trunking(interface) + trunking = {} + out = transport.command("sh interface #{interface} switchport") + lines = out.split("\n") + lines.shift; lines.pop + lines.each do |l| + case l + when /^Administrative mode:\s+(.*)$/i + case $1 + when "trunk" + trunking[:mode] = :trunk + when "static access" + trunking[:mode] = :access + else + raise "Unknown switchport mode: #{$1} for #{interface}" + end + when /^Administrative Trunking Encapsulation:\s+(.*)$/ + case $1 + when "dot1q","isl" + trunking[:encapsulation] = $1.to_sym if trunking[:mode] == :trunk + else + raise "Unknown switchport encapsulation: #{$1} for #{interface}" + end + when /^Access Mode VLAN:\s+(.*) \(\(Inactive\)\)$/ + # nothing + when /^Access Mode VLAN:\s+(.*) \(.*\)$/ + trunking[:native_vlan] = $1 if trunking[:mode] == :access + when /^Trunking VLANs Enabled:\s+(.*)$/ + next if trunking[:mode] == :access + vlans = $1 + trunking[:allowed_trunk_vlans] = case vlans + when /all/i + :all + when /none/i + :none + else + vlans + end + end + end + trunking + end + +end diff --git a/lib/puppet/util/network_device/cisco/interface.rb b/lib/puppet/util/network_device/cisco/interface.rb new file mode 100644 index 000000000..63d5492a7 --- /dev/null +++ b/lib/puppet/util/network_device/cisco/interface.rb @@ -0,0 +1,82 @@ +require 'puppet/util/network_device/cisco' +require 'puppet/util/network_device/ipcalc' + +# this manages setting properties to an interface in a cisco switch or router +class Puppet::Util::NetworkDevice::Cisco::Interface + + include Puppet::Util::NetworkDevice::IPCalc + extend Puppet::Util::NetworkDevice::IPCalc + + attr_reader :transport, :name + + def initialize(name, transport) + @name = name + @transport = transport + end + + COMMANDS = { + # property => order, ios command/block/array + :description => [1, "description %s"], + :speed => [2, "speed %s"], + :duplex => [3, "duplex %s"], + :native_vlan => [4, "switchport access vlan %s"], + :encapsulation => [5, "switchport trunk encapsulation %s"], + :mode => [6, "switchport mode %s"], + :allowed_trunk_vlans => [7, "switchport trunk allowed vlan %s"], + :etherchannel => [8, ["channel-group %s", "port group %s"]], + :ipaddress => [9, + lambda do |prefix,ip,option| + ip.ipv6? ? "ipv6 address #{ip.to_s}/#{prefix} #{option}" : + "ip address #{ip.to_s} #{netmask(Socket::AF_INET,prefix)}" + end], + :ensure => [10, lambda { |value| value == :present ? "no shutdown" : "shutdown" } ] + } + + def update(is={}, should={}) + Puppet.debug("Updating interface #{name}") + command("conf t") + command("interface #{name}") + + # apply changes in a defined orders for cisco IOS devices + [is.keys, should.keys].flatten.uniq.sort {|a,b| COMMANDS[a][0] <=> COMMANDS[b][0] }.each do |property| + # They're equal, so do nothing. + next if is[property] == should[property] + + # We're deleting it + if should[property] == :absent or should[property].nil? + execute(property, is[property], "no ") + next + end + + # We're replacing an existing value or creating a new one + execute(property, should[property]) + end + + command("exit") + command("exit") + end + + def execute(property, value, prefix='') + case COMMANDS[property][1] + when Array + COMMANDS[property][1].each do |command| + transport.command(prefix + command % value) do |out| + break unless out =~ /^%/ + end + end + when String + command(prefix + COMMANDS[property][1] % value) + when Proc + value = [value] unless value.is_a?(Array) + value.each do |value| + command(prefix + COMMANDS[property][1].call(*value)) + end + end + end + + def command(command) + transport.command(command) do |out| + Puppet.err "Error while executing #{command}, device returned #{out}" if out =~ /^%/mo + end + end +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/ipcalc.rb b/lib/puppet/util/network_device/ipcalc.rb new file mode 100644 index 000000000..2b4f360b7 --- /dev/null +++ b/lib/puppet/util/network_device/ipcalc.rb @@ -0,0 +1,68 @@ + +require 'puppet/util/network_device' +module Puppet::Util::NetworkDevice::IPCalc + + # This is a rip-off of authstore + Octet = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])' + IPv4 = "#{Octet}\.#{Octet}\.#{Octet}\.#{Octet}" + IPv6_full = "_:_:_:_:_:_:_:_|_:_:_:_:_:_::_?|_:_:_:_:_::((_:)?_)?|_:_:_:_::((_:){0,2}_)?|_:_:_::((_:){0,3}_)?|_:_::((_:){0,4}_)?|_::((_:){0,5}_)?|::((_:){0,6}_)?" + IPv6_partial = "_:_:_:_:_:_:|_:_:_:_::(_:)?|_:_::(_:){0,2}|_::(_:){0,3}" + IP = "#{IPv4}|#{IPv6_full}".gsub(/_/,'([0-9a-fA-F]{1,4})').gsub(/\(/,'(?:') + + def parse(value) + case value + when /^(#{IP})\/(\d+)$/ # 12.34.56.78/24, a001:b002::efff/120, c444:1000:2000::9:192.168.0.1/112 + [$2.to_i,IPAddr.new($1)] + when /^(#{IP})$/ # 10.20.30.40, + value = IPAddr.new(value) + [bits(value.family),value] + end + end + + def bits(family) + family == Socket::AF_INET6 ? 128 : 32 + end + + def fullmask(family) + (1 << bits(family)) - 1 + end + + def mask(family, length) + (1 << (bits(family) - length)) - 1 + end + + # returns ip address netmask from prefix length + def netmask(family, length) + IPAddr.new(fullmask(family) & ~mask(family, length) , family) + end + + # returns an IOS wildmask + def wildmask(family, length) + IPAddr.new(mask(family, length) , family) + end + + # returns ip address prefix length from netmask + def prefix_length(netmask) + mask_addr = netmask.to_i + return 0 if mask_addr == 0 + length=32 + if (netmask.ipv6?) + length=128 + end + + mask = mask_addr < 2**length ? length : 128 + + mask.times do + if ((mask_addr & 1) == 1) + break + end + mask_addr = mask_addr >> 1 + mask = mask - 1 + end + mask + end + + def linklocal?(ip) + end + +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport.rb b/lib/puppet/util/network_device/transport.rb new file mode 100644 index 000000000..e64fe9b69 --- /dev/null +++ b/lib/puppet/util/network_device/transport.rb @@ -0,0 +1,5 @@ +# stub +module Puppet::Util::NetworkDevice + module Transport + end +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport/base.rb b/lib/puppet/util/network_device/transport/base.rb new file mode 100644 index 000000000..1d62209cb --- /dev/null +++ b/lib/puppet/util/network_device/transport/base.rb @@ -0,0 +1,26 @@ + +require 'puppet/util/network_device' +require 'puppet/util/network_device/transport' + +class Puppet::Util::NetworkDevice::Transport::Base + attr_accessor :user, :password, :host, :port + attr_accessor :default_prompt, :timeout + + def initialize + @timeout = 10 + end + + def send(cmd) + end + + def expect(prompt) + end + + def command(cmd, options = {}) + send(cmd) + expect(options[:prompt] || default_prompt) do |output| + yield output if block_given? + end + end + +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport/ssh.rb b/lib/puppet/util/network_device/transport/ssh.rb new file mode 100644 index 000000000..b3cf51b8a --- /dev/null +++ b/lib/puppet/util/network_device/transport/ssh.rb @@ -0,0 +1,115 @@ + +require 'puppet/util/network_device' +require 'puppet/util/network_device/transport' +require 'puppet/util/network_device/transport/base' +require 'net/ssh' + +# This is an adaptation/simplification of gem net-ssh-telnet, which aims to have +# a sane interface to Net::SSH. Credits goes to net-ssh-telnet authors +class Puppet::Util::NetworkDevice::Transport::Ssh < Puppet::Util::NetworkDevice::Transport::Base + + attr_accessor :buf, :ssh, :channel, :verbose + + def initialize + super + end + + def handles_login? + true + end + + def eof? + !! @eof + end + + def connect(&block) + @output = [] + @channel_data = "" + + begin + Puppet.debug("connecting to #{host} as #{user}") + @ssh = Net::SSH.start(host, user, :port => port, :password => password, :timeout => timeout) + rescue TimeoutError + raise TimeoutError, "timed out while opening an ssh connection to the host" + end + + @buf = "" + @eof = false + @channel = nil + @ssh.open_channel do |channel| + channel.request_pty { |ch,success| raise "failed to open pty" unless success } + + channel.send_channel_request("shell") do |ch, success| + raise "failed to open ssh shell channel" unless success + + ch.on_data { |ch,data| @buf << data } + ch.on_extended_data { |ch,type,data| @buf << data if type == 1 } + ch.on_close { @eof = true } + + @channel = ch + expect(default_prompt, &block) + # this is a little bit unorthodox, we're trying to escape + # the ssh loop there while still having the ssh connection up + # otherwise we wouldn't be able to return ssh stdout/stderr + # for a given call of command. + return + end + + end + @ssh.loop + + end + + def close + @channel.close if @channel + @channel = nil + @ssh.close if @ssh + end + + def expect(prompt) + line = '' + sock = @ssh.transport.socket + + while not @eof + break if line =~ prompt and @buf == '' + break if sock.closed? + + IO::select([sock], [sock], nil, nil) + + process_ssh + + # at this point we have accumulated some data in @buf + # or the channel has been closed + if @buf != "" + line += @buf.gsub(/\r\n/no, "\n") + @buf = '' + yield line if block_given? + elsif @eof + # channel has been closed + break if line =~ prompt + if line == '' + line = nil + yield nil if block_given? + end + break + end + end + Puppet.debug("ssh: expected #{line}") if @verbose + line + end + + def send(line) + Puppet.debug("ssh: send #{line}") if @verbose + @channel.send_data(line + "\n") + end + + def process_ssh + while @buf == "" and not eof? + begin + @channel.connection.process(0.1) + rescue IOError + @eof = true + end + end + end +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport/telnet.rb b/lib/puppet/util/network_device/transport/telnet.rb new file mode 100644 index 000000000..e55079e06 --- /dev/null +++ b/lib/puppet/util/network_device/transport/telnet.rb @@ -0,0 +1,42 @@ +require 'puppet/util/network_device' +require 'puppet/util/network_device/transport' +require 'puppet/util/network_device/transport/base' +require 'net/telnet' + +class Puppet::Util::NetworkDevice::Transport::Telnet < Puppet::Util::NetworkDevice::Transport::Base + def initialize + super + end + + def handles_login? + false + end + + def connect + @telnet = Net::Telnet::new("Host" => host, "Port" => port || 23, + "Timeout" => 10, + "Prompt" => default_prompt, "Output_log" => "/tmp/out.log") + end + + def close + @telnet.close if @telnet + @telnet = nil + end + + def expect(prompt) + @telnet.waitfor(prompt) do |out| + yield out if block_given? + end + end + + def command(cmd, options = {}) + send(cmd) + expect(options[:prompt] || default_prompt) do |output| + yield output if block_given? + end + end + + def send(line) + @telnet.puts(line) + end +end
\ No newline at end of file diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index 9d0e0a715..cec8a57d9 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -1,4 +1,4 @@ -# Provides utility functions to help interfaces Puppet to SELinux. +# Provides utility functions to help interface Puppet to SELinux. # # This requires the very new SELinux Ruby bindings. These bindings closely # mirror the SELinux C library interface. |
