summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJosh Cooper <josh@puppetlabs.com>2011-04-21 14:37:50 -0700
committerJosh Cooper <josh@puppetlabs.com>2011-04-21 14:37:50 -0700
commit01f610bb223b435dc52f491260af3ea002930102 (patch)
tree93565136d5ef2b4156fdd64476792e441bcfbb4e /lib
parentac428b9557e2da251e4b51e48de844833ca0aa2a (diff)
parentfc66e98b84b9a16728af054485883334a5887cca (diff)
downloadpuppet-01f610bb223b435dc52f491260af3ea002930102.tar.gz
puppet-01f610bb223b435dc52f491260af3ea002930102.tar.xz
puppet-01f610bb223b435dc52f491260af3ea002930102.zip
Merge branch 'next'
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/application/cert.rb98
-rw-r--r--lib/puppet/application/device.rb255
-rw-r--r--lib/puppet/application/face_base.rb87
-rw-r--r--lib/puppet/application/kick.rb35
-rw-r--r--lib/puppet/defaults.rb19
-rw-r--r--lib/puppet/face/certificate.rb28
-rw-r--r--lib/puppet/face/facts.rb4
-rw-r--r--lib/puppet/face/help.rb26
-rw-r--r--lib/puppet/face/indirector.rb15
-rw-r--r--lib/puppet/face/node.rb2
-rw-r--r--lib/puppet/face/parser.rb5
-rw-r--r--lib/puppet/indirector/facts/network_device.rb25
-rw-r--r--lib/puppet/indirector/queue.rb2
-rw-r--r--lib/puppet/indirector/request.rb51
-rw-r--r--lib/puppet/interface.rb92
-rw-r--r--lib/puppet/interface/action.rb124
-rw-r--r--lib/puppet/interface/action_builder.rb44
-rw-r--r--lib/puppet/interface/action_manager.rb9
-rw-r--r--lib/puppet/interface/option.rb56
-rw-r--r--lib/puppet/interface/option_builder.rb37
-rw-r--r--lib/puppet/interface/option_manager.rb4
-rw-r--r--lib/puppet/network/http/handler.rb8
-rw-r--r--lib/puppet/node.rb23
-rwxr-xr-xlib/puppet/node/facts.rb20
-rw-r--r--lib/puppet/parser/compiler.rb19
-rw-r--r--lib/puppet/parser/resource.rb19
-rw-r--r--lib/puppet/parser/scope.rb10
-rw-r--r--lib/puppet/provider/cisco.rb9
-rw-r--r--lib/puppet/provider/interface/cisco.rb18
-rw-r--r--lib/puppet/provider/network_device.rb23
-rwxr-xr-xlib/puppet/provider/package/aptitude.rb1
-rwxr-xr-xlib/puppet/provider/package/pkgutil.rb175
-rw-r--r--lib/puppet/provider/vlan/cisco.rb14
-rw-r--r--lib/puppet/resource/catalog.rb1
-rw-r--r--lib/puppet/transaction.rb4
-rw-r--r--lib/puppet/type.rb28
-rw-r--r--lib/puppet/type/interface.rb2
-rwxr-xr-xlib/puppet/type/schedule.rb2
-rw-r--r--lib/puppet/type/vlan.rb2
-rw-r--r--lib/puppet/util/command_line.rb3
-rw-r--r--lib/puppet/util/network_device.rb19
-rw-r--r--lib/puppet/util/network_device/base.rb32
-rw-r--r--lib/puppet/util/network_device/cisco/device.rb10
-rw-r--r--lib/puppet/util/network_device/cisco/facts.rb72
-rw-r--r--lib/puppet/util/network_device/config.rb93
-rw-r--r--lib/puppet/util/network_device/transport.rb4
-rw-r--r--lib/puppet/util/network_device/transport/ssh.rb4
-rw-r--r--lib/puppet/util/queue.rb2
-rw-r--r--lib/puppet/util/queue/stomp.rb4
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)