diff options
| author | Josh Cooper <josh@puppetlabs.com> | 2011-04-21 14:37:50 -0700 |
|---|---|---|
| committer | Josh Cooper <josh@puppetlabs.com> | 2011-04-21 14:37:50 -0700 |
| commit | 01f610bb223b435dc52f491260af3ea002930102 (patch) | |
| tree | 93565136d5ef2b4156fdd64476792e441bcfbb4e /lib | |
| parent | ac428b9557e2da251e4b51e48de844833ca0aa2a (diff) | |
| parent | fc66e98b84b9a16728af054485883334a5887cca (diff) | |
| download | puppet-01f610bb223b435dc52f491260af3ea002930102.tar.gz puppet-01f610bb223b435dc52f491260af3ea002930102.tar.xz puppet-01f610bb223b435dc52f491260af3ea002930102.zip | |
Merge branch 'next'
Diffstat (limited to 'lib')
49 files changed, 1357 insertions, 282 deletions
diff --git a/lib/puppet/application/cert.rb b/lib/puppet/application/cert.rb index c08775380..162672b6a 100644 --- a/lib/puppet/application/cert.rb +++ b/lib/puppet/application/cert.rb @@ -61,9 +61,8 @@ but mostly used for signing certificate requests from puppet clients. USAGE ----- -puppet cert [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] - [-g|--generate] [-l|--list] [-s|--sign] [-r|--revoke] [-p|--print] - [-c|--clean] [--verify] [--digest <digest>] [--fingerprint] [host] +puppet cert <action> [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] + [--digest <digest>] [<host>] DESCRIPTION @@ -73,6 +72,51 @@ certificate requests, this script is available for signing outstanding requests. It can be used to list outstanding requests and then either sign them individually or sign all of them. +ACTIONS +------- + +Every action except 'list' and 'generate' requires a hostname to act on, +unless the '--all' option is set. + +* clean: + Revoke a host's certificate (if applicable) and remove all files + related to that host from puppet cert's storage. This is useful when + rebuilding hosts, since new certificate signing requests will only be + honored if puppet cert does not have a copy of a signed certificate + for that host. If '--all' is specified then all host certificates, + both signed and unsigned, will be removed. + +* fingerprint: + Print the DIGEST (defaults to md5) fingerprint of a host's + certificate. + +* generate: + Generate a certificate for a named client. A certificate/keypair will + be generated for each client named on the command line. + +* list: + List outstanding certificate requests. If '--all' is specified, signed + certificates are also listed, prefixed by '+', and revoked or invalid + certificates are prefixed by '-' (the verification outcome is printed + in parenthesis). + +* print: + Print the full-text version of a host's certificate. + +* revoke: + Revoke the certificate of a client. The certificate can be specified + either by its serial number (given as a decimal number or a + hexadecimal number prefixed by '0x') or by its hostname. The + certificate is revoked by adding it to the Certificate Revocation List + given by the 'cacrl' configuration option. Note that the puppet master + needs to be restarted after revoking certificates. + +* sign: + Sign an outstanding certificate request. + +* verify: + Verify the named certificate against the local CA certificate. + OPTIONS ------- @@ -88,72 +132,32 @@ configuration options can also be generated by running puppet cert with '--genconfig'. * --all: - Operate on all items. Currently only makes sense with '--sign', - '--clean', or '--list'. + Operate on all items. Currently only makes sense with the 'sign', + 'clean', 'list', and 'fingerprint' actions. * --digest: Set the digest for fingerprinting (defaults to md5). Valid values depends on your openssl and openssl ruby extension version, but should contain at least md5, sha1, md2, sha256. -* --clean: - Remove all files related to a host from puppet cert's storage. This is - useful when rebuilding hosts, since new certificate signing requests - will only be honored if puppet cert does not have a copy of a signed - certificate for that host. The certificate of the host is also - revoked. If '--all' is specified then all host certificates, both - signed and unsigned, will be removed. - * --debug: Enable full debugging. -* --generate: - Generate a certificate for a named client. A certificate/keypair will - be generated for each client named on the command line. - * --help: Print this help message -* --list: - List outstanding certificate requests. If '--all' is specified, signed - certificates are also listed, prefixed by '+', and revoked or invalid - certificates are prefixed by '-' (the verification outcome is printed - in parenthesis). - -* --print: - Print the full-text version of a host's certificate. - -* --fingerprint: - Print the DIGEST (defaults to md5) fingerprint of a host's - certificate. - -* --revoke: - Revoke the certificate of a client. The certificate can be specified - either by its serial number, given as a decimal number or a - hexadecimal number prefixed by '0x', or by its hostname. The - certificate is revoked by adding it to the Certificate Revocation List - given by the 'cacrl' config parameter. Note that the puppetmasterd - needs to be restarted after revoking certificates. - -* --sign: - Sign an outstanding certificate request. Unless '--all' is specified, - hosts must be listed after all flags. - * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. -* --verify: - Verify the named certificate against the local CA certificate. - EXAMPLE ------- - $ puppet cert -l + $ puppet cert list culain.madstop.com - $ puppet cert -s culain.madstop.com + $ puppet cert sign culain.madstop.com AUTHOR diff --git a/lib/puppet/application/device.rb b/lib/puppet/application/device.rb new file mode 100644 index 000000000..df5bac26a --- /dev/null +++ b/lib/puppet/application/device.rb @@ -0,0 +1,255 @@ +require 'puppet/application' +require 'puppet/util/network_device' + + +class Puppet::Application::Device < Puppet::Application + + should_parse_config + run_mode :agent + + attr_accessor :args, :agent, :host + + def preinit + # Do an initial trap, so that cancels don't get a stack trace. + trap(:INT) do + $stderr.puts "Cancelling startup" + exit(0) + end + + { + :waitforcert => nil, + :detailed_exitcodes => false, + :verbose => false, + :debug => false, + :centrallogs => false, + :setdest => false, + }.each do |opt,val| + options[opt] = val + end + + @args = {} + end + + option("--centrallogging") + option("--debug","-d") + option("--verbose","-v") + + option("--detailed-exitcodes") do |arg| + options[:detailed_exitcodes] = true + end + + option("--logdest DEST", "-l DEST") do |arg| + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + puts detail.backtrace if Puppet[:debug] + $stderr.puts detail.to_s + end + end + + option("--waitforcert WAITFORCERT", "-w") do |arg| + options[:waitforcert] = arg.to_i + end + + option("--port PORT","-p") do |arg| + @args[:Port] = arg + end + + def help + <<-HELP + +puppet-device(8) -- Manage remote network devices +======== + +SYNOPSIS +-------- +Retrieves all configurations from the puppet master and apply +them to the remote devices configured in /etc/puppet/device.conf. + +Currently must be run out periodically, using cron or something similar. + +USAGE +----- + puppet device [-d|--debug] [--detailed-exitcodes] [-V|--version] + [-h|--help] [-l|--logdest syslog|<file>|console] + [-v|--verbose] [-w|--waitforcert <seconds>] + + +DESCRIPTION +----------- +Once the client has a signed certificate for a given remote device, it will +retrieve its configuration and apply it. + +USAGE NOTES +----------- +One need a /etc/puppet/device.conf file with the following content: + +[remote.device.fqdn] +type <type> +url <url> + +where: + * type: the current device type (the only value at this time is cisco) + * url: an url allowing to connect to the device + +Supported url must conforms to: + scheme://user:password@hostname/?query + + with: + * scheme: either ssh or telnet + * user: username, can be omitted depending on the switch/router configuration + * password: the connection password + * query: this is device specific. Cisco devices supports an enable parameter whose + value would be the enable password. + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration file +is also a valid long argument. For example, 'server' is a valid configuration +parameter, so you can specify '--server <servername>' as an argument. + +* --debug: + Enable full debugging. + +* --detailed-exitcodes: + Provide transaction information via exit codes. If this is enabled, an + exit code of '2' means there were changes, and an exit code of '4' means + that there were failures during the transaction. This option only makes + sense in conjunction with --onetime. + +* --help: + Print this help message + +* --logdest: + Where to send messages. Choose between syslog, the console, and a log file. + Defaults to sending messages to syslog, or the console if debugging or + verbosity is enabled. + +* --verbose: + Turn on verbose reporting. + +* --waitforcert: + This option only matters for daemons that do not yet have certificates + and it is enabled by default, with a value of 120 (seconds). This causes + +puppet agent+ to connect to the server every 2 minutes and ask it to sign a + certificate request. This is useful for the initial setup of a puppet + client. You can turn off waiting for certificates by specifying a time + of 0. + +EXAMPLE +------- + $ puppet device --server puppet.domain.com + +AUTHOR +------ +Brice Figureau + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC +Licensed under the Apache 2.0 License + HELP + end + + + def main + vardir = Puppet[:vardir] + confdir = Puppet[:confdir] + certname = Puppet[:certname] + + # find device list + require 'puppet/util/network_device/config' + devices = Puppet::Util::NetworkDevice::Config.devices + if devices.empty? + Puppet.err "No device found in #{Puppet[:deviceconfig]}" + exit(1) + end + devices.each_value do |device| + begin + Puppet.info "starting applying configuration to #{device.name} at #{device.url}" + + # override local $vardir and $certname + Puppet.settings.set_value(:confdir, File.join(Puppet[:devicedir], device.name), :cli) + Puppet.settings.set_value(:vardir, File.join(Puppet[:devicedir], device.name), :cli) + Puppet.settings.set_value(:certname, device.name, :cli) + + # this will reload and recompute default settings and create the devices sub vardir, or we hope so :-) + Puppet.settings.use :main, :agent, :ssl + + # this init the device singleton, so that the facts terminus + # and the various network_device provider can use it + Puppet::Util::NetworkDevice.init(device) + + # ask for a ssl cert if needed, but at least + # setup the ssl system for this device. + setup_host + + require 'puppet/configurer' + configurer = Puppet::Configurer.new + report = configurer.run(:network_device => true) + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err detail.to_s + ensure + Puppet.settings.set_value(:vardir, vardir, :cli) + Puppet.settings.set_value(:confdir, confdir, :cli) + Puppet.settings.set_value(:certname, certname, :cli) + end + end + end + + # Handle the logging settings. + def setup_logs + if options[:debug] or options[:verbose] + Puppet::Util::Log.newdestination(:console) + if options[:debug] + Puppet::Util::Log.level = :debug + else + Puppet::Util::Log.level = :info + end + end + + Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] + end + + def setup_host + @host = Puppet::SSL::Host.new + waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) + cert = @host.wait_for_cert(waitforcert) + end + + def setup + setup_logs + + args[:Server] = Puppet[:server] + if options[:centrallogs] + logdest = args[:Server] + + logdest += ":" + args[:Port] if args.include?(:Port) + Puppet::Util::Log.newdestination(logdest) + end + + Puppet.settings.use :main, :agent, :device, :ssl + + # Always ignoreimport for agent. It really shouldn't even try to import, + # but this is just a temporary band-aid. + Puppet[:ignoreimport] = true + + # We need to specify a ca location for all of the SSL-related i + # indirected classes to work; in fingerprint mode we just need + # access to the local files and we don't need a ca. + Puppet::SSL::Host.ca_location = :remote + + Puppet::Transaction::Report.indirection.terminus_class = :rest + + # Override the default; puppetd needs this, usually. + # You can still override this on the command-line with, e.g., :compiler. + Puppet[:catalog_terminus] = :rest + + Puppet[:facts_terminus] = :network_device + + Puppet::Resource::Catalog.indirection.cache_class = :yaml + end +end diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb index 2a048a532..9da48af55 100644 --- a/lib/puppet/application/face_base.rb +++ b/lib/puppet/application/face_base.rb @@ -1,6 +1,7 @@ require 'puppet/application' require 'puppet/face' require 'optparse' +require 'pp' class Puppet::Application::FaceBase < Puppet::Application should_parse_config @@ -14,8 +15,8 @@ class Puppet::Application::FaceBase < Puppet::Application Puppet::Util::Log.level = :info end - option("--format FORMAT") do |arg| - @format = arg.to_sym + option("--render-as FORMAT") do |arg| + @render_as = arg.to_sym end option("--mode RUNMODE", "-r") do |arg| @@ -25,7 +26,7 @@ class Puppet::Application::FaceBase < Puppet::Application end - attr_accessor :face, :action, :type, :arguments, :format + attr_accessor :face, :action, :type, :arguments, :render_as attr_writer :exit_code # This allows you to set the exit code if you don't want to just exit @@ -34,17 +35,49 @@ class Puppet::Application::FaceBase < Puppet::Application @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) + format = render_as || action.render_as || :for_humans + + # Invoke the rendering hook supplied by the user, if appropriate. + if hook = action.when_rendering(format) then + result = hook.call(result) + end + + if format == :for_humans then + render_for_humans(result) else - result.send(render_method) + render_method = Puppet::Network::FormatHandler.format(format).render_method + if render_method == "to_pson" + PSON::pretty_generate(result, :allow_nan => true, :max_nesting => false) + else + result.send(render_method) + end end end + def render_for_humans(result) + # String to String + return result if result.is_a? String + return result if result.is_a? Numeric + + # Simple hash to table + if result.is_a? Hash and result.keys.all? { |x| x.is_a? String or x.is_a? Numeric } + output = '' + column_a = result.map do |k,v| k.to_s.length end.max + 2 + column_b = 79 - column_a + result.sort_by { |k,v| k.to_s } .each do |key, value| + output << key.to_s.ljust(column_a) + output << PP.pp(value, '', column_b). + chomp.gsub(/\n */) { |x| x + (' ' * column_a) } + output << "\n" + end + return output + end + + # ...or pretty-print the inspect outcome. + return result.pretty_inspect + end + def preinit super Signal.trap(:INT) do @@ -59,9 +92,8 @@ class Puppet::Application::FaceBase < Puppet::Application # 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 + @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym + @face = Puppet::Face[@type, :current] # Now, walk the command line and identify the action. We skip over # arguments based on introspecting the action and all, and find the first @@ -94,16 +126,13 @@ class Puppet::Application::FaceBase < Puppet::Application 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 + @action = @face.get_action(item.to_sym) end end - unless @action - raise OptionParser::MissingArgument.new("No action given on the command line") + if @action.nil? + @action = @face.get_default_action() + @is_default_action = true end # Now we can interact with the default option code to build behaviour @@ -111,7 +140,7 @@ class Puppet::Application::FaceBase < Puppet::Application @action.options.each do |option| option = @action.get_option(option) # make it the object. self.class.option(*option.optparse) # ...and make the CLI parse it. - end + end if @action # ...and invoke our parent to parse all the command line options. super @@ -138,7 +167,10 @@ class Puppet::Application::FaceBase < Puppet::Application # 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) + # Of course, now that we have default actions, we should leave the + # "action" name on if we didn't actually consume it when we found our + # action. + @arguments.delete_at(0) unless @is_default_action # We copy all of the app options to the end of the call; This allows each # action to read in the options. This replaces the older model where we @@ -150,8 +182,17 @@ class Puppet::Application::FaceBase < Puppet::Application def main # Call the method associated with the provided action (e.g., 'find'). - if result = @face.send(@action.name, *arguments) - puts render(result) + if @action + result = @face.send(@action.name, *arguments) + puts render(result) unless result.nil? + else + if arguments.first.is_a? Hash + puts "#{@face} does not have a default action" + else + puts "#{@face} does not respond to action #{arguments.first}" + end + + puts Puppet::Face[:help, :current].help(@face.name, *arguments) end exit(exit_code) end diff --git a/lib/puppet/application/kick.rb b/lib/puppet/application/kick.rb index 536699442..4f3ed1802 100644 --- a/lib/puppet/application/kick.rb +++ b/lib/puppet/application/kick.rb @@ -76,31 +76,16 @@ copy things like LDAP settings. USAGE NOTES ----------- -'puppet kick' is useless unless 'puppet agent' is listening. See its -documentation for more information, but the gist is that you must enable -'listen' on the 'puppet agent' daemon, either using '--listen' on the -command line or adding 'listen = true' in its config file. In addition, -you need to set the daemons up to specifically allow connections by -creating the 'namespaceauth' file, normally at -'/etc/puppet/namespaceauth.conf'. This file specifies who has access to -each namespace; if you create the file you must add every namespace you -want any Puppet daemon to allow -- it is currently global to all Puppet -daemons. - -An example file looks like this: - - [fileserver] - allow *.madstop.com - - [puppetmaster] - allow *.madstop.com - - [puppetrunner] - allow culain.madstop.com - -This is what you would install on your Puppet master; non-master hosts -could leave off the 'fileserver' and 'puppetmaster' namespaces. - +Puppet kick is useless unless puppet agent is listening for incoming +connections and allowing access to the `run` endpoint. This entails +starting the agent with `listen = true` in its puppet.conf file, and +allowing access to the `/run` path in its auth.conf file; see +`http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more +details. + +Additionally, due to a known bug, you must make sure a +namespaceauth.conf file exists in puppet agent's $confdir. This file +will not be consulted, and may be left empty. OPTIONS ------- diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 680762b94..139c3c763 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -418,7 +418,7 @@ module Puppet :desc => "Where the puppet master web server logs." }, :masterport => [8140, "Which port puppet master listens on."], - :node_name => ["cert", "How the puppetmaster determines the client's identity + :node_name => ["cert", "How the puppet master 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. Possible values are 'cert' (use the subject's CN in the client's @@ -487,6 +487,11 @@ module Puppet This should match how often the hosts report back to the server."] ) + setdefaults(:device, + :devicedir => {:default => "$vardir/devices", :mode => "750", :desc => "The root directory of devices' $vardir"}, + :deviceconfig => ["$confdir/device.conf","Path to the device config file for puppet device"] + ) + setdefaults(:agent, :localconfig => { :default => "$statedir/localconfig", :owner => "root", @@ -523,10 +528,10 @@ module Puppet :runinterval => [1800, # 30 minutes "How often puppet agent applies the client configuration; in seconds."], :listen => [false, "Whether puppet agent should listen for - connections. If this is true, then by default only the - `runner` server is started, which allows remote authorized - and authenticated nodes to connect and trigger `puppet agent` - runs."], + connections. If this is true, then puppet agent will accept incoming + REST API requests, subject to the default ACLs and the ACLs set in + the `rest_authconfig` file. Puppet agent can respond usefully to + requests on the `run`, `facts`, `certificate`, and `resource` endpoints."], :ca_server => ["$server", "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale."], @@ -623,8 +628,8 @@ module Puppet :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."], :http_compression => [false, "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. - Your puppetmaster needs to support compression (usually by activating some settings in a reverse-proxy - in front of the puppetmaster, which rules out webrick). + Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy + in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs."] ) diff --git a/lib/puppet/face/certificate.rb b/lib/puppet/face/certificate.rb index 77e80f099..4c2950fb3 100644 --- a/lib/puppet/face/certificate.rb +++ b/lib/puppet/face/certificate.rb @@ -2,24 +2,16 @@ 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" + option "--ca-location LOCATION" do + before_action do |action, args, options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + end + end action :generate do + summary "Generate a new Certificate Signing Request for HOST" + 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) @@ -27,8 +19,9 @@ Puppet::Face::Indirector.define(:certificate, '0.0.1') do end action :list do + summary "List all Certificate Signing Requests" + 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 } @@ -36,8 +29,9 @@ Puppet::Face::Indirector.define(:certificate, '0.0.1') do end action :sign do + summary "Sign a Certificate Signing Request for HOST" + 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) diff --git a/lib/puppet/face/facts.rb b/lib/puppet/face/facts.rb index 8668b2531..04eab93a5 100644 --- a/lib/puppet/face/facts.rb +++ b/lib/puppet/face/facts.rb @@ -2,10 +2,10 @@ 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 + render_as :yaml + when_invoked do |options| Puppet::Node::Facts.indirection.terminus_class = :facter facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) diff --git a/lib/puppet/face/help.rb b/lib/puppet/face/help.rb index 1c2da9e83..a762fb02e 100644 --- a/lib/puppet/face/help.rb +++ b/lib/puppet/face/help.rb @@ -13,12 +13,38 @@ Puppet::Face.define(:help, '0.0.1') do desc "Which version of the interface to show help for" end + default 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 + if args.select { |x| x == 'help' }.length > 2 then + c = "\n !\"'),-./7:;<GIJLST\\_`abcdefhiklmnoprstuwx|}".split('') + i = <<-'EOT'.gsub(/\s*/, '').to_i(36) + 2s7ytxy5vpj74kbab5xzf1ik2roinzlefaspjrzckiert5xbaxvwlku3a91w7y1rsd + nenp51gwpulmnrp54nwdil36fjgjarab801y0r5a9nh1hdfgi99arn5c5t3zhxbvzi + u6wx5r1tb7lun7pro69nrxunqqixsh6qmmv0ms0i0yycqw3pystyzmiita0lpxynqs + qkbjwadcx82n76wwpzbht8i8rgvqhqick8mk3cs3rvwdjookpgu0rxw4tcezned5sq + z5x8z9vntyyz0s4h6hjhtwtbytsmmu7ltvdftaixc7fkt276sqm48ab4yv0ot9y26n + z0xniy4pfl1x300lt6h9c8of49vf799ieuxwnoycsjlmtd4qntzit524j0tdn6n5aj + mq3z10apjuhkzprvmu53z1gnacymnoforrz5mbqto062kckgw5463pxwzg8liglub4 + ubnr0dln1s6iy3ummxuhim7m5a7yedl3gyy6ow4qqtmsigv27lysooau24zpsccsvx + ddwygjprqpbwon7i9s1279m1fpinvva8mfh6bgmotrpxsh1c8rc83l3u0utf5i200y + l7ui0ngcbcjyr4erzdee2tqk3fpjvb82t8xhncruhgn7j5dh2m914qzhb0gkoom47k + 6et7rp4tqjnrv0y2apk5qdl1x1hnbkkxup5ys6ip2ksmtpd3ipmrdtswxr5xwfiqtm + 60uyjr1v79irhnkrbbt4fwhgqjby1qflgwt9c1wpayzzucep6npgbn3f1k6cn4pug3 + 1u02wel4tald4hij8m5p49xr8u4ero1ucs5uht42o8nhpmpe7c7xf9t85i85m9m5kk + tgoqkgbu52gy5aoteyp8jkm3vri9fnkmwa5h60zt8otja72joxjb40p2rz2vp8f8q9 + nnggxt3x90pe5u4048ntyuha78q1oikhhpvw9j083yc3l00hz5ehv9c1au5gvctyap + zprub289qruve9qsyuh75j04wzkemqw3uhisrfs92u1ahv2qlqxmorgob16c1vbqkx + ttkoyp2agkt0v5l7lec25p0jqun9y39k41h67aeb5ihiqsftxc9azmg31hc73dk8ur + lj88vgbmgt8yln9rchw60whgxvnv9zn6cxbr482svctswc5a07atj + EOT + 607.times{i,x=i.divmod(1035);a,b=x.divmod(23);print(c[a]*b)} + raise ArgumentError, "Such panic is really not required." + end raise ArgumentError, "help only takes two (optional) arguments, a face name, and an action" end diff --git a/lib/puppet/face/indirector.rb b/lib/puppet/face/indirector.rb index f48611e4b..6c7708b51 100644 --- a/lib/puppet/face/indirector.rb +++ b/lib/puppet/face/indirector.rb @@ -5,6 +5,14 @@ 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." + + before_action do |action, args, options| + set_terminus(options[:terminus]) + end + + after_action do |action, args, options| + indirection.reset_terminus_class + end end def self.indirections @@ -17,7 +25,6 @@ that we should describe in this file somehow." def call_indirection_method(method, *args) options = args.last - options.has_key?(:terminus) and set_terminus(options[:terminus]) begin result = indirection.__send__(method, *args) @@ -26,7 +33,6 @@ that we should describe in this file somehow." raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" end - indirection.reset_terminus_class return result end @@ -49,16 +55,11 @@ that we should describe in this file somehow." # 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 diff --git a/lib/puppet/face/node.rb b/lib/puppet/face/node.rb index fd1a548d6..12336df8f 100644 --- a/lib/puppet/face/node.rb +++ b/lib/puppet/face/node.rb @@ -1,5 +1,3 @@ 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 index c44810b99..d4aaaf043 100644 --- a/lib/puppet/face/parser.rb +++ b/lib/puppet/face/parser.rb @@ -6,7 +6,10 @@ Puppet::Face.define(:parser, '0.0.1') do when_invoked do |*args| args.pop files = args - files << Puppet[:manifest] if files.empty? + if files.empty? + files << Puppet[:manifest] + Puppet.notice "No manifest specified. Validating the default manifest #{Puppet[:manifest]}" + end files.each do |file| Puppet[:manifest] = file Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear diff --git a/lib/puppet/indirector/facts/network_device.rb b/lib/puppet/indirector/facts/network_device.rb new file mode 100644 index 000000000..c9bac803e --- /dev/null +++ b/lib/puppet/indirector/facts/network_device.rb @@ -0,0 +1,25 @@ +require 'puppet/node/facts' +require 'puppet/indirector/code' + +class Puppet::Node::Facts::NetworkDevice < Puppet::Indirector::Code + desc "Retrieve facts from a network device." + + # Look a device's facts up through the current device. + def find(request) + result = Puppet::Node::Facts.new(request.key, Puppet::Util::NetworkDevice.current.facts) + + result.add_local_facts + result.stringify + result.downcase_if_necessary + + result + end + + def destroy(facts) + raise Puppet::DevError, "You cannot destroy facts in the code store; it is only used for getting facts from a remote device" + end + + def save(facts) + raise Puppet::DevError, "You cannot save facts to the code store; it is only used for getting facts from a remote device" + end +end
\ No newline at end of file diff --git a/lib/puppet/indirector/queue.rb b/lib/puppet/indirector/queue.rb index fd089f431..85ffacacc 100644 --- a/lib/puppet/indirector/queue.rb +++ b/lib/puppet/indirector/queue.rb @@ -36,7 +36,7 @@ class Puppet::Indirector::Queue < Puppet::Indirector::Terminus def save(request) result = nil benchmark :info, "Queued #{indirection.name} for #{request.key}" do - result = client.send_message(queue, request.instance.render(:pson)) + result = client.publish_message(queue, request.instance.render(:pson)) end result rescue => detail diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb index fd8d654dd..1918a3fb5 100644 --- a/lib/puppet/indirector/request.rb +++ b/lib/puppet/indirector/request.rb @@ -14,6 +14,51 @@ class Puppet::Indirector::Request OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :instance, :environment] + def self.from_pson(json) + raise ArgumentError, "No indirection name provided in json data" unless indirection_name = json['type'] + raise ArgumentError, "No method name provided in json data" unless method = json['method'] + raise ArgumentError, "No key provided in json data" unless key = json['key'] + + request = new(indirection_name, method, key, json['attributes']) + + if instance = json['instance'] + klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model + if instance.is_a?(klass) + request.instance = instance + else + request.instance = klass.from_pson(instance) + end + end + + request + end + + def to_pson(*args) + result = { + 'document_type' => 'Puppet::Indirector::Request', + 'data' => { + 'type' => indirection_name, + 'method' => method, + 'key' => key + } + } + data = result['data'] + attributes = {} + OPTION_ATTRIBUTES.each do |key| + next unless value = send(key) + attributes[key] = value + end + + options.each do |opt, value| + attributes[opt] = value + end + + data['attributes'] = attributes unless attributes.empty? + data['instance'] = instance if instance + + result.to_pson(*args) + end + # Is this an authenticated request? def authenticated? # Double negative, so we just get true or false @@ -61,9 +106,11 @@ class Puppet::Indirector::Request self.indirection_name = indirection_name self.method = method + options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash } + set_attributes(options) - @options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash } + @options = options if key_or_instance.is_a?(String) || key_or_instance.is_a?(Symbol) key = key_or_instance @@ -153,7 +200,7 @@ class Puppet::Indirector::Request def set_attributes(options) OPTION_ATTRIBUTES.each do |attribute| - if options.include?(attribute) + if options.include?(attribute.to_sym) send(attribute.to_s + "=", options[attribute]) options.delete(attribute) end diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index 6570ebe46..5c8ade749 100644 --- a/lib/puppet/interface.rb +++ b/lib/puppet/interface.rb @@ -62,18 +62,35 @@ class Puppet::Interface end end - attr_accessor :default_format - def set_default_format(format) - self.default_format = format.to_sym + Puppet.warning("set_default_format is deprecated (and ineffective); use render_as on your actions instead.") end - attr_accessor :summary + ######################################################################## + # Documentation. We currently have to rewrite both getters because we share + # the same instance between build-time and the runtime instance. When that + # splits out this should merge into a module that both the action and face + # include. --daniel 2011-04-17 + attr_accessor :summary, :description def summary(value = nil) - @summary = value unless value.nil? + self.summary = value unless value.nil? @summary end + def summary=(value) + value = value.to_s + value =~ /\n/ and + raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead." + + @summary = value + end + + def description(value = nil) + self.description = value unless value.nil? + @description + end + + ######################################################################## attr_reader :name, :version def initialize(name, version, &block) @@ -83,33 +100,17 @@ class Puppet::Interface @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 + Puppet::Interface.autoloader.search_directories.each do |dir| + Dir.glob(File.join(dir, "puppet/face/#{name}", "*.rb")).each do |file| + action = file.sub(dir, '').sub(/^[\\\/]/, '').sub(/\.rb/, '') + Puppet.debug "Loading action '#{action}' for '#{name}' from '#{dir}/#{action}.rb'" + require(action) end end end @@ -117,4 +118,43 @@ class Puppet::Interface def to_s "Puppet::Face[#{name.inspect}, #{version.inspect}]" end + + ######################################################################## + # Action decoration, whee! You are not expected to care about this code, + # which exists to support face building and construction. I marked these + # private because the implementation is crude and ugly, and I don't yet know + # enough to work out how to make it clean. + # + # Once we have established that these methods will likely change radically, + # to be unrecognizable in the final outcome. At which point we will throw + # all this away, replace it with something nice, and work out if we should + # be making this visible to the outside world... --daniel 2011-04-14 + private + def __invoke_decorations(type, action, passed_args = [], passed_options = {}) + [:before, :after].member?(type) or fail "unknown decoration type #{type}" + + # Collect the decoration methods matching our pass. + methods = action.options.select do |name| + passed_options.has_key? name + end.map do |name| + action.get_option(name).__decoration_name(type) + end + + methods.each do |hook| + begin + respond_to? hook and self.__send__(hook, action, passed_args, passed_options) + rescue => e + Puppet.warning("invoking #{action} #{type} hook: #{e}") + end + end + end + + def __add_method(name, proc) + meta_def(name, &proc) + method(name).unbind + end + def self.__add_method(name, proc) + define_method(name, proc) + instance_method(name) + end end diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb index db338e39e..23366b407 100644 --- a/lib/puppet/interface/action.rb +++ b/lib/puppet/interface/action.rb @@ -7,8 +7,10 @@ class Puppet::Interface::Action 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 + + @options = {} + @when_rendering = {} end # This is not nice, but it is the easiest way to make us behave like the @@ -20,11 +22,79 @@ class Puppet::Interface::Action return bound_version end - attr_reader :name def to_s() "#{@face}##{@name}" end + attr_reader :name + attr_accessor :default + def default? + !!@default + end + attr_accessor :summary + + ######################################################################## + # Support for rendering formats and all. + def when_rendering(type) + unless type.is_a? Symbol + raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}" + end + return unless @when_rendering.has_key? type + return @when_rendering[type].bind(@face) + end + def set_rendering_method_for(type, proc) + unless proc.is_a? Proc + msg = "The second argument to set_rendering_method_for must be a Proc" + msg += ", not #{proc.class.name}" unless proc.nil? + raise ArgumentError, msg + end + if proc.arity != 1 then + msg = "when_rendering methods take one argument, the result, not " + if proc.arity < 0 then + msg += "a variable number" + else + msg += proc.arity.to_s + end + raise ArgumentError, msg + end + unless type.is_a? Symbol + raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}" + end + if @when_rendering.has_key? type then + raise ArgumentError, "You can't define a rendering method for #{type} twice" + end + # Now, the ugly bit. We add the method to our interface object, and + # retrieve it, to rotate through the dance of getting a suitable method + # object out of the whole process. --daniel 2011-04-18 + @when_rendering[type] = + @face.__send__( :__add_method, __render_method_name_for(type), proc) + end + + def __render_method_name_for(type) + :"#{name}_when_rendering_#{type}" + end + private :__render_method_name_for + + + attr_accessor :render_as + def render_as=(value) + @render_as = value.to_sym + end + + + ######################################################################## + # Documentation stuff, whee! + attr_accessor :summary, :description + def summary=(value) + value = value.to_s + value =~ /\n/ and + raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead." + + @summary = value + end + + + ######################################################################## # Initially, this was defined to allow the @action.invoke pattern, which is # a very natural way to invoke behaviour given our introspection # capabilities. Heck, our initial plan was to have the faces delegate to @@ -82,13 +152,24 @@ class Puppet::Interface::Action # 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" + file = __FILE__ + "+eval" + line = __LINE__ + 1 + wrapper = <<WRAPPER +def #{@name}(*args) + if args.last.is_a? Hash then + options = args.last + else + args << (options = {}) + end + + action = get_action(#{name.inspect}) + action.validate_args(args) + __invoke_decorations(:before, action, args, options) + rval = self.__send__(#{internal_name.inspect}, *args) + __invoke_decorations(:after, action, args, options) + return rval +end +WRAPPER if @face.is_a?(Class) @face.class_eval do eval wrapper, nil, file, line end @@ -123,7 +204,28 @@ class Puppet::Interface::Action (@options.keys + @face.options).sort end - def get_option(name) - @options[name.to_sym] || @face.get_option(name) + def get_option(name, with_inherited_options = true) + option = @options[name.to_sym] + if option.nil? and with_inherited_options + option = @face.get_option(name) + end + option + end + + def validate_args(args) + required = options.map do |name| + get_option(name) + end.select(&:required?).collect(&:name) - args.last.keys + + return if required.empty? + raise ArgumentError, "missing required options (#{required.join(', ')})" + end + + ######################################################################## + # Support code for action decoration; see puppet/interface.rb for the gory + # details of why this is hidden away behind private. --daniel 2011-04-15 + private + def __add_method(name, proc) + @face.__send__ :__add_method, name, proc end end diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb index 34bb3fa44..2ffa38709 100644 --- a/lib/puppet/interface/action_builder.rb +++ b/lib/puppet/interface/action_builder.rb @@ -20,16 +20,54 @@ class Puppet::Interface::ActionBuilder # 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 when_rendering(type = nil, &block) + if type.nil? then # the default error message sucks --daniel 2011-04-18 + raise ArgumentError, 'You must give a rendering format to when_rendering' + end + if block.nil? then + raise ArgumentError, 'You must give a block to when_rendering' + end + @action.set_rendering_method_for(type, 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 + def default(value = true) + @action.default = !!value + end + + def render_as(value = nil) + value.nil? and raise ArgumentError, "You must give a rendering format to render_as" + + formats = Puppet::Network::FormatHandler.formats << :for_humans + unless formats.include? value + raise ArgumentError, "#{value.inspect} is not a valid rendering format: #{formats.sort.join(", ")}" + end + + @action.render_as = value + end + + # Metaprogram the simple DSL from the target class. + Puppet::Interface::Action.instance_methods.grep(/=$/).each do |setter| + next if setter =~ /^=/ + dsl = setter.sub(/=$/, '') + + unless private_instance_methods.include? dsl + # Using eval because the argument handling semantics are less awful than + # when we use the define_method/block version. The later warns on older + # Ruby versions if you pass the wrong number of arguments, but carries + # on, which is totally not what we want. --daniel 2011-04-18 + eval <<METHOD +def #{dsl}(value) + @action.#{dsl} = value +end +METHOD + end end end diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb index d75697afa..440be2d1b 100644 --- a/lib/puppet/interface/action_manager.rb +++ b/lib/puppet/interface/action_manager.rb @@ -5,8 +5,13 @@ module Puppet::Interface::ActionManager # the code to do so. def action(name, &block) @actions ||= {} + @default_action ||= nil raise "Action #{name} already defined for #{self}" if action?(name) action = Puppet::Interface::ActionBuilder.build(self, name, &block) + if action.default + raise "Actions #{@default_action.name} and #{name} cannot both be default" if @default_action + @default_action = action + end @actions[action.name] = action end @@ -50,6 +55,10 @@ module Puppet::Interface::ActionManager return result end + def get_default_action + @default_action + end + def action?(name) actions.include?(name.to_sym) end diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb index ccc2fbba7..f4c56cb2c 100644 --- a/lib/puppet/interface/option.rb +++ b/lib/puppet/interface/option.rb @@ -1,19 +1,4 @@ -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 = [] @@ -58,8 +43,9 @@ class Puppet::Interface::Option # 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 + @optional_argument = @optparse.any? { |o| o=~/[ =]\[/ } + @optional_argument and raise ArgumentError, "Options with optional arguments are not supported" + if @optional_argument and not @optparse.all? { |o| o=~/[ =]\[/ } then raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" end end @@ -79,4 +65,40 @@ class Puppet::Interface::Option raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ name.to_sym end + + + def takes_argument? + !!@argument + end + def optional_argument? + !!@optional_argument + end + def required? + !!@required + end + + attr_reader :parent, :name, :aliases, :optparse + attr_accessor :required, :desc + + attr_accessor :before_action + def before_action=(proc) + proc.is_a? Proc or raise ArgumentError, "before action hook for #{self} is a #{proc.class.name.inspect}, not a proc" + @before_action = + @parent.__send__(:__add_method, __decoration_name(:before), proc) + end + + attr_accessor :after_action + def after_action=(proc) + proc.is_a? Proc or raise ArgumentError, "after action hook for #{self} is a #{proc.class.name.inspect}, not a proc" + @after_action = + @parent.__send__(:__add_method, __decoration_name(:after), proc) + end + + def __decoration_name(type) + if @parent.is_a? Puppet::Interface::Action then + :"option #{name} from #{parent.name} #{type} decoration" + else + :"option #{name} #{type} decoration" + end + end end diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb index 2240b3e4a..8f358c222 100644 --- a/lib/puppet/interface/option_builder.rb +++ b/lib/puppet/interface/option_builder.rb @@ -11,15 +11,44 @@ class Puppet::Interface::OptionBuilder def initialize(face, *declaration, &block) @face = face @option = Puppet::Interface::Option.new(face, *declaration) - block and instance_eval(&block) + instance_eval(&block) if block_given? @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... + next if setter =~ /^=/ + dsl = setter.sub(/=$/, '') - dsl = setter.to_s.sub(/=$/, '') - define_method(dsl) do |value| @option.send(setter, value) end + unless private_instance_methods.include? dsl + define_method(dsl) do |value| @option.send(setter, value) end + end + end + + # Override some methods that deal in blocks, not objects. + def before_action(&block) + block or raise ArgumentError, "#{@option} before_action requires a block" + if @option.before_action + raise ArgumentError, "#{@option} already has a before_action set" + end + unless block.arity == 3 then + raise ArgumentError, "before_action takes three arguments, action, args, and options" + end + @option.before_action = block + end + + def after_action(&block) + block or raise ArgumentError, "#{@option} after_action requires a block" + if @option.after_action + raise ArgumentError, "#{@option} already has a after_action set" + end + unless block.arity == 3 then + raise ArgumentError, "after_action takes three arguments, action, args, and options" + end + @option.after_action = block + end + + def required(value = true) + @option.required = value end end diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb index 56df9760f..d42359c07 100644 --- a/lib/puppet/interface/option_manager.rb +++ b/lib/puppet/interface/option_manager.rb @@ -37,10 +37,10 @@ module Puppet::Interface::OptionManager result.sort end - def get_option(name) + def get_option(name, with_inherited_options = true) @options ||= {} result = @options[name.to_sym] - unless result then + if result.nil? and with_inherited_options then if self.is_a?(Class) and superclass.respond_to?(:get_option) result = superclass.get_option(name) elsif self.class.respond_to?(:get_option) diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 2b9e81b61..2c78a0283 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -122,10 +122,10 @@ module Puppet::Network::HTTP::Handler end # Execute our head. - def do_head(indirection_request, request, response) - unless indirection_request.model.head(indirection_request.key, indirection_request.to_hash) - Puppet.info("Could not find #{indirection_request.indirection_name} for '#{indirection_request.key}'") - return do_exception(response, "Could not find #{indirection_request.indirection_name} #{indirection_request.key}", 404) + def do_head(indirection_name, key, params, request, response) + unless self.model(indirection_name).indirection.head(key, params) + Puppet.info("Could not find #{indirection_name} for '#{key}'") + return do_exception(response, "Could not find #{indirection_name} #{key}", 404) end # No need to set a response because no response is expected from a diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 5b0a98615..4bd4d1de6 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -20,6 +20,29 @@ class Puppet::Node attr_accessor :name, :classes, :source, :ipaddress, :parameters attr_reader :time + def self.from_pson(pson) + raise ArgumentError, "No name provided in pson data" unless name = pson['name'] + + node = new(name) + node.classes = pson['classes'] + node.parameters = pson['parameters'] + node.environment = pson['environment'] + node + end + + def to_pson(*args) + result = { + 'document_type' => "Puppet::Node", + 'data' => {} + } + result['data']['name'] = name + result['data']['classes'] = classes unless classes.empty? + result['data']['parameters'] = parameters unless parameters.empty? + result['data']['environment'] = environment.name + + result.to_pson(*args) + end + def environment return super if @environment diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index 577b62b62..2ff7156c8 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -61,18 +61,22 @@ class Puppet::Node::Facts def self.from_pson(data) result = new(data['name'], data['values']) - result.timestamp = Time.parse(data['timestamp']) - result.expiration = Time.parse(data['expiration']) + result.timestamp = Time.parse(data['timestamp']) if data['timestamp'] + result.expiration = Time.parse(data['expiration']) if data['expiration'] result end def to_pson(*args) - { - 'expiration' => expiration, - 'name' => name, - 'timestamp' => timestamp, - 'values' => strip_internal, - }.to_pson(*args) + result = { + 'document_type' => "Puppet::Node::Facts", + 'data' => {} + } + + result['data']['name'] = name + result['data']['expiration'] = expiration if expiration + result['data']['timestamp'] = timestamp if timestamp + result['data']['values'] = strip_internal + result.to_pson(*args) end # Add internal data to the facts for storage. diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index a891f1a11..613fcae74 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -56,23 +56,20 @@ class Puppet::Parser::Compiler # Note that this will fail if the resource is not unique. @catalog.add_resource(resource) + if resource.type.to_s.downcase != "class" && resource[:stage] + raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" + end - # Add our container edge. If we're a class, then we get treated specially - we can - # control the stage that the class is applied in. Otherwise, we just - # get added to our parent container. + # Stages should not be inside of classes. They are always a + # top-level container, regardless of where they appear in the + # manifest. return if resource.type.to_s.downcase == "stage" + # This adds a resource to the class it lexically appears in in the + # manifest. if resource.type.to_s.downcase != "class" - raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" if resource[:stage] return @catalog.add_edge(scope.resource, resource) end - - unless stage = @catalog.resource(:stage, resource[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main) - raise ArgumentError, "Could not find stage #{resource[:stage] || :main} specified by #{resource}" - end - - resource[:stage] ||= stage.title unless stage.title == :main - @catalog.add_edge(stage, resource) end # Do we use nodes found in the code, vs. the external node sources? diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index ace01bb4b..3bb5f8601 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -62,13 +62,30 @@ class Puppet::Parser::Resource < Puppet::Resource scope.environment end + # Process the stage metaparameter for a class. A containment edge + # is drawn from the class to the stage. The stage for containment + # defaults to main, if none is specified. + def add_edge_to_stage + return unless self.type.to_s.downcase == "class" + + unless stage = catalog.resource(:stage, self[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main) + raise ArgumentError, "Could not find stage #{self[:stage] || :main} specified by #{self}" + end + + self[:stage] ||= stage.title unless stage.title == :main + catalog.add_edge(stage, self) + end + # Retrieve the associated definition and evaluate it. def evaluate return if evaluated? @evaluated = true if klass = resource_type and ! builtin_type? finish - return klass.evaluate_code(self) + evaluated_code = klass.evaluate_code(self) + add_edge_to_stage + + return evaluated_code elsif builtin? devfail "Cannot evaluate a builtin type (#{type})" else diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 8de9d60b1..ed67cd141 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -101,7 +101,7 @@ class Puppet::Parser::Scope # Remove this when rebasing def environment - compiler.environment + compiler ? compiler.environment : nil end def find_hostclass(name) @@ -222,12 +222,12 @@ class Puppet::Parser::Scope private :qualified_scope - # Look up a variable. The simplest value search we do. + # 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 =~ /^(.*)::(.+)$/ - begin + begin qualified_scope($1).lookupvar($2,options) rescue RuntimeError => e location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' @@ -238,7 +238,7 @@ class Puppet::Parser::Scope # 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." + Puppet.deprecation_warning "Dynamic lookup of $#{name}#{location} is deprecated. Support will be removed in Puppet 2.8. Use a fully-qualified variable name (e.g., $classname::variable) or parameterized classes." end table[name] elsif parent @@ -443,6 +443,6 @@ class Puppet::Parser::Scope def extend_with_functions_module extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) - extend Puppet::Parser::Functions.environment_module(compiler ? environment : nil) + extend Puppet::Parser::Functions.environment_module(environment) end end diff --git a/lib/puppet/provider/cisco.rb b/lib/puppet/provider/cisco.rb new file mode 100644 index 000000000..918982f59 --- /dev/null +++ b/lib/puppet/provider/cisco.rb @@ -0,0 +1,9 @@ +require 'puppet/util/network_device/cisco/device' +require 'puppet/provider/network_device' + +# This is the base class of all prefetched cisco device providers +class Puppet::Provider::Cisco < Puppet::Provider::NetworkDevice + def self.device(url) + Puppet::Util::NetworkDevice::Cisco::Device.new(url) + end +end diff --git a/lib/puppet/provider/interface/cisco.rb b/lib/puppet/provider/interface/cisco.rb index f3bd202e9..795a7f1ac 100644 --- a/lib/puppet/provider/interface/cisco.rb +++ b/lib/puppet/provider/interface/cisco.rb @@ -1,22 +1,20 @@ -require 'puppet/util/network_device/cisco/device' -require 'puppet/provider/network_device' +require 'puppet/provider/cisco' -Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::NetworkDevice do +Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for interface." mk_resource_methods - def self.lookup(url, name) + def self.lookup(device, name) interface = nil - network_gear = Puppet::Util::NetworkDevice::Cisco::Device.new(url) - network_gear.command do |ng| - interface = network_gear.interface(name) + device.command do |ng| + interface = device.interface(name) end interface end - def initialize(*args) + def initialize(device, *args) super end @@ -26,8 +24,4 @@ Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Netwo end super end - - def device - @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url]) - end end diff --git a/lib/puppet/provider/network_device.rb b/lib/puppet/provider/network_device.rb index 58865fddc..46be27968 100644 --- a/lib/puppet/provider/network_device.rb +++ b/lib/puppet/provider/network_device.rb @@ -2,17 +2,22 @@ # This is the base class of all prefetched network device provider class Puppet::Provider::NetworkDevice < Puppet::Provider - def self.lookup(url, name) + def self.device(url) + raise "This provider doesn't implement the necessary device method" + end + + def self.lookup(device, 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) + device = Puppet::Util::NetworkDevice.current || device(resource[:device_url]) + if result = lookup(device, name) result[:ensure] = :present - resource.provider = new(result) + resource.provider = new(device, result) else - resource.provider = new(:ensure => :absent) + resource.provider = new(device, :ensure => :absent) end end end @@ -21,8 +26,12 @@ class Puppet::Provider::NetworkDevice < Puppet::Provider @property_hash[:ensure] != :absent end - def initialize(*args) - super + attr_accessor :device + + def initialize(device, *args) + super(*args) + + @device = device # Make a duplicate, so that we have a copy for comparison # at the end. @@ -56,4 +65,4 @@ class Puppet::Provider::NetworkDevice < Puppet::Provider def properties @property_hash.dup end -end
\ No newline at end of file +end diff --git a/lib/puppet/provider/package/aptitude.rb b/lib/puppet/provider/package/aptitude.rb index 8bdf984e6..2eafd3ef8 100755 --- a/lib/puppet/provider/package/aptitude.rb +++ b/lib/puppet/provider/package/aptitude.rb @@ -12,6 +12,7 @@ Puppet::Type.type(:package).provide :aptitude, :parent => :apt, :source => :dpkg args.flatten! # Apparently aptitude hasn't always supported a -q flag. args.delete("-q") if args.include?("-q") + args.delete("--force-yes") if args.include?("--force-yes") output = aptitude(*args) # Yay, stupid aptitude doesn't throw an error when the package is missing. diff --git a/lib/puppet/provider/package/pkgutil.rb b/lib/puppet/provider/package/pkgutil.rb new file mode 100755 index 000000000..a1d844f73 --- /dev/null +++ b/lib/puppet/provider/package/pkgutil.rb @@ -0,0 +1,175 @@ +# Packaging using Peter Bonivart's pkgutil program. +Puppet::Type.type(:package).provide :pkgutil, :parent => :sun, :source => :sun do + desc "Package management using Peter Bonivart's ``pkgutil`` command on Solaris." + + pkgutil_bin = "pkgutil" + if FileTest.executable?("/opt/csw/bin/pkgutil") + pkgutil_bin = "/opt/csw/bin/pkgutil" + end + + confine :operatingsystem => :solaris + + commands :pkguti => pkgutil_bin + + def self.healthcheck() + unless FileTest.exists?("/var/opt/csw/pkgutil/admin") + Puppet.notice "It is highly recommended you create '/var/opt/csw/pkgutil/admin'." + Puppet.notice "See /var/opt/csw/pkgutil" + end + + correct_wgetopts = false + [ "/opt/csw/etc/pkgutil.conf", "/etc/opt/csw/pkgutil.conf" ].each do |confpath| + File.open(confpath) do |conf| + conf.each {|line| correct_wgetopts = true if line =~ /^\s*wgetopts\s*=.*(-nv|-q|--no-verbose|--quiet)/ } + end + end + if ! correct_wgetopts + Puppet.notice "It is highly recommended that you set 'wgetopts=-nv' in your pkgutil.conf." + end + end + + def self.instances(hash = {}) + healthcheck + + # Use the available pkg list (-a) to work out aliases + aliases = {} + availlist.each do |pkg| + aliases[pkg[:name]] = pkg[:alias] + end + + # The -c pkglist lists installed packages + pkginsts = [] + pkglist(hash).each do |pkg| + pkg.delete(:avail) + pkginsts << new(pkg) + + # Create a second instance with the alias if it's different + pkgalias = aliases[pkg[:name]] + if pkgalias and pkg[:name] != pkgalias + apkg = pkg.dup + apkg[:name] = pkgalias + pkginsts << new(apkg) + end + end + + pkginsts + end + + # Turns a pkgutil -a listing into hashes with the common alias, full + # package name and available version + def self.availlist + output = pkguti ["-a"] + + list = output.split("\n").collect do |line| + next if line =~ /^common\s+package/ # header of package list + next if noise?(line) + + if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/ + { :alias => $1, :name => $2, :avail => $3 } + else + Puppet.warning "Cannot match %s" % line + end + end.reject { |h| h.nil? } + end + + # Turn our pkgutil -c listing into a bunch of hashes. + # Supports :justme => packagename, which uses the optimised --single arg + def self.pkglist(hash) + command = ["-c"] + + if hash[:justme] + # The --single option speeds up the execution, because it queries + # the package managament system for one package only. + command << "--single" + command << hash[:justme] + end + + output = pkguti(command).split("\n") + + if output[-1] == "Not in catalog" + Puppet.warning "Package not in pkgutil catalog: %s" % hash[:justme] + return nil + end + + list = output.collect do |line| + next if line =~ /installed\s+catalog/ # header of package list + next if noise?(line) + + pkgsplit(line) + end.reject { |h| h.nil? } + + if hash[:justme] + # Single queries may have been for an alias so return the name requested + if list.any? + list[-1][:name] = hash[:justme] + return list[-1] + end + else + list.reject! { |h| h[:ensure] == :absent } + return list + end + end + + # Identify common types of pkgutil noise as it downloads catalogs etc + def self.noise?(line) + true if line =~ /^#/ + true if line =~ /^Checking integrity / # use_gpg + true if line =~ /^gpg: / # gpg verification + true if line =~ /^=+> / # catalog fetch + true if line =~ /\d+:\d+:\d+ URL:/ # wget without -q + false + end + + # Split the different lines into hashes. + def self.pkgsplit(line) + if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/ + hash = {} + hash[:name] = $1 + hash[:ensure] = if $2 == "notinst" + :absent + else + $2 + end + hash[:avail] = $3 + + if hash[:avail] =~ /^SAME\s*$/ + hash[:avail] = hash[:ensure] + end + + # Use the name method, so it works with subclasses. + hash[:provider] = self.name + + return hash + else + Puppet.warning "Cannot match %s" % line + return nil + end + end + + def install + pkguti "-y", "-i", @resource[:name] + end + + # Retrieve the version from the current package file. + def latest + hash = self.class.pkglist(:justme => @resource[:name]) + hash[:avail] if hash + end + + def query + if hash = self.class.pkglist(:justme => @resource[:name]) + hash + else + {:ensure => :absent} + end + end + + def update + pkguti "-y", "-u", @resource[:name] + end + + def uninstall + pkguti "-y", "-r", @resource[:name] + end +end + diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb index 46e172c73..3421d35b0 100644 --- a/lib/puppet/provider/vlan/cisco.rb +++ b/lib/puppet/provider/vlan/cisco.rb @@ -1,22 +1,20 @@ -require 'puppet/util/network_device/cisco/device' -require 'puppet/provider/network_device' +require 'puppet/provider/cisco' -Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::NetworkDevice do +Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for vlans." mk_resource_methods - def self.lookup(url, id) + def self.lookup(device, 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) + def initialize(device, *args) super end @@ -27,8 +25,4 @@ Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::NetworkDev end super end - - def device - @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url]) - end end diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb index 98c29657e..a6cff9bdc 100644 --- a/lib/puppet/resource/catalog.rb +++ b/lib/puppet/resource/catalog.rb @@ -133,6 +133,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph transaction.report = options[:report] if options[:report] transaction.tags = options[:tags] if options[:tags] transaction.ignoreschedules = true if options[:ignoreschedules] + transaction.for_network_device = options[:network_device] transaction.add_times :config_retrieval => self.retrieval_duration || 0 diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index d7845fbc9..3728a2fff 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -12,7 +12,7 @@ class Puppet::Transaction require 'puppet/transaction/resource_harness' require 'puppet/resource/status' - attr_accessor :component, :catalog, :ignoreschedules + attr_accessor :component, :catalog, :ignoreschedules, :for_network_device attr_accessor :configurator # The report, once generated. @@ -339,6 +339,8 @@ class Puppet::Transaction resource.warning "Skipping because of failed dependencies" elsif resource.virtual? resource.debug "Skipping because virtual" + elsif resource.appliable_to_device? ^ for_network_device + resource.debug "Skipping #{resource.appliable_to_device? ? 'device' : 'host'} resources because running on a #{for_network_device ? 'device' : 'host'}" else return false end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index c0e5d390b..656b8f264 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -116,6 +116,26 @@ class Type ens end + def self.apply_to_device + @apply_to = :device + end + + def self.apply_to_host + @apply_to = :host + end + + def self.apply_to_all + @apply_to = :both + end + + def self.apply_to + @apply_to ||= :host + end + + def self.can_apply_to(target) + [ target == :device ? :device : :host, :both ].include?(apply_to) + end + # Deal with any options passed into parameters. def self.handle_param_options(name, options) # If it's a boolean parameter, create a method to test the value easily @@ -1895,6 +1915,14 @@ class Type def virtual?; !!@virtual; end def exported?; !!@exported; end + + def appliable_to_device? + self.class.can_apply_to(:device) + end + + def appliable_to_host? + self.class.can_apply_to(:host) + end end end diff --git a/lib/puppet/type/interface.rb b/lib/puppet/type/interface.rb index 7560a0552..d3b5cb06f 100644 --- a/lib/puppet/type/interface.rb +++ b/lib/puppet/type/interface.rb @@ -10,6 +10,8 @@ Puppet::Type.newtype(:interface) do interface mode (access or trunking, native vlan and encapsulation), switchport characteristics (speed, duplex)." + apply_to_device + ensurable do defaultvalues diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb index 5fb008f6f..f60f96fd2 100755 --- a/lib/puppet/type/schedule.rb +++ b/lib/puppet/type/schedule.rb @@ -43,6 +43,8 @@ module Puppet This will cause resources to be applied every 30 minutes by default. " + apply_to_all + newparam(:name) do desc "The name of the schedule. This name is used to retrieve the schedule when assigning it to an object: diff --git a/lib/puppet/type/vlan.rb b/lib/puppet/type/vlan.rb index 6708ea4f5..e39daf994 100644 --- a/lib/puppet/type/vlan.rb +++ b/lib/puppet/type/vlan.rb @@ -5,6 +5,8 @@ Puppet::Type.newtype(:vlan) do @doc = "This represents a router or switch vlan." + apply_to_device + ensurable newparam(:name) do diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index a884b8658..714d03f74 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -14,7 +14,8 @@ module Puppet 'queue' => 'puppetqd', 'resource' => 'ralsh', 'kick' => 'puppetrun', - 'master' => 'puppetmasterd' + 'master' => 'puppetmasterd', + 'device' => 'puppetdevice' ) def initialize(zero = $0, argv = ARGV, stdin = STDIN) diff --git a/lib/puppet/util/network_device.rb b/lib/puppet/util/network_device.rb index bca66016b..7fb8e2ff3 100644 --- a/lib/puppet/util/network_device.rb +++ b/lib/puppet/util/network_device.rb @@ -1,2 +1,17 @@ -module Puppet::Util::NetworkDevice -end
\ No newline at end of file +class Puppet::Util::NetworkDevice + class << self + attr_reader :current + end + + def self.init(device) + require "puppet/util/network_device/#{device.provider}/device" + @current = Puppet::Util::NetworkDevice.const_get(device.provider.capitalize).const_get(:Device).new(device.url) + rescue => detail + raise "Can't load #{device.provider} for #{device.name}: #{detail}" + end + + # Should only be used in tests + def self.teardown + @current = nil + end +end diff --git a/lib/puppet/util/network_device/base.rb b/lib/puppet/util/network_device/base.rb index ff96c8693..7d6c3fc44 100644 --- a/lib/puppet/util/network_device/base.rb +++ b/lib/puppet/util/network_device/base.rb @@ -3,27 +3,25 @@ require 'uri' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' -module Puppet::Util::NetworkDevice - class Base +class Puppet::Util::NetworkDevice::Base - attr_accessor :url, :transport + attr_accessor :url, :transport - def initialize(url) - @url = URI.parse(url) + def initialize(url) + @url = URI.parse(url) - @autoloader = Puppet::Util::Autoload.new( - self, - "puppet/util/network_device/transport", - :wrap => false - ) + @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 + 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
\ 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 index 1f350991a..005470e13 100644 --- a/lib/puppet/util/network_device/cisco/device.rb +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -3,6 +3,7 @@ require 'puppet/util' require 'puppet/util/network_device/base' require 'puppet/util/network_device/ipcalc' require 'puppet/util/network_device/cisco/interface' +require 'puppet/util/network_device/cisco/facts' require 'ipaddr' class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice::Base @@ -91,6 +92,15 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: interface end + def facts + @facts ||= Puppet::Util::NetworkDevice::Cisco::Facts.new(transport) + facts = {} + command do |ng| + facts = @facts.retrieve + end + facts + end + def interface(name) ifname = canonalize_ifname(name) interface = parse_interface(ifname) diff --git a/lib/puppet/util/network_device/cisco/facts.rb b/lib/puppet/util/network_device/cisco/facts.rb new file mode 100644 index 000000000..40e2b37c2 --- /dev/null +++ b/lib/puppet/util/network_device/cisco/facts.rb @@ -0,0 +1,72 @@ + +require 'puppet/util/network_device/cisco' +require 'puppet/util/network_device/ipcalc' + +# this retrieves facts from a cisco device +class Puppet::Util::NetworkDevice::Cisco::Facts + + attr_reader :transport + + def initialize(transport) + @transport = transport + end + + def retrieve + facts = {} + facts.merge(parse_show_ver) + end + + def parse_show_ver + facts = {} + out = @transport.command("sh ver") + lines = out.split("\n") + lines.shift; lines.pop + lines.each do |l| + case l + # cisco WS-C2924C-XL (PowerPC403GA) processor (revision 0x11) with 8192K/1024K bytes of memory. + # Cisco 1841 (revision 5.0) with 355328K/37888K bytes of memory. + # Cisco 877 (MPC8272) processor (revision 0x200) with 118784K/12288K bytes of memory. + # cisco WS-C2960G-48TC-L (PowerPC405) processor (revision C0) with 61440K/4088K bytes of memory. + # cisco WS-C2950T-24 (RC32300) processor (revision R0) with 19959K bytes of memory. + when /[cC]isco ([\w-]+) (?:\(([\w-]+)\) processor )?\(revision (.+)\) with (\d+[KMG])(?:\/(\d+[KMG]))? bytes of memory\./ + facts[:hardwaremodel] = $1 + facts[:processor] = $2 if $2 + facts[:hardwarerevision] = $3 + facts[:memorysize] = $4 + # uptime + # Switch uptime is 1 year, 12 weeks, 6 days, 22 hours, 32 minutes + # c2950 uptime is 3 weeks, 1 day, 23 hours, 36 minutes + # c2960 uptime is 2 years, 27 weeks, 5 days, 21 hours, 30 minutes + # router uptime is 5 weeks, 1 day, 3 hours, 30 minutes + when /^\s*([\w-]+)\s+uptime is (.*?)$/ + facts[:hostname] = $1 + facts[:uptime] = $2 + facts[:uptime_seconds] = uptime_to_seconds($2) + facts[:uptime_days] = facts[:uptime_seconds] / 86400 + # "IOS (tm) C2900XL Software (C2900XL-C3H2S-M), Version 12.0(5)WC10, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.0(5)WC10", :operatingsystemmajrelease => "12.0", :operatingsystemfeature => "C3H2S"}, + # "IOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA8a, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.1(22)EA8a", :operatingsystemmajrelease => "12.1", :operatingsystemfeature => "I6K2L2Q4"}, + # "Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(44)SE, RELEASE SOFTWARE (fc1)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.2(44)SE", :operatingsystemmajrelease => "12.2", :operatingsystemfeature => "LANBASEK9"}, + # "Cisco IOS Software, C870 Software (C870-ADVIPSERVICESK9-M), Version 12.4(11)XJ4, RELEASE SOFTWARE (fc2)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(11)XJ40", :operatingsystemmajrelease => "12.4XJ", :operatingsystemfeature => "ADVIPSERVICESK9"}, + # "Cisco IOS Software, 1841 Software (C1841-ADVSECURITYK9-M), Version 12.4(24)T4, RELEASE SOFTWARE (fc2)" =>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(24)T4", :operatingsystemmajrelease => "12.4T", :operatingsystemfeature => "ADVSECURITYK9"}, + when /(?:Cisco )?(IOS)\s*(?:\(tm\) |Software, )?(?:\w+)\s+Software\s+\(\w+-(\w+)-\w+\), Version ([0-9.()A-Za-z]+),/ + facts[:operatingsystem] = $1 + facts[:operatingsystemrelease] = $3 + facts[:operatingsystemmajrelease] = ios_major_version(facts[:operatingsystemrelease]) + facts[:operatingsystemfeature] = $2 + end + end + facts + end + + def ios_major_version(version) + version.gsub(/^(\d+)\.(\d+)\(.+\)([A-Z]+)([\da-z]+)?/, '\1.\2\3') + end + + def uptime_to_seconds(uptime) + captures = (uptime.match /^(?:(?:(?:(?:(\d+) years?,)?\s*(\d+) weeks?,)?\s*(\d+) days?,)?\s*(\d+) hours?,)?\s*(\d+) minutes?$/).captures + seconds = captures.zip([31536000, 604800, 86400, 3600, 60]).inject(0) do |total, (x,y)| + total + (x.nil? ? 0 : x.to_i * y) + end + end + +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/config.rb b/lib/puppet/util/network_device/config.rb new file mode 100644 index 000000000..17f4e254c --- /dev/null +++ b/lib/puppet/util/network_device/config.rb @@ -0,0 +1,93 @@ +require 'ostruct' +require 'puppet/util/loadedfile' + +class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile + + def self.main + @main ||= self.new + end + + def self.devices + main.devices || [] + end + + attr_reader :devices + + def exists? + FileTest.exists?(@file) + end + + def initialize() + @file = Puppet[:deviceconfig] + + raise Puppet::DevError, "No device config file defined" unless @file + return unless self.exists? + super(@file) + @devices = {} + + read(true) # force reading at start + end + + # Read the configuration file. + def read(force = false) + return unless FileTest.exists?(@file) + + parse if force or changed? + end + + private + + def parse + begin + devices = {} + device = nil + File.open(@file) { |f| + count = 1 + f.each { |line| + case line + when /^\s*#/ # skip comments + count += 1 + next + when /^\s*$/ # skip blank lines + count += 1 + next + when /^\[([\w.]+)\]\s*$/ # [device.fqdn] + name = $1 + name.chomp! + raise ConfigurationError, "Duplicate device found at line #{count}, already found at #{device.line}" if devices.include?(name) + device = OpenStruct.new + device.name = name + device.line = count + Puppet.debug "found device: #{device.name} at #{device.line}" + devices[name] = device + when /^\s*(type|url)\s+(.+)$/ + parse_directive(device, $1, $2, count) + else + raise ConfigurationError, "Invalid line #{count}: #{line}" + end + count += 1 + } + } + rescue Errno::EACCES => detail + Puppet.err "Configuration error: Cannot read #{@file}; cannot serve" + #raise Puppet::Error, "Cannot read #{@config}" + rescue Errno::ENOENT => detail + Puppet.err "Configuration error: '#{@file}' does not exit; cannot serve" + end + + @devices = devices + end + + def parse_directive(device, var, value, count) + case var + when "type" + device.provider = value + when "url" + device.url = value + else + raise ConfigurationError, + "Invalid argument '#{var}' at line #{count}" + end + 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 index e64fe9b69..cef8f3859 100644 --- a/lib/puppet/util/network_device/transport.rb +++ b/lib/puppet/util/network_device/transport.rb @@ -1,5 +1,3 @@ # stub -module Puppet::Util::NetworkDevice - module Transport - end +module Puppet::Util::NetworkDevice::Transport 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 index b3cf51b8a..bf0e7193c 100644 --- a/lib/puppet/util/network_device/transport/ssh.rb +++ b/lib/puppet/util/network_device/transport/ssh.rb @@ -31,6 +31,10 @@ class Puppet::Util::NetworkDevice::Transport::Ssh < Puppet::Util::NetworkDevice: @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" + rescue Net::SSH::AuthenticationFailed + raise Puppet::Error, "SSH authentication failure connecting to #{host} as #{user}" + rescue Net::SSH::Exception => detail + raise Puppet::Error, "SSH connection failure to #{host}" end @buf = "" diff --git a/lib/puppet/util/queue.rb b/lib/puppet/util/queue.rb index 02357742a..636bdcf2e 100644 --- a/lib/puppet/util/queue.rb +++ b/lib/puppet/util/queue.rb @@ -30,7 +30,7 @@ require 'puppet/util/instance_loader' # # The client plugins are expected to implement an interface similar to that of Stomp::Client: # * <tt>new</tt> should return a connected, ready-to-go client instance. Note that no arguments are passed in. -# * <tt>send_message(queue, message)</tt> should send the _message_ to the specified _queue_. +# * <tt>publish_message(queue, message)</tt> should publish the _message_ to the specified _queue_. # * <tt>subscribe(queue)</tt> _block_ subscribes to _queue_ and executes _block_ upon receiving a message. # * _queue_ names are simple names independent of the message broker or client library. No "/queue/" prefixes like in Stomp::Client. module Puppet::Util::Queue diff --git a/lib/puppet/util/queue/stomp.rb b/lib/puppet/util/queue/stomp.rb index c18edae6a..cabc56627 100644 --- a/lib/puppet/util/queue/stomp.rb +++ b/lib/puppet/util/queue/stomp.rb @@ -28,8 +28,8 @@ class Puppet::Util::Queue::Stomp end end - def send_message(target, msg) - stomp_client.send(stompify_target(target), msg, :persistent => true) + def publish_message(target, msg) + stomp_client.publish(stompify_target(target), msg, :persistent => true) end def subscribe(target) |
