Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'next'

  • Loading branch information...
commit 01f610bb223b435dc52f491260af3ea002930102 2 parents ac428b9 + fc66e98
Josh Cooper joshcooper authored
Showing with 3,597 additions and 808 deletions.
  1. +0 −115 README.strings
  2. +50 −0 acceptance/pending/ticket_3360_allow_duplicate_csr_with_option_set.rb
  3. 0  acceptance/pending/ticket_3360_reject_duplicate_csr_with_option_unset.rb
  4. +28 −0 acceptance/pending/ticket_5027_warn_on_dynamic_scope.rb
  5. +38 −0 acceptance/pending/ticket_6928_puppet_master_parse_fails.rb
  6. +39 −0 acceptance/tests/stages/ticket_4655_default_stage_for_classes.rb
  7. +51 −47 lib/puppet/application/cert.rb
  8. +255 −0 lib/puppet/application/device.rb
  9. +64 −23 lib/puppet/application/face_base.rb
  10. +10 −25 lib/puppet/application/kick.rb
  11. +12 −7 lib/puppet/defaults.rb
  12. +11 −17 lib/puppet/face/certificate.rb
  13. +2 −2 lib/puppet/face/facts.rb
  14. +26 −0 lib/puppet/face/help.rb
  15. +8 −7 lib/puppet/face/indirector.rb
  16. +0 −2  lib/puppet/face/node.rb
  17. +4 −1 lib/puppet/face/parser.rb
  18. +25 −0 lib/puppet/indirector/facts/network_device.rb
  19. +1 −1  lib/puppet/indirector/queue.rb
  20. +49 −2 lib/puppet/indirector/request.rb
  21. +66 −26 lib/puppet/interface.rb
  22. +113 −11 lib/puppet/interface/action.rb
  23. +41 −3 lib/puppet/interface/action_builder.rb
  24. +9 −0 lib/puppet/interface/action_manager.rb
  25. +39 −17 lib/puppet/interface/option.rb
  26. +33 −4 lib/puppet/interface/option_builder.rb
  27. +2 −2 lib/puppet/interface/option_manager.rb
  28. +4 −4 lib/puppet/network/http/handler.rb
  29. +23 −0 lib/puppet/node.rb
  30. +12 −8 lib/puppet/node/facts.rb
  31. +8 −11 lib/puppet/parser/compiler.rb
  32. +18 −1 lib/puppet/parser/resource.rb
  33. +5 −5 lib/puppet/parser/scope.rb
  34. +9 −0 lib/puppet/provider/cisco.rb
  35. +6 −12 lib/puppet/provider/interface/cisco.rb
  36. +16 −7 lib/puppet/provider/network_device.rb
  37. +1 −0  lib/puppet/provider/package/aptitude.rb
  38. +175 −0 lib/puppet/provider/package/pkgutil.rb
  39. +4 −10 lib/puppet/provider/vlan/cisco.rb
  40. +1 −0  lib/puppet/resource/catalog.rb
  41. +3 −1 lib/puppet/transaction.rb
  42. +28 −0 lib/puppet/type.rb
  43. +2 −0  lib/puppet/type/interface.rb
  44. +2 −0  lib/puppet/type/schedule.rb
  45. +2 −0  lib/puppet/type/vlan.rb
  46. +2 −1  lib/puppet/util/command_line.rb
  47. +17 −2 lib/puppet/util/network_device.rb
  48. +15 −17 lib/puppet/util/network_device/base.rb
  49. +10 −0 lib/puppet/util/network_device/cisco/device.rb
  50. +72 −0 lib/puppet/util/network_device/cisco/facts.rb
  51. +93 −0 lib/puppet/util/network_device/config.rb
  52. +1 −3 lib/puppet/util/network_device/transport.rb
  53. +4 −0 lib/puppet/util/network_device/transport/ssh.rb
  54. +1 −1  lib/puppet/util/queue.rb
  55. +2 −2 lib/puppet/util/queue/stomp.rb
  56. +2 −2 spec/integration/indirector/catalog/queue_spec.rb
  57. +56 −0 spec/integration/transaction_spec.rb
  58. +111 −0 spec/lib/matchers/json.rb
  59. +1 −0  spec/lib/puppet/face/basetest.rb
  60. +35 −0 spec/shared_behaviours/documentation_on_faces.rb
  61. +29 −25 spec/shared_behaviours/things_that_declare_options.rb
  62. +349 −0 spec/unit/application/device_spec.rb
  63. +96 −9 spec/unit/application/face_base_spec.rb
  64. +3 −4 spec/unit/application/indirection_base_spec.rb
  65. +7 −2 spec/unit/face/certificate_spec.rb
  66. +1 −5 spec/unit/face/facts_spec.rb
  67. +14 −10 spec/unit/face/help_spec.rb
  68. +1 −3 spec/unit/face/node_spec.rb
  69. +89 −0 spec/unit/indirector/facts/network_device_spec.rb
  70. +4 −4 spec/unit/indirector/queue_spec.rb
  71. +96 −0 spec/unit/indirector/request_spec.rb
  72. +5 −0 spec/unit/indirector/rest_spec.rb
  73. +159 −35 spec/unit/interface/action_builder_spec.rb
  74. +20 −0 spec/unit/interface/action_manager_spec.rb
  75. +223 −3 spec/unit/interface/action_spec.rb
  76. +0 −4 spec/unit/interface/face_collection_spec.rb
  77. +56 −10 spec/unit/interface/option_builder_spec.rb
  78. +25 −0 spec/unit/interface/option_spec.rb
  79. +16 −26 spec/unit/interface_spec.rb
  80. +7 −11 spec/unit/network/http/handler_spec.rb
  81. +30 −4 spec/unit/node/facts_spec.rb
  82. +64 −0 spec/unit/node_spec.rb
  83. +8 −46 spec/unit/parser/compiler_spec.rb
  84. +61 −3 spec/unit/parser/resource_spec.rb
  85. +5 −1 spec/unit/parser/scope_spec.rb
  86. +16 −0 spec/unit/provider/cisco_spec.rb
  87. +7 −13 spec/unit/provider/interface/cisco_spec.rb
  88. +13 −8 spec/unit/provider/network_device_spec.rb
  89. +182 −0 spec/unit/provider/package/pkgutil_spec.rb
  90. +7 −13 spec/unit/provider/vlan/cisco_spec.rb
  91. +1 −0  spec/unit/resource/catalog_spec.rb
  92. +18 −0 spec/unit/transaction_spec.rb
  93. +5 −0 spec/unit/type/interface_spec.rb
  94. +8 −0 spec/unit/type/schedule_spec.rb
  95. +5 −0 spec/unit/type/vlan_spec.rb
  96. +12 −125 spec/unit/util/network_device/cisco/device_spec.rb
  97. +63 −0 spec/unit/util/network_device/cisco/facts_spec.rb
  98. +102 −0 spec/unit/util/network_device/config_spec.rb
  99. +8 −0 spec/unit/util/network_device/transport/ssh_spec.rb
  100. +50 −0 spec/unit/util/network_device_spec.rb
  101. +10 −10 spec/unit/util/queue/stomp_spec.rb
  102. +5 −1 spec/watchr.rb
  103. +0 −34 test/lib/puppettest/railstesting.rb
115 README.strings
View
@@ -1,115 +0,0 @@
-Puppet Strings
-=================
-A set of executables that provide complete CLI access to Puppet's
-core data types. They also provide String classes for
-each of the core data types, which are extensible via plugins.
-
-For instance, you can create a new action for catalogs at
-lib/puppet/string/catalog/$action.rb.
-
-This is a Puppet module and should work fine if you install it
-in Puppet's module path.
-
-**Note that this only works with Puppet 2.6.next (and thus will work
-with 2.6.5), because there is otherwise a bug in finding Puppet applications.
-You also have to either install the lib files into your Puppet libdir, or
-you need to add this lib directory to your RUBYLIB.**
-
-This is meant to be tested and iterated upon, with the plan that it will be
-merged into Puppet core once we're satisfied with it.
-
-Usage
------
-The general usage is:
-
- $ puppet <string> <verb> <name>
-
-So, e.g.:
-
- $ puppet facts find myhost.domain.com
- $ puppet node destroy myhost
-
-You can use it to list all known data types and the available terminus classes:
-
- $ puppet string list
- catalog : active_record, compiler, queue, rest, yaml
- certificate : ca, file, rest
- certificate_request : ca, file, rest
- certificate_revocation_list : ca, file, rest
- file_bucket_file : file, rest
- inventory : yaml
- key : ca, file
- node : active_record, exec, ldap, memory, plain, rest, yaml
- report : processor, rest, yaml
- resource : ral, rest
- resource_type : parser, rest
- status : local, rest
-
-But most interestingly, you can use it for two main purposes:
-
-* As a client for any Puppet REST server, such as catalogs, facts, reports, etc.
-* As a local CLI for any local Puppet data
-
-A simple case is looking at the local facts:
-
- $ puppet facts find localhost
-
-If you're on the server, you can look in that server's fact collection:
-
- $ puppet facts --mode master --vardir /tmp/foo --terminus yaml find localhost
-
-Note that we're setting both the vardir and the 'mode', which switches from the default 'agent' mode to server mode (requires a patch in my branch).
-
-If you'd prefer the data be outputted in json instead of yaml, well, you can do that, too:
-
- $ puppet find --mode master facts --vardir /tmp/foo --terminus yaml --format pson localhost
-
-To test using it as an endpoint for compiling and retrieving catalogs from a remote server, (from my commit), try this:
-
- # Terminal 1
- $ sbin/puppetmasterd --trace --confdir /tmp/foo --vardir /tmp/foo --debug --manifest ~/bin/test.pp --certname localhost --no-daemonize
-
- # Terminal 2
- $ sbin/puppetd --trace --debug --confdir /tmp/foo --vardir /tmp/foo --certname localhost --server localhost --test --report
-
- # Terminal 3, actual testing
- $ puppet catalog find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest
-
-This compiles a test catalog (assuming that ~/bin/test.pp exists) and returns it. With the right auth setup, you can also get facts:
-
- $ puppet facts find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest
-
-Or use IRB to do the same thing:
-
- $ irb
- >> require 'puppet/string'
- => true
- >> string = Puppet::String[:facts, '1.0.0']
- => #<Puppet::String::Facts:0x1024a1390 @format=:yaml>
- >> facts = string.find("myhost")
-
-Like I said, a prototype, but I'd love it if people would play it with some and make some recommendations.
-
-Extending
----------
-Like most parts of Puppet, these are easy to extend. Just drop a new action into a given string's directory. E.g.:
-
- $ cat lib/puppet/string/catalog/select.rb
- # Select and show a list of resources of a given type.
- Puppet::String.define(:catalog, '1.0.0') do
- action :select do
- invoke do |host,type|
- catalog = Puppet::Resource::Catalog.indirection.find(host)
-
- catalog.resources.reject { |res| res.type != type }.each { |res| puts res }
- end
- end
- end
- $ puppet catalog select localhost Class
- Class[main]
- Class[Settings]
- $
-
-Notice that this gets loaded automatically when you try to use it. So, if you have a simple command you've written, such as for cleaning up nodes or diffing catalogs, you an port it to this framework and it should fit cleanly.
-
-Also note that strings are versioned. These version numbers are interpreted according to Semantic Versioning (http://semver.org).
50 acceptance/pending/ticket_3360_allow_duplicate_csr_with_option_set.rb
View
@@ -0,0 +1,50 @@
+test_name "#3360: Allow duplicate CSR when allow_duplicate_certs is on"
+
+agent_hostnames = agents.map {|a| a.to_s}
+
+# Kill running Puppet Master -- should not be running at this point
+step "Master: kill running Puppet Master"
+on master, "ps -U puppet | awk '/puppet/ { print \$1 }' | xargs kill || echo \"Puppet Master not running\""
+
+step "Master: Start Puppet Master"
+on master, puppet_master("--allow_duplicate_certs --certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --noop")
+
+step "Generate a certificate request for the agent"
+on agents, "puppet certificate generate `hostname -f` --ca-location remote --server #{master}"
+
+step "Collect the original certs"
+on master, puppet_cert("--sign --all")
+original_certs = on master, puppet_cert("--list --all")
+
+old_certs = {}
+original_certs.stdout.each_line do |line|
+ if line =~ /^\+ (\S+) \((.+)\)$/
+ old_certs[$1] = $2
+ puts "old cert: #{$1} #{$2}"
+ end
+end
+
+step "Make another request with the same certname"
+on agents, "puppet certificate generate `hostname -f` --ca-location remote --server #{master}"
+
+step "Collect the new certs"
+
+on master, puppet_cert("--sign --all")
+new_cert_list = on master, puppet_cert("--list --all")
+
+new_certs = {}
+new_cert_list.stdout.each_line do |line|
+ if line =~ /^\+ (\S+) \((.+)\)$/
+ new_certs[$1] = $2
+ puts "new cert: #{$1} #{$2}"
+ end
+end
+
+step "Verify the certs have changed"
+# using the agent name as the key may cause errors;
+# agent name from cfg file is likely to have short name
+# where certs might be signed with long names.
+old_certs.each_key { |key|
+ next if key.include? master # skip the masters cert, only care about agents
+ fail_test("#{key} does not have a new signed certificate") if old_certs[key] == new_certs[key]
+}
0  acceptance/pending/ticket_3360_reject_duplicate_csr_with_option_unset.rb
View
No changes.
28 acceptance/pending/ticket_5027_warn_on_dynamic_scope.rb
View
@@ -0,0 +1,28 @@
+test_name "#5027: Issue warnings when using dynamic scope"
+
+step "Apply dynamic scoping manifest on agents"
+apply_manifest_on agents, %q{
+ $foo = 'foo_value'
+
+ class a {
+ $bar = 'bar_value'
+
+ include b
+ }
+
+ class b inherits c {
+ notify { $baz: } # should not generate a warning -- inherited from class c
+ notify { $bar: } # should generate a warning -- uses dynamic scoping
+ notify { $foo: } # should not generate a warning -- comes from top scope
+ }
+
+ class c {
+ $baz = 'baz_value'
+ }
+
+ include a
+}
+
+step "Verify deprecation warning"
+fail_test "Deprecation warning not issued" unless
+ stdout.include? 'warning: Dynamic lookup of $bar will not be supported in future versions. Use a fully-qualified variable name or parameterized classes.'
38 acceptance/pending/ticket_6928_puppet_master_parse_fails.rb
View
@@ -0,0 +1,38 @@
+test_name "#6928: Puppet --parseonly should return deprication message"
+
+# Create good and bad formatted manifests
+step "Master: create valid, invalid formatted manifests"
+create_remote_file(master, '/tmp/good.pp', %w{notify{good:}} )
+create_remote_file(master, '/tmp/bad.pp', 'notify{bad:')
+
+step "Master: use --parseonly on an invalid manifest, should return 1 and issue deprecation warning"
+on master, puppet_master( %w{--parseonly /tmp/bad.pp} ), :acceptable_exit_codes => [ 1 ]
+ fail_test "Deprecation warning not issued for --parseonly" unless
+ stdout.include? '--parseonly has been removed. Please use \'puppet parser validate <manifest>\''
+
+step "Agents: create valid, invalid formatted manifests"
+agents.each do |host|
+ create_remote_file(host, '/tmp/good.pp', %w{notify{good:}} )
+ create_remote_file(host, '/tmp/bad.pp', 'notify{bad:')
+end
+
+step "Agents: use --parseonly on an invalid manifest, should return 1 and issue deprecation warning"
+agents.each do |host|
+ on(host, "puppet --parseonly /tmp/bad.pp}", :acceptable_exit_codes => [ 1 ]) do
+ fail_test "Deprecation warning not issued for --parseonly" unless
+ stdout.include? '--parseonly has been removed. Please use \'puppet parser validate <manifest>\''
+ end
+end
+
+step "Test Face for ‘parser validate’ with good manifest -- should pass"
+agents.each do |host|
+ on(host, "puppet parser validate /tmp/good.pp", :acceptable_exit_codes => [ 0 ])
+end
+
+step "Test Face for ‘parser validate’ with bad manifest -- should fail"
+agents.each do |host|
+ on(host, "puppet parser validate /tmp/bad.pp", :acceptable_exit_codes => [ 1 ]) do
+ fail_test "Bad manifest detection failed" unless
+ stderr.include? 'Could not run: Could not parse for environment production'
+ end
+end
39 acceptance/tests/stages/ticket_4655_default_stage_for_classes.rb
View
@@ -0,0 +1,39 @@
+test_name "#4655: Allow setting the default stage for parameterized classes"
+
+temp_file_name = "/tmp/4655-stage-in-parameterized-class.#{$$}"
+test_manifest = <<HERE
+stage { one: before => Stage[two] }
+stage { two: before => Stage[three] }
+stage { three: before => Stage[main] }
+
+class in_one {
+ exec { "echo 'in_one' > #{temp_file_name}":
+ path => '/usr/bin:/bin',
+ }
+}
+class { in_one: stage => "one" }
+
+class in_two( $stage=two ){
+ exec { "echo 'in_two' >> #{temp_file_name}":
+ path => '/usr/bin:/bin',
+ }
+}
+class { in_two: }
+
+class in_three {
+ exec { "echo 'in_three' >> #{temp_file_name}":
+ path => '/usr/bin:/bin',
+ }
+}
+class { "in_three": stage => "three" }
+HERE
+
+expected_results = "in_one
+in_two
+in_three
+"
+apply_manifest_on agents, test_manifest
+
+on(agents, "cat #{temp_file_name}").each do |result|
+ assert_equal(expected_results, "#{result.stdout}", "Unexpected result for host '#{result.host}'")
+end
98 lib/puppet/application/cert.rb
View
@@ -61,9 +61,8 @@ def help
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 @@ def help
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 @@ def help
'--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
255 lib/puppet/application/device.rb
View
@@ -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
87 lib/puppet/application/face_base.rb
View
@@ -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 @@ def exit_code
@exit_code || 0
end
- # Override this if you need custom rendering.
def render(result)
- render_method = Puppet::Network::FormatHandler.format(format).render_method
- if render_method == "to_pson"
- jj result
- exit(0)
+ 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 @@ def parse_options
# 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 @@ def parse_options
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 @@ def parse_options
@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 @@ def setup
# 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 @@ def setup
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
35 lib/puppet/application/kick.rb
View
@@ -76,31 +76,16 @@ def help
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
-------
19 lib/puppet/defaults.rb
View
@@ -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."]
)
28 lib/puppet/face/certificate.rb
View
@@ -2,24 +2,16 @@
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 @@
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 @@
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)
4 lib/puppet/face/facts.rb
View
@@ -2,10 +2,10 @@
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])
26 lib/puppet/face/help.rb
View
@@ -13,12 +13,38 @@
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
15 lib/puppet/face/indirector.rb
View
@@ -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 @@ def self.terminus_classes(indirection)
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 @@ def call_indirection_method(method, *args)
raise "Could not call '#{method}' on '#{indirection_name}': #{detail}"
end
- indirection.reset_terminus_class
return result
end
@@ -49,16 +55,11 @@ def call_indirection_method(method, *args)
# 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
2  lib/puppet/face/node.rb
View
@@ -1,5 +1,3 @@
require 'puppet/face/indirector'
-
Puppet::Face::Indirector.define(:node, '0.0.1') do
- set_default_format :yaml
end
5 lib/puppet/face/parser.rb
View
@@ -6,7 +6,10 @@
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
25 lib/puppet/indirector/facts/network_device.rb
View
@@ -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
2  lib/puppet/indirector/queue.rb
View
@@ -36,7 +36,7 @@ def find(request)
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
51 lib/puppet/indirector/request.rb
View
@@ -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 @@ def initialize(indirection_name, method, key_or_instance, options_or_instance =
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 @@ def to_s
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
92 lib/puppet/interface.rb
View
@@ -62,18 +62,35 @@ def [](name, version)
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 @@ def initialize(name, version, &block)
@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 @@ def load_actions
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
124 lib/puppet/interface/action.rb
View
@@ -7,8 +7,10 @@ def initialize(face, name, attrs = {})
raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/
@face = face
@name = name.to_sym
- @options = {}
attrs.each do |k, v| send("#{k}=", v) end
+
+ @options = {}
+ @when_rendering = {}
end
# This is not nice, but it is the easiest way to make us behave like the
@@ -20,11 +22,79 @@ def __dup_and_rebind_to(to)
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 @@ def when_invoked=(block)
# 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 @@ def options
(@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
44 lib/puppet/interface/action_builder.rb
View
@@ -20,16 +20,54 @@ def initialize(face, name, &block)
# 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
9 lib/puppet/interface/action_manager.rb
View
@@ -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 @@ def get_action(name)
return result
end
+ def get_default_action
+ @default_action
+ end
+
def action?(name)
actions.include?(name.to_sym)
end
56 lib/puppet/interface/option.rb
View
@@ -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 @@ def initialize(parent, *declaration, &block)
# 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 @@ def optparse_to_name(declaration)
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
37 lib/puppet/interface/option_builder.rb
View
@@ -11,15 +11,44 @@ def self.build(face, *declaration, &block)
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
4 lib/puppet/interface/option_manager.rb
View
@@ -37,10 +37,10 @@ def options
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)
8 lib/puppet/network/http/handler.rb
View
@@ -122,10 +122,10 @@ def do_find(indirection_name, key, params, request, response)
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
23 lib/puppet/node.rb
View
@@ -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
20 lib/puppet/node/facts.rb
View
@@ -61,18 +61,22 @@ def ==(other)
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.
19 lib/puppet/parser/compiler.rb
View
@@ -56,23 +56,20 @@ def add_resource(scope, resource)
# 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?
19 lib/puppet/parser/resource.rb
View
@@ -62,13 +62,30 @@ def environment
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
10 lib/puppet/parser/scope.rb
View
@@ -101,7 +101,7 @@ def add_namespace(ns)
# Remove this when rebasing
def environment
- compiler.environment
+ compiler ? compiler.environment : nil
end
def find_hostclass(name)
@@ -222,12 +222,12 @@ def qualified_scope(classname)
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 @@ def lookupvar(name, options = {})
# 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 @@ def resolve_type_and_titles(type, titles)
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
9 lib/puppet/provider/cisco.rb
View
@@ -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
18 lib/puppet/provider/interface/cisco.rb
View
@@ -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 @@ def flush
end
super
end
-
- def device
- @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url])
- end
end
23 lib/puppet/provider/network_device.rb
View
@@ -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 @@ def exists?
@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 @@ def former_properties
def properties
@property_hash.dup
end
-end
+end
1  lib/puppet/provider/package/aptitude.rb
View
@@ -12,6 +12,7 @@ def aptget(*args)
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.
175 lib/puppet/provider/package/pkgutil.rb
View
@@ -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