summaryrefslogtreecommitdiffstats
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
parentac428b9557e2da251e4b51e48de844833ca0aa2a (diff)
parentfc66e98b84b9a16728af054485883334a5887cca (diff)
downloadpuppet-01f610bb223b435dc52f491260af3ea002930102.tar.gz
puppet-01f610bb223b435dc52f491260af3ea002930102.tar.xz
puppet-01f610bb223b435dc52f491260af3ea002930102.zip
Merge branch 'next'
-rw-r--r--README.strings115
-rw-r--r--acceptance/pending/ticket_3360_allow_duplicate_csr_with_option_set.rb50
-rw-r--r--acceptance/pending/ticket_3360_reject_duplicate_csr_with_option_unset.rb0
-rw-r--r--acceptance/pending/ticket_5027_warn_on_dynamic_scope.rb28
-rw-r--r--acceptance/pending/ticket_6928_puppet_master_parse_fails.rb38
-rw-r--r--acceptance/tests/stages/ticket_4655_default_stage_for_classes.rb39
-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
-rwxr-xr-xspec/integration/indirector/catalog/queue_spec.rb4
-rwxr-xr-xspec/integration/transaction_spec.rb56
-rw-r--r--spec/lib/matchers/json.rb111
-rwxr-xr-xspec/lib/puppet/face/basetest.rb1
-rw-r--r--spec/shared_behaviours/documentation_on_faces.rb35
-rwxr-xr-xspec/shared_behaviours/things_that_declare_options.rb54
-rw-r--r--spec/unit/application/device_spec.rb349
-rwxr-xr-xspec/unit/application/face_base_spec.rb105
-rwxr-xr-xspec/unit/application/indirection_base_spec.rb7
-rwxr-xr-xspec/unit/face/certificate_spec.rb9
-rwxr-xr-xspec/unit/face/facts_spec.rb6
-rwxr-xr-xspec/unit/face/help_spec.rb24
-rwxr-xr-xspec/unit/face/node_spec.rb4
-rw-r--r--spec/unit/indirector/facts/network_device_spec.rb89
-rwxr-xr-xspec/unit/indirector/queue_spec.rb8
-rwxr-xr-xspec/unit/indirector/request_spec.rb96
-rwxr-xr-xspec/unit/indirector/rest_spec.rb5
-rwxr-xr-xspec/unit/interface/action_builder_spec.rb194
-rwxr-xr-xspec/unit/interface/action_manager_spec.rb20
-rwxr-xr-xspec/unit/interface/action_spec.rb226
-rwxr-xr-xspec/unit/interface/face_collection_spec.rb4
-rwxr-xr-xspec/unit/interface/option_builder_spec.rb66
-rwxr-xr-xspec/unit/interface/option_spec.rb25
-rwxr-xr-xspec/unit/interface_spec.rb42
-rwxr-xr-xspec/unit/network/http/handler_spec.rb18
-rwxr-xr-xspec/unit/node/facts_spec.rb34
-rwxr-xr-xspec/unit/node_spec.rb64
-rwxr-xr-xspec/unit/parser/compiler_spec.rb54
-rwxr-xr-xspec/unit/parser/resource_spec.rb64
-rwxr-xr-xspec/unit/parser/scope_spec.rb6
-rw-r--r--spec/unit/provider/cisco_spec.rb16
-rwxr-xr-xspec/unit/provider/interface/cisco_spec.rb20
-rwxr-xr-xspec/unit/provider/network_device_spec.rb21
-rwxr-xr-xspec/unit/provider/package/pkgutil_spec.rb182
-rwxr-xr-xspec/unit/provider/vlan/cisco_spec.rb20
-rwxr-xr-xspec/unit/resource/catalog_spec.rb1
-rwxr-xr-xspec/unit/transaction_spec.rb18
-rwxr-xr-xspec/unit/type/interface_spec.rb5
-rwxr-xr-xspec/unit/type/schedule_spec.rb8
-rwxr-xr-xspec/unit/type/vlan_spec.rb5
-rwxr-xr-xspec/unit/util/network_device/cisco/device_spec.rb137
-rw-r--r--spec/unit/util/network_device/cisco/facts_spec.rb63
-rw-r--r--spec/unit/util/network_device/config_spec.rb102
-rwxr-xr-xspec/unit/util/network_device/transport/ssh_spec.rb8
-rw-r--r--spec/unit/util/network_device_spec.rb50
-rwxr-xr-xspec/unit/util/queue/stomp_spec.rb20
-rwxr-xr-xspec/watchr.rb6
-rw-r--r--test/lib/puppettest/railstesting.rb34
103 files changed, 3597 insertions, 808 deletions
diff --git a/README.strings b/README.strings
deleted file mode 100644
index 28289ee10..000000000
--- a/README.strings
+++ /dev/null
@@ -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).
diff --git a/acceptance/pending/ticket_3360_allow_duplicate_csr_with_option_set.rb b/acceptance/pending/ticket_3360_allow_duplicate_csr_with_option_set.rb
new file mode 100644
index 000000000..ba02227ea
--- /dev/null
+++ b/acceptance/pending/ticket_3360_allow_duplicate_csr_with_option_set.rb
@@ -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]
+}
diff --git a/acceptance/pending/ticket_3360_reject_duplicate_csr_with_option_unset.rb b/acceptance/pending/ticket_3360_reject_duplicate_csr_with_option_unset.rb
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/acceptance/pending/ticket_3360_reject_duplicate_csr_with_option_unset.rb
diff --git a/acceptance/pending/ticket_5027_warn_on_dynamic_scope.rb b/acceptance/pending/ticket_5027_warn_on_dynamic_scope.rb
new file mode 100644
index 000000000..762116ce9
--- /dev/null
+++ b/acceptance/pending/ticket_5027_warn_on_dynamic_scope.rb
@@ -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.'
diff --git a/acceptance/pending/ticket_6928_puppet_master_parse_fails.rb b/acceptance/pending/ticket_6928_puppet_master_parse_fails.rb
new file mode 100644
index 000000000..aac53138a
--- /dev/null
+++ b/acceptance/pending/ticket_6928_puppet_master_parse_fails.rb
@@ -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
diff --git a/acceptance/tests/stages/ticket_4655_default_stage_for_classes.rb b/acceptance/tests/stages/ticket_4655_default_stage_for_classes.rb
new file mode 100644
index 000000000..1b0b537bf
--- /dev/null
+++ b/acceptance/tests/stages/ticket_4655_default_stage_for_classes.rb
@@ -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
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)
diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb
index 569f096bf..940c8bab8 100755
--- a/spec/integration/indirector/catalog/queue_spec.rb
+++ b/spec/integration/indirector/catalog/queue_spec.rb
@@ -19,13 +19,13 @@ describe "Puppet::Resource::Catalog::Queue", :if => Puppet.features.pson? do
after { Puppet.settings.clear }
- it "should render catalogs to pson and send them via the queue client when catalogs are saved" do
+ it "should render catalogs to pson and publish them via the queue client when catalogs are saved" do
terminus = Puppet::Resource::Catalog.indirection.terminus(:queue)
client = mock 'client'
terminus.stubs(:client).returns client
- client.expects(:send_message).with(:catalog, @catalog.to_pson)
+ client.expects(:publish_message).with(:catalog, @catalog.to_pson)
request = Puppet::Indirector::Request.new(:catalog, :save, "foo", :instance => @catalog)
diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb
index 41a1ebac3..78d62fc51 100755
--- a/spec/integration/transaction_spec.rb
+++ b/spec/integration/transaction_spec.rb
@@ -75,6 +75,62 @@ describe Puppet::Transaction do
transaction.evaluate
end
+ it "should not apply device resources on normal host" do
+ catalog = Puppet::Resource::Catalog.new
+ resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1"
+ catalog.add_resource resource
+
+ transaction = Puppet::Transaction.new(catalog)
+ transaction.for_network_device = false
+
+ transaction.expects(:apply).never.with(resource, nil)
+
+ transaction.evaluate
+ transaction.resource_status(resource).should be_skipped
+ end
+
+ it "should not apply host resources on device" do
+ catalog = Puppet::Resource::Catalog.new
+ resource = Puppet::Type.type(:file).new :path => "/foo/bar", :backup => false
+ catalog.add_resource resource
+
+ transaction = Puppet::Transaction.new(catalog)
+ transaction.for_network_device = true
+
+ transaction.expects(:apply).never.with(resource, nil)
+
+ transaction.evaluate
+ transaction.resource_status(resource).should be_skipped
+ end
+
+ it "should apply device resources on device" do
+ catalog = Puppet::Resource::Catalog.new
+ resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1"
+ catalog.add_resource resource
+
+ transaction = Puppet::Transaction.new(catalog)
+ transaction.for_network_device = true
+
+ transaction.expects(:apply).with(resource, nil)
+
+ transaction.evaluate
+ transaction.resource_status(resource).should_not be_skipped
+ end
+
+ it "should apply resources appliable on host and device on a device" do
+ catalog = Puppet::Resource::Catalog.new
+ resource = Puppet::Type.type(:schedule).new :name => "test"
+ catalog.add_resource resource
+
+ transaction = Puppet::Transaction.new(catalog)
+ transaction.for_network_device = true
+
+ transaction.expects(:apply).with(resource, nil)
+
+ transaction.evaluate
+ transaction.resource_status(resource).should_not be_skipped
+ end
+
# Verify that one component requiring another causes the contained
# resources in the requiring component to get refreshed.
it "should propagate events from a contained resource through its container to its dependent container's contained resources" do
diff --git a/spec/lib/matchers/json.rb b/spec/lib/matchers/json.rb
new file mode 100644
index 000000000..798e4cd21
--- /dev/null
+++ b/spec/lib/matchers/json.rb
@@ -0,0 +1,111 @@
+RSpec::Matchers.define :set_json_attribute do |*attributes|
+ def format
+ @format ||= Puppet::Network::FormatHandler.format('pson')
+ end
+
+ chain :to do |value|
+ @value = value
+ end
+
+ def json(instance)
+ PSON.parse(instance.to_pson)
+ end
+
+ def attr_value(attrs, instance)
+ attrs = attrs.dup
+ hash = json(instance)['data']
+ while attrs.length > 0
+ name = attrs.shift
+ hash = hash[name]
+ end
+ hash
+ end
+
+ match do |instance|
+ result = attr_value(attributes, instance)
+ if @value
+ result == @value
+ else
+ ! result.nil?
+ end
+ end
+
+ failure_message_for_should do |instance|
+ if @value
+ "expected #{instance.inspect} to set #{attributes.inspect} to #{@value.inspect}; got #{attr_value(attributes, instance).inspect}"
+ else
+ "expected #{instance.inspect} to set #{attributes.inspect} but was nil"
+ end
+ end
+
+ failure_message_for_should_not do |instance|
+ if @value
+ "expected #{instance.inspect} not to set #{attributes.inspect} to #{@value.inspect}"
+ else
+ "expected #{instance.inspect} not to set #{attributes.inspect} to nil"
+ end
+ end
+end
+
+RSpec::Matchers.define :set_json_document_type_to do |type|
+ def format
+ @format ||= Puppet::Network::FormatHandler.format('pson')
+ end
+
+ match do |instance|
+ json(instance)['document_type'] == type
+ end
+
+ def json(instance)
+ PSON.parse(instance.to_pson)
+ end
+
+ failure_message_for_should do |instance|
+ "expected #{instance.inspect} to set document_type to #{type.inspect}; got #{json(instance)['document_type'].inspect}"
+ end
+
+ failure_message_for_should_not do |instance|
+ "expected #{instance.inspect} not to set document_type to #{type.inspect}"
+ end
+end
+
+RSpec::Matchers.define :read_json_attribute do |attribute|
+ def format
+ @format ||= Puppet::Network::FormatHandler.format('pson')
+ end
+
+ chain :from do |value|
+ @json = value
+ end
+
+ chain :as do |as|
+ @value = as
+ end
+
+ match do |klass|
+ raise "Must specify json with 'from'" unless @json
+
+ @instance = format.intern(klass, @json)
+ if @value
+ @instance.send(attribute) == @value
+ else
+ ! @instance.send(attribute).nil?
+ end
+ end
+
+ failure_message_for_should do |klass|
+ if @value
+ "expected #{klass} to read #{attribute} from #{@json} as #{@value.inspect}; got #{@instance.send(attribute).inspect}"
+ else
+ "expected #{klass} to read #{attribute} from #{@json} but was nil"
+ end
+ end
+
+ failure_message_for_should_not do |klass|
+ if @value
+ "expected #{klass} not to set #{attribute} to #{@value}"
+ else
+ "expected #{klass} not to set #{attribute} to nil"
+ end
+ end
+end
diff --git a/spec/lib/puppet/face/basetest.rb b/spec/lib/puppet/face/basetest.rb
index 00616f74f..e935161ae 100755
--- a/spec/lib/puppet/face/basetest.rb
+++ b/spec/lib/puppet/face/basetest.rb
@@ -1 +1,2 @@
+require 'puppet/face'
Puppet::Face.define(:basetest, '0.0.1')
diff --git a/spec/shared_behaviours/documentation_on_faces.rb b/spec/shared_behaviours/documentation_on_faces.rb
new file mode 100644
index 000000000..41b4015c9
--- /dev/null
+++ b/spec/shared_behaviours/documentation_on_faces.rb
@@ -0,0 +1,35 @@
+# encoding: UTF-8
+shared_examples_for "documentation on faces" do
+ context "description" do
+ describe "#summary" do
+ it "should accept a summary" do
+ text = "this is my summary"
+ expect { subject.summary = text }.to_not raise_error
+ subject.summary.should == text
+ end
+
+ it "should accept a long, long, long summary" do
+ text = "I never know when to stop with the word banana" + ("na" * 1000)
+ expect { subject.summary = text }.to_not raise_error
+ subject.summary.should == text
+ end
+
+ it "should reject a summary with a newline" do
+ expect { subject.summary = "with\nnewlines" }.
+ to raise_error ArgumentError, /summary should be a single line/
+ end
+ end
+
+ describe "#description" do
+ it "should accept a description" do
+ subject.description = "hello"
+ subject.description.should == "hello"
+ end
+
+ it "should accept a description with a newline" do
+ subject.description = "hello \n my \n fine \n friend"
+ subject.description.should == "hello \n my \n fine \n friend"
+ end
+ end
+ end
+end
diff --git a/spec/shared_behaviours/things_that_declare_options.rb b/spec/shared_behaviours/things_that_declare_options.rb
index ac358eacd..5300a159a 100755
--- a/spec/shared_behaviours/things_that_declare_options.rb
+++ b/spec/shared_behaviours/things_that_declare_options.rb
@@ -1,45 +1,45 @@
# encoding: UTF-8
shared_examples_for "things that declare options" do
it "should support options without arguments" do
- subject = add_options_to { option "--bar" }
- subject.should be_option :bar
+ thing = add_options_to { option "--bar" }
+ thing.should be_option :bar
end
it "should support options with an empty block" do
- subject = add_options_to do
+ thing = add_options_to do
option "--foo" do
# this section deliberately left blank
end
end
- subject.should be
- subject.should be_option :foo
+ thing.should be
+ thing.should be_option :foo
end
{ "--foo=" => :foo }.each do |input, option|
it "should accept #{name.inspect}" do
- subject = add_options_to { option input }
- subject.should be_option option
+ thing = add_options_to { option input }
+ thing.should be_option option
end
end
it "should support option documentation" do
text = "Sturm und Drang (German pronunciation: [ˈʃtʊʁm ʊnt ˈdʁaŋ]) …"
- subject = add_options_to do
+ thing = add_options_to do
option "--foo" do
desc text
end
end
- subject.get_option(:foo).desc.should == text
+ thing.get_option(:foo).desc.should == text
end
it "should list all the options" do
- subject = add_options_to do
+ thing = add_options_to do
option "--foo"
option "--bar"
end
- subject.options.should =~ [:foo, :bar]
+ thing.options.should =~ [:foo, :bar]
end
it "should detect conflicts in long options" do
@@ -95,22 +95,24 @@ shared_examples_for "things that declare options" do
should raise_error ArgumentError, /inconsistent about taking an argument/
end
- it "should accept optional arguments" do
- subject = add_options_to do option "--foo=[baz]", "--bar=[baz]" end
- [:foo, :bar].each do |name|
- subject.should be_option name
- end
+ it "should not accept optional arguments" do
+ expect do
+ thing = add_options_to do option "--foo=[baz]", "--bar=[baz]" end
+ [:foo, :bar].each do |name|
+ thing.should be_option name
+ end
+ end.to raise_error(ArgumentError, /optional arguments are not supported/)
end
describe "#takes_argument?" do
it "should detect an argument being absent" do
- subject = add_options_to do option "--foo" end
- subject.get_option(:foo).should_not be_takes_argument
+ thing = add_options_to do option "--foo" end
+ thing.get_option(:foo).should_not be_takes_argument
end
- ["=FOO", " FOO", "=[FOO]", " [FOO]"].each do |input|
+ ["=FOO", " FOO"].each do |input|
it "should detect an argument given #{input.inspect}" do
- subject = add_options_to do option "--foo#{input}" end
- subject.get_option(:foo).should be_takes_argument
+ thing = add_options_to do option "--foo#{input}" end
+ thing.get_option(:foo).should be_takes_argument
end
end
end
@@ -131,10 +133,12 @@ shared_examples_for "things that declare options" do
end
["=[FOO]", " [FOO]"].each do |input|
- it "should be true if the argument is optional (like #{input.inspect})" do
- option = add_options_to do option "--foo#{input}" end.get_option(:foo)
- option.should be_takes_argument
- option.should be_optional_argument
+ it "should fail if the argument is optional (like #{input.inspect})" do
+ expect do
+ option = add_options_to do option "--foo#{input}" end.get_option(:foo)
+ option.should be_takes_argument
+ option.should be_optional_argument
+ end.to raise_error(ArgumentError, /optional arguments are not supported/)
end
end
end
diff --git a/spec/unit/application/device_spec.rb b/spec/unit/application/device_spec.rb
new file mode 100644
index 000000000..086e321e9
--- /dev/null
+++ b/spec/unit/application/device_spec.rb
@@ -0,0 +1,349 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/application/device'
+require 'puppet/util/network_device/config'
+require 'ostruct'
+require 'puppet/configurer'
+
+describe Puppet::Application::Device do
+ before :each do
+ @device = Puppet::Application[:device]
+# @device.stubs(:puts)
+ @device.preinit
+ Puppet::Util::Log.stubs(:newdestination)
+ Puppet::Util::Log.stubs(:level=)
+
+ Puppet::Node.indirection.stubs(:terminus_class=)
+ Puppet::Node.indirection.stubs(:cache_class=)
+ Puppet::Node::Facts.indirection.stubs(:terminus_class=)
+ end
+
+ it "should operate in agent run_mode" do
+ @device.class.run_mode.name.should == :agent
+ end
+
+ it "should ask Puppet::Application to parse Puppet configuration file" do
+ @device.should_parse_config?.should be_true
+ end
+
+ it "should declare a main command" do
+ @device.should respond_to(:main)
+ end
+
+ it "should declare a preinit block" do
+ @device.should respond_to(:preinit)
+ end
+
+ describe "in preinit" do
+ before :each do
+ @device.stubs(:trap)
+ end
+
+ it "should catch INT" do
+ @device.expects(:trap).with { |arg,block| arg == :INT }
+
+ @device.preinit
+ end
+ end
+
+ describe "when handling options" do
+ before do
+ @device.command_line.stubs(:args).returns([])
+ end
+
+ [:centrallogging, :debug, :verbose,].each do |option|
+ it "should declare handle_#{option} method" do
+ @device.should respond_to("handle_#{option}".to_sym)
+ end
+
+ it "should store argument value when calling handle_#{option}" do
+ @device.options.expects(:[]=).with(option, 'arg')
+ @device.send("handle_#{option}".to_sym, 'arg')
+ end
+ end
+
+ it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do
+ Puppet[:onetime] = true
+ Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0)
+ @device.setup_host
+ end
+
+ it "should use supplied waitforcert when --onetime is specified" do
+ Puppet[:onetime] = true
+ @device.handle_waitforcert(60)
+ Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60)
+ @device.setup_host
+ end
+
+ it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do
+ Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120)
+ @device.setup_host
+ end
+
+ it "should set the log destination with --logdest" do
+ @device.options.stubs(:[]=).with { |opt,val| opt == :setdest }
+ Puppet::Log.expects(:newdestination).with("console")
+
+ @device.handle_logdest("console")
+ end
+
+ it "should put the setdest options to true" do
+ @device.options.expects(:[]=).with(:setdest,true)
+
+ @device.handle_logdest("console")
+ end
+
+ it "should parse the log destination from the command line" do
+ @device.command_line.stubs(:args).returns(%w{--logdest /my/file})
+
+ Puppet::Util::Log.expects(:newdestination).with("/my/file")
+
+ @device.parse_options
+ end
+
+ it "should store the waitforcert options with --waitforcert" do
+ @device.options.expects(:[]=).with(:waitforcert,42)
+
+ @device.handle_waitforcert("42")
+ end
+
+ it "should set args[:Port] with --port" do
+ @device.handle_port("42")
+ @device.args[:Port].should == "42"
+ end
+
+ end
+
+ describe "during setup" do
+ before :each do
+ @device.options.stubs(:[])
+ Puppet.stubs(:info)
+ FileTest.stubs(:exists?).returns(true)
+ Puppet[:libdir] = "/dev/null/lib"
+ Puppet::SSL::Host.stubs(:ca_location=)
+ Puppet::Transaction::Report.indirection.stubs(:terminus_class=)
+ Puppet::Resource::Catalog.indirection.stubs(:terminus_class=)
+ Puppet::Resource::Catalog.indirection.stubs(:cache_class=)
+ Puppet::Node::Facts.indirection.stubs(:terminus_class=)
+ @host = stub_everything 'host'
+ Puppet::SSL::Host.stubs(:new).returns(@host)
+ Puppet.stubs(:settraps)
+ end
+
+ it "should call setup_logs" do
+ @device.expects(:setup_logs)
+ @device.setup
+ end
+
+ describe "when setting up logs" do
+ before :each do
+ Puppet::Util::Log.stubs(:newdestination)
+ end
+
+ it "should set log level to debug if --debug was passed" do
+ @device.options.stubs(:[]).with(:debug).returns(true)
+
+ Puppet::Util::Log.expects(:level=).with(:debug)
+
+ @device.setup_logs
+ end
+
+ it "should set log level to info if --verbose was passed" do
+ @device.options.stubs(:[]).with(:verbose).returns(true)
+
+ Puppet::Util::Log.expects(:level=).with(:info)
+
+ @device.setup_logs
+ end
+
+ [:verbose, :debug].each do |level|
+ it "should set console as the log destination with level #{level}" do
+ @device.options.stubs(:[]).with(level).returns(true)
+
+ Puppet::Util::Log.expects(:newdestination).with(:console)
+
+ @device.setup_logs
+ end
+ end
+
+ it "should set syslog as the log destination if no --logdest" do
+ @device.options.stubs(:[]).with(:setdest).returns(false)
+
+ Puppet::Util::Log.expects(:newdestination).with(:syslog)
+
+ @device.setup_logs
+ end
+
+ end
+
+ it "should set a central log destination with --centrallogs" do
+ @device.options.stubs(:[]).with(:centrallogs).returns(true)
+ Puppet[:server] = "puppet.reductivelabs.com"
+ Puppet::Util::Log.stubs(:newdestination).with(:syslog)
+
+ Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com")
+
+ @device.setup
+ end
+
+ it "should use :main, :agent, :device and :ssl config" do
+ Puppet.settings.expects(:use).with(:main, :agent, :device, :ssl)
+
+ @device.setup
+ end
+
+ it "should install a remote ca location" do
+ Puppet::SSL::Host.expects(:ca_location=).with(:remote)
+
+ @device.setup
+ end
+
+ it "should tell the report handler to use REST" do
+ Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest)
+
+ @device.setup
+ end
+
+ it "should change the catalog_terminus setting to 'rest'" do
+ Puppet[:catalog_terminus] = :foo
+ @device.setup
+ Puppet[:catalog_terminus].should == :rest
+ end
+
+ it "should tell the catalog handler to use cache" do
+ Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml)
+
+ @device.setup
+ end
+
+ it "should change the facts_terminus setting to 'network_device'" do
+ Puppet[:facts_terminus] = :foo
+
+ @device.setup
+ Puppet[:facts_terminus].should == :network_device
+ end
+ end
+
+ describe "when initializing each devices SSL" do
+ before(:each) do
+ @host = stub_everything 'host'
+ Puppet::SSL::Host.stubs(:new).returns(@host)
+ end
+
+ it "should create a new ssl host" do
+ Puppet::SSL::Host.expects(:new).returns(@host)
+ @device.setup_host
+ end
+
+ it "should wait for a certificate" do
+ @device.options.stubs(:[]).with(:waitforcert).returns(123)
+ @host.expects(:wait_for_cert).with(123)
+
+ @device.setup_host
+ end
+ end
+
+
+ describe "when running" do
+ before :each do
+ @device.options.stubs(:[]).with(:fingerprint).returns(false)
+ Puppet.stubs(:notice)
+ @device.options.stubs(:[]).with(:client)
+ Puppet::Util::NetworkDevice::Config.stubs(:devices).returns({})
+ end
+
+ it "should dispatch to main" do
+ @device.stubs(:main)
+ @device.run_command
+ end
+
+ it "should get the device list" do
+ device_hash = stub_everything 'device hash'
+ Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash)
+ @device.main
+ end
+
+ it "should exit if the device list is empty" do
+ @device.expects(:exit).with(1)
+ @device.main
+ end
+
+ describe "for each device" do
+ before(:each) do
+ Puppet[:vardir] = "/dummy"
+ Puppet[:confdir] = "/dummy"
+ Puppet[:certname] = "certname"
+ @device_hash = {
+ "device1" => OpenStruct.new(:name => "device1", :url => "url", :provider => "cisco"),
+ "device2" => OpenStruct.new(:name => "device2", :url => "url", :provider => "cisco"),
+ }
+ Puppet::Util::NetworkDevice::Config.stubs(:devices).returns(@device_hash)
+ Puppet.settings.stubs(:set_value)
+ Puppet.settings.stubs(:use)
+ @device.stubs(:setup_host)
+ Puppet::Util::NetworkDevice.stubs(:init)
+ @configurer = stub_everything 'configurer'
+ Puppet::Configurer.stubs(:new).returns(@configurer)
+ end
+
+ it "should set vardir to the device vardir" do
+ Puppet.settings.expects(:set_value).with(:vardir, "/dummy/devices/device1", :cli)
+ @device.main
+ end
+
+ it "should set confdir to the device confdir" do
+ Puppet.settings.expects(:set_value).with(:confdir, "/dummy/devices/device1", :cli)
+ @device.main
+ end
+
+ it "should set certname to the device certname" do
+ Puppet.settings.expects(:set_value).with(:certname, "device1", :cli)
+ Puppet.settings.expects(:set_value).with(:certname, "device2", :cli)
+ @device.main
+ end
+
+ it "should make sure all the required folders and files are created" do
+ Puppet.settings.expects(:use).with(:main, :agent, :ssl).twice
+ @device.main
+ end
+
+ it "should initialize the device singleton" do
+ Puppet::Util::NetworkDevice.expects(:init).with(@device_hash["device1"]).then.with(@device_hash["device2"])
+ @device.main
+ end
+
+ it "should setup the SSL context" do
+ @device.expects(:setup_host).twice
+ @device.main
+ end
+
+ it "should launch a configurer for this device" do
+ @configurer.expects(:run).twice
+ @device.main
+ end
+
+ [:vardir, :confdir].each do |setting|
+ it "should cleanup the #{setting} setting after the run" do
+ configurer = states('configurer').starts_as('notrun')
+ Puppet.settings.expects(:set_value).with(setting, "/dummy/devices/device1", :cli).when(configurer.is('notrun'))
+ @configurer.expects(:run).twice.then(configurer.is('run'))
+ Puppet.settings.expects(:set_value).with(setting, "/dummy", :cli).when(configurer.is('run'))
+
+ @device.main
+ end
+ end
+
+ it "should cleanup the certname setting after the run" do
+ configurer = states('configurer').starts_as('notrun')
+ Puppet.settings.expects(:set_value).with(:certname, "device1", :cli).when(configurer.is('notrun'))
+ @configurer.expects(:run).twice.then(configurer.is('run'))
+ Puppet.settings.expects(:set_value).with(:certname, "certname", :cli).when(configurer.is('run'))
+
+ @device.main
+ end
+
+ end
+ end
+end
diff --git a/spec/unit/application/face_base_spec.rb b/spec/unit/application/face_base_spec.rb
index 939712ef8..5403608cf 100755
--- a/spec/unit/application/face_base_spec.rb
+++ b/spec/unit/application/face_base_spec.rb
@@ -11,7 +11,6 @@ describe Puppet::Application::FaceBase do
Puppet::Face.define(:basetest, '0.0.1') do
option("--[no-]boolean")
option("--mandatory MANDATORY")
- option("--optional [OPTIONAL]")
action :foo do
option("--action")
@@ -60,19 +59,39 @@ describe Puppet::Application::FaceBase do
app.face.name.should == :basetest
end
- it "should set the format based on the face default" do
- app.format.should == :pson
- end
-
it "should find the action" do
app.action.should be
app.action.name.should == :foo
end
end
- it "should fail if no action is given" do
- expect { app.preinit; app.parse_options }.
- to raise_error OptionParser::MissingArgument, /No action given/
+ it "should use the default action if not given any arguments" do
+ app.command_line.stubs(:args).returns []
+ action = stub(:options => [])
+ Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action)
+ app.stubs(:main)
+ app.run
+ app.action.should == action
+ app.arguments.should == [ { } ]
+ end
+
+ it "should use the default action if not given a valid one" do
+ app.command_line.stubs(:args).returns %w{bar}
+ action = stub(:options => [])
+ Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action)
+ app.stubs(:main)
+ app.run
+ app.action.should == action
+ app.arguments.should == [ 'bar', { } ]
+ end
+
+ it "should have no action if not given a valid one and there is no default action" do
+ app.command_line.stubs(:args).returns %w{bar}
+ Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil)
+ app.stubs(:main)
+ app.run
+ app.action.should be_nil
+ app.arguments.should == [ 'bar', { } ]
end
it "should report a sensible error when options with = fail" do
@@ -169,7 +188,6 @@ describe Puppet::Application::FaceBase do
app.face = Puppet::Face[:basetest, '0.0.1']
app.action = app.face.get_action(:foo)
- app.format = :pson
app.arguments = ["myname", "myarg"]
end
@@ -178,10 +196,79 @@ describe Puppet::Application::FaceBase do
app.main
end
+ it "should lookup help when it cannot do anything else" do
+ app.action = nil
+ Puppet::Face[:help, :current].expects(:help).with(:basetest, *app.arguments)
+ app.stubs(:puts) # meh. Don't print nil, thanks. --daniel 2011-04-12
+ app.main
+ end
+
it "should use its render method to render any result" do
app.expects(:render).with(app.arguments.length + 1)
app.stubs(:puts) # meh. Don't print nil, thanks. --daniel 2011-04-12
app.main
end
end
+
+ describe "#render" do
+ before :each do
+ app.face = Puppet::Face[:basetest, '0.0.1']
+ app.action = app.face.get_action(:foo)
+ end
+
+ ["hello", 1, 1.0].each do |input|
+ it "should just return a #{input.class.name}" do
+ app.render(input).should == input
+ end
+ end
+
+ [[1, 2], ["one"], [{ 1 => 1 }]].each do |input|
+ it "should render #{input.class} using the 'pp' library" do
+ app.render(input).should == input.pretty_inspect
+ end
+ end
+
+ it "should render a non-trivially-keyed Hash with the 'pp' library" do
+ hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 }
+ app.render(hash).should == hash.pretty_inspect
+ end
+
+ it "should render a {String,Numeric}-keyed Hash into a table" do
+ object = Object.new
+ hash = { "one" => 1, "two" => [], "three" => {}, "four" => object,
+ 5 => 5, 6.0 => 6 }
+
+ # Gotta love ASCII-betical sort order. Hope your objects are better
+ # structured for display than my test one is. --daniel 2011-04-18
+ app.render(hash).should == <<EOT
+5 5
+6.0 6
+four #{object.pretty_inspect.chomp}
+one 1
+three {}
+two []
+EOT
+ end
+
+ it "should render a hash nicely with a multi-line value" do
+ hash = {
+ "number" => { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 },
+ "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 }
+ }
+ app.render(hash).should == <<EOT
+number {"1"=>"1111111111111111111111111111111111111111",
+ "2"=>"2222222222222222222222222222222222222222",
+ "3"=>"3333333333333333333333333333333333333333"}
+text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "c"=>"cccccccccccccccccccccccccccccccccccccccc"}
+EOT
+ end
+
+ it "should invoke the action rendering hook while rendering" do
+ app.action.set_rendering_method_for(:for_humans, proc { |value| "bi-winning!" })
+ app.action.render_as = :for_humans
+ app.render("bi-polar?").should == "bi-winning!"
+ end
+ end
end
diff --git a/spec/unit/application/indirection_base_spec.rb b/spec/unit/application/indirection_base_spec.rb
index 63ab11eed..57740384a 100755
--- a/spec/unit/application/indirection_base_spec.rb
+++ b/spec/unit/application/indirection_base_spec.rb
@@ -23,12 +23,11 @@ describe Puppet::Application::IndirectionBase do
it "should accept a terminus command line option" do
# It would be nice not to have to stub this, but whatever... writing an
# entire indirection stack would cause us more grief. --daniel 2011-03-31
- terminus = mock("test indirection terminus")
+ terminus = stub_everything("test indirection terminus")
Puppet::Indirector::Indirection.expects(:instance).
- with(:testindirection).twice.returns()
+ with(:testindirection).returns(terminus)
- subject.command_line.
- instance_variable_set('@args', %w{--terminus foo save})
+ subject.command_line.instance_variable_set('@args', %w{--terminus foo save})
# Not a very nice thing. :(
$stderr.stubs(:puts)
diff --git a/spec/unit/face/certificate_spec.rb b/spec/unit/face/certificate_spec.rb
index dbcc888ad..b0bbf1af6 100755
--- a/spec/unit/face/certificate_spec.rb
+++ b/spec/unit/face/certificate_spec.rb
@@ -6,9 +6,14 @@ describe Puppet::Face[:certificate, '0.0.1'] do
end
it "should set the ca location when invoked" do
- pending "#6983: This is broken in the actual face..."
Puppet::SSL::Host.expects(:ca_location=).with(:foo)
Puppet::SSL::Host.indirection.expects(:save)
- subject.sign :ca_location => :foo
+ subject.sign "hello, friend", :ca_location => :foo
+ end
+
+ it "(#7059) should set the ca location when an inherited action is invoked" do
+ Puppet::SSL::Host.expects(:ca_location=).with(:foo)
+ subject.indirection.expects(:find)
+ subject.find "hello, friend", :ca_location => :foo
end
end
diff --git a/spec/unit/face/facts_spec.rb b/spec/unit/face/facts_spec.rb
index e6411f836..6ab6ad5be 100755
--- a/spec/unit/face/facts_spec.rb
+++ b/spec/unit/face/facts_spec.rb
@@ -2,14 +2,10 @@
require 'spec_helper'
describe Puppet::Face[:facts, '0.0.1'] do
- it "should define an 'upload' fact" do
+ it "should define an 'upload' action" do
subject.should be_action(:upload)
end
- it "should set its default format to :yaml" do
- subject.default_format.should == :yaml
- end
-
describe "when uploading" do
it "should set the terminus_class to :facter"
diff --git a/spec/unit/face/help_spec.rb b/spec/unit/face/help_spec.rb
index e67f29e07..faa5f9617 100755
--- a/spec/unit/face/help_spec.rb
+++ b/spec/unit/face/help_spec.rb
@@ -7,7 +7,7 @@ describe Puppet::Face[:help, '0.0.1'] do
end
it "should have a default action of help" do
- pending "REVISIT: we don't support default actions yet"
+ subject.get_action('help').should be_default
end
it "should accept a call with no arguments" do
@@ -55,19 +55,23 @@ describe Puppet::Face[:help, '0.0.1'] do
end
end
- Puppet::Face.faces.each do |name|
- face = Puppet::Face[name, :current]
- summary = face.summary
+ it "should list all faces" do
+ Puppet::Face.faces.each do |name|
+ face = Puppet::Face[name, :current]
+ summary = face.summary
- it { should =~ %r{ #{name} } }
- it { should =~ %r{ #{name} +#{summary}} } if summary
+ subject.should =~ %r{ #{name} }
+ summary and subject.should =~ %r{ #{name} +#{summary}}
+ end
end
- Puppet::Face[:help, :current].legacy_applications.each do |appname|
- it { should =~ %r{ #{appname} } }
+ it "should list all legacy applications" do
+ Puppet::Face[:help, :current].legacy_applications.each do |appname|
+ subject.should =~ %r{ #{appname} }
- summary = Puppet::Face[:help, :current].horribly_extract_summary_from(appname)
- summary and it { should =~ %r{ #{summary}\b} }
+ summary = Puppet::Face[:help, :current].horribly_extract_summary_from(appname)
+ summary and subject.should =~ %r{ #{summary}\b}
+ end
end
end
diff --git a/spec/unit/face/node_spec.rb b/spec/unit/face/node_spec.rb
index 90d258db9..d19312c58 100755
--- a/spec/unit/face/node_spec.rb
+++ b/spec/unit/face/node_spec.rb
@@ -2,7 +2,5 @@
require 'spec_helper'
describe Puppet::Face[:node, '0.0.1'] do
- it "should set its default format to :yaml" do
- subject.default_format.should == :yaml
- end
+ it "REVISIT: really should have some tests"
end
diff --git a/spec/unit/indirector/facts/network_device_spec.rb b/spec/unit/indirector/facts/network_device_spec.rb
new file mode 100644
index 000000000..302a810e8
--- /dev/null
+++ b/spec/unit/indirector/facts/network_device_spec.rb
@@ -0,0 +1,89 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke Kanies on 2007-9-23.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+require 'puppet/indirector/facts/network_device'
+
+describe Puppet::Node::Facts::NetworkDevice do
+ it "should be a subclass of the Code terminus" do
+ Puppet::Node::Facts::NetworkDevice.superclass.should equal(Puppet::Indirector::Code)
+ end
+
+ it "should have documentation" do
+ Puppet::Node::Facts::NetworkDevice.doc.should_not be_nil
+ end
+
+ it "should be registered with the configuration store indirection" do
+ indirection = Puppet::Indirector::Indirection.instance(:facts)
+ Puppet::Node::Facts::NetworkDevice.indirection.should equal(indirection)
+ end
+
+ it "should have its name set to :facter" do
+ Puppet::Node::Facts::NetworkDevice.name.should == :network_device
+ end
+end
+
+describe Puppet::Node::Facts::NetworkDevice do
+ before :each do
+ @remote_device = stub 'remote_device', :facts => {}
+ Puppet::Util::NetworkDevice.stubs(:current).returns(@remote_device)
+ @device = Puppet::Node::Facts::NetworkDevice.new
+ @name = "me"
+ @request = stub 'request', :key => @name
+ end
+
+ describe Puppet::Node::Facts::NetworkDevice, " when finding facts" do
+ it "should return a Facts instance" do
+ @device.find(@request).should be_instance_of(Puppet::Node::Facts)
+ end
+
+ it "should return a Facts instance with the provided key as the name" do
+ @device.find(@request).name.should == @name
+ end
+
+ it "should return the device facts as the values in the Facts instance" do
+ @remote_device.expects(:facts).returns("one" => "two")
+ facts = @device.find(@request)
+ facts.values["one"].should == "two"
+ end
+
+ it "should add local facts" do
+ facts = Puppet::Node::Facts.new("foo")
+ Puppet::Node::Facts.expects(:new).returns facts
+ facts.expects(:add_local_facts)
+
+ @device.find(@request)
+ end
+
+ it "should convert all facts into strings" do
+ facts = Puppet::Node::Facts.new("foo")
+ Puppet::Node::Facts.expects(:new).returns facts
+ facts.expects(:stringify)
+
+ @device.find(@request)
+ end
+
+ it "should call the downcase hook" do
+ facts = Puppet::Node::Facts.new("foo")
+ Puppet::Node::Facts.expects(:new).returns facts
+ facts.expects(:downcase_if_necessary)
+
+ @device.find(@request)
+ end
+ end
+
+ describe Puppet::Node::Facts::NetworkDevice, " when saving facts" do
+ it "should fail" do
+ proc { @device.save(@facts) }.should raise_error(Puppet::DevError)
+ end
+ end
+
+ describe Puppet::Node::Facts::NetworkDevice, " when destroying facts" do
+ it "should fail" do
+ proc { @device.destroy(@facts) }.should raise_error(Puppet::DevError)
+ end
+ end
+end
diff --git a/spec/unit/indirector/queue_spec.rb b/spec/unit/indirector/queue_spec.rb
index b84ed2aea..eba136bbc 100755
--- a/spec/unit/indirector/queue_spec.rb
+++ b/spec/unit/indirector/queue_spec.rb
@@ -60,20 +60,20 @@ describe Puppet::Indirector::Queue, :if => Puppet.features.pson? do
describe "when saving" do
it 'should render the instance using pson' do
@subject.expects(:render).with(:pson)
- @store.client.stubs(:send_message)
+ @store.client.stubs(:publish_message)
@store.save(@request)
end
- it "should send the rendered message to the appropriate queue on the client" do
+ it "should publish the rendered message to the appropriate queue on the client" do
@subject.expects(:render).returns "mypson"
- @store.client.expects(:send_message).with(:my_queue, "mypson")
+ @store.client.expects(:publish_message).with(:my_queue, "mypson")
@store.save(@request)
end
it "should catch any exceptions raised" do
- @store.client.expects(:send_message).raises ArgumentError
+ @store.client.expects(:publish_message).raises ArgumentError
lambda { @store.save(@request) }.should raise_error(Puppet::Error)
end
diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb
index 965d54188..ba7dc815e 100755
--- a/spec/unit/indirector/request_spec.rb
+++ b/spec/unit/indirector/request_spec.rb
@@ -1,5 +1,6 @@
#!/usr/bin/env rspec
require 'spec_helper'
+require 'matchers/json'
require 'puppet/indirector/request'
describe Puppet::Indirector::Request do
@@ -300,4 +301,99 @@ describe Puppet::Indirector::Request do
lambda { @request.query_string }.should raise_error(ArgumentError)
end
end
+
+ describe "when converting to json" do
+ before do
+ @request = Puppet::Indirector::Request.new(:facts, :find, "foo")
+ end
+
+ it "should produce a hash with the document_type set to 'request'" do
+ @request.should set_json_document_type_to("Puppet::Indirector::Request")
+ end
+
+ it "should set the 'key'" do
+ @request.should set_json_attribute("key").to("foo")
+ end
+
+ it "should include an attribute for its indirection name" do
+ @request.should set_json_attribute("type").to("facts")
+ end
+
+ it "should include a 'method' attribute set to its method" do
+ @request.should set_json_attribute("method").to("find")
+ end
+
+ it "should add all attributes under the 'attributes' attribute" do
+ @request.ip = "127.0.0.1"
+ @request.should set_json_attribute("attributes", "ip").to("127.0.0.1")
+ end
+
+ it "should add all options under the 'attributes' attribute" do
+ @request.options["opt"] = "value"
+ PSON.parse(@request.to_pson)["data"]['attributes']['opt'].should == "value"
+ end
+
+ it "should include the instance if provided" do
+ facts = Puppet::Node::Facts.new("foo")
+ @request.instance = facts
+ PSON.parse(@request.to_pson)["data"]['instance'].should be_instance_of(Puppet::Node::Facts)
+ end
+ end
+
+ describe "when converting from json" do
+ before do
+ @request = Puppet::Indirector::Request.new(:facts, :find, "foo")
+ @klass = Puppet::Indirector::Request
+ @format = Puppet::Network::FormatHandler.format('pson')
+ end
+
+ def from_json(json)
+ @format.intern(Puppet::Indirector::Request, json)
+ end
+
+ it "should set the 'key'" do
+ from_json(@request.to_pson).key.should == "foo"
+ end
+
+ it "should fail if no key is provided" do
+ json = PSON.parse(@request.to_pson)
+ json['data'].delete("key")
+ lambda { from_json(json.to_pson) }.should raise_error(ArgumentError)
+ end
+
+ it "should set its indirector name" do
+ from_json(@request.to_pson).indirection_name.should == :facts
+ end
+
+ it "should fail if no type is provided" do
+ json = PSON.parse(@request.to_pson)
+ json['data'].delete("type")
+ lambda { from_json(json.to_pson) }.should raise_error(ArgumentError)
+ end
+
+ it "should set its method" do
+ from_json(@request.to_pson).method.should == "find"
+ end
+
+ it "should fail if no method is provided" do
+ json = PSON.parse(@request.to_pson)
+ json['data'].delete("method")
+ lambda { from_json(json.to_pson) }.should raise_error(ArgumentError)
+ end
+
+ it "should initialize with all attributes and options" do
+ @request.ip = "127.0.0.1"
+ @request.options["opt"] = "value"
+ result = from_json(@request.to_pson)
+ result.options[:opt].should == "value"
+ result.ip.should == "127.0.0.1"
+ end
+
+ it "should set its instance as an instance if one is provided" do
+ facts = Puppet::Node::Facts.new("foo")
+ @request.instance = facts
+ result = from_json(@request.to_pson)
+ result.instance.should be_instance_of(Puppet::Node::Facts)
+ end
+ end
end
diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb
index 513eb8352..5637080e8 100755
--- a/spec/unit/indirector/rest_spec.rb
+++ b/spec/unit/indirector/rest_spec.rb
@@ -233,6 +233,11 @@ describe Puppet::Indirector::REST do
params[s] = 'foo'
end
+ # The request special-cases this parameter, and it
+ # won't be passed on to the server, so we remove it here
+ # to avoid a failure.
+ params.delete('ip')
+
@request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", params.merge(:environment => "myenv"))
@connection.expects(:post).with do |uri, body|
diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb
index 5b04df900..bf7afa74e 100755
--- a/spec/unit/interface/action_builder_spec.rb
+++ b/spec/unit/interface/action_builder_spec.rb
@@ -1,68 +1,192 @@
#!/usr/bin/env rspec
require 'spec_helper'
require 'puppet/interface/action_builder'
+require 'puppet/network/format_handler'
describe Puppet::Interface::ActionBuilder do
- describe "::build" do
- it "should build an action" do
- action = Puppet::Interface::ActionBuilder.build(nil, :foo) do
+ let :face do Puppet::Interface.new(:puppet_interface_actionbuilder, '0.0.1') end
+
+ it "should build an action" do
+ action = Puppet::Interface::ActionBuilder.build(nil, :foo) do
+ end
+ action.should be_a(Puppet::Interface::Action)
+ action.name.should == :foo
+ end
+
+ it "should define a method on the face which invokes the action" do
+ face = Puppet::Interface.new(:action_builder_test_interface, '0.0.1') do
+ action(:foo) { when_invoked { "invoked the method" } }
+ end
+
+ face.foo.should == "invoked the method"
+ end
+
+ it "should require a block" do
+ expect { Puppet::Interface::ActionBuilder.build(nil, :foo) }.
+ should raise_error("Action :foo must specify a block")
+ end
+
+ describe "when handling options" do
+ it "should have a #option DSL function" do
+ method = nil
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ method = self.method(:option)
+ end
+ method.should be
+ end
+
+ it "should define an option without a block" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ option "--bar"
end
- action.should be_a(Puppet::Interface::Action)
- action.name.should == :foo
+ action.should be_option :bar
end
- it "should define a method on the face which invokes the action" do
- face = Puppet::Interface.new(:action_builder_test_interface, '0.0.1')
+ it "should accept an empty block" do
action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- when_invoked do
- "invoked the method"
+ option "--bar" do
+ # This space left deliberately blank.
end
end
+ action.should be_option :bar
+ end
+ end
+
+ context "inline documentation" do
+ it "should set the summary" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ summary "this is some text"
+ end
+ action.summary.should == "this is some text"
+ end
+ end
- face.foo.should == "invoked the method"
+ context "action defaulting" do
+ it "should set the default to true" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ default
+ end
+ action.default.should be_true
end
- it "should require a block" do
- expect { Puppet::Interface::ActionBuilder.build(nil, :foo) }.
- should raise_error("Action :foo must specify a block")
+ it "should not be default by, er, default. *cough*" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do end
+ action.default.should be_false
end
+ end
- describe "when handling options" do
- let :face do Puppet::Interface.new(:option_handling, '0.0.1') end
+ context "#when_rendering" do
+ it "should fail if no rendering format is given" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering do true end
+ end
+ }.to raise_error ArgumentError, /must give a rendering format to when_rendering/
+ end
- it "should have a #option DSL function" do
- method = nil
+ it "should fail if no block is given" do
+ expect {
Puppet::Interface::ActionBuilder.build(face, :foo) do
- method = self.method(:option)
+ when_rendering :json
end
- method.should be
- end
+ }.to raise_error ArgumentError, /must give a block to when_rendering/
+ end
- it "should define an option without a block" do
- action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- option "--bar"
+ it "should fail if the block takes no arguments" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do true end
end
- action.should be_option :bar
+ }.to raise_error ArgumentError, /when_rendering methods take one argument, the result, not/
+ end
+
+ it "should fail if the block takes more than one argument" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a, b, c| true end
+ end
+ }.to raise_error ArgumentError, /when_rendering methods take one argument, the result, not/
+ end
+
+ it "should fail if the block takes a variable number of arguments" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |*args| true end
+ end
+ }.to raise_error(ArgumentError,
+ /when_rendering methods take one argument, the result, not/)
+ end
+
+ it "should stash a rendering block" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| true end
end
+ action.when_rendering(:json).should be_an_instance_of Method
+ end
- it "should accept an empty block" do
- action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- option "--bar" do
- # This space left deliberately blank.
- end
+ it "should fail if you try to set the same rendering twice" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| true end
+ when_rendering :json do |a| true end
end
- action.should be_option :bar
+ }.to raise_error ArgumentError, /You can't define a rendering method for json twice/
+ end
+
+ it "should work if you set two different renderings" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| true end
+ when_rendering :yaml do |a| true end
end
+ action.when_rendering(:json).should be_an_instance_of Method
+ action.when_rendering(:yaml).should be_an_instance_of Method
end
- context "inline documentation" do
- let :face do Puppet::Interface.new(:inline_action_docs, '0.0.1') end
+ it "should be bound to the face when called" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| self end
+ end
+ action.when_rendering(:json).call(true).should == face
+ end
+ end
+
+ context "#render_as" do
+ it "should default to nil (eg: based on context)" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do end
+ action.render_as.should be_nil
+ end
+
+ it "should fail if not rendering format is given" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ render_as
+ end
+ }.to raise_error ArgumentError, /must give a rendering format to render_as/
+ end
- it "should set the summary" do
+ Puppet::Network::FormatHandler.formats.each do |name|
+ it "should accept #{name.inspect} format" do
action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- summary "this is some text"
+ render_as name
end
- action.summary.should == "this is some text"
+ action.render_as.should == name
+ end
+ end
+
+ it "should accept :for_humans format" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ render_as :for_humans
+ end
+ action.render_as.should == :for_humans
+ end
+
+ [:if_you_define_this_format_you_frighten_me, "json", 12].each do |input|
+ it "should fail if given #{input.inspect}" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ render_as input
+ end
+ }.to raise_error ArgumentError, /#{input.inspect} is not a valid rendering format/
end
end
end
diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb
index c4b21eaac..07d517c18 100755
--- a/spec/unit/interface/action_manager_spec.rb
+++ b/spec/unit/interface/action_manager_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
# This is entirely an internal class for Interface, so we have to load it instead of our class.
require 'puppet/interface'
+require 'puppet/face'
class ActionManagerTester
include Puppet::Interface::ActionManager
@@ -103,6 +104,8 @@ describe Puppet::Interface::ActionManager do
@klass = Class.new do
include Puppet::Interface::ActionManager
extend Puppet::Interface::ActionManager
+ def __invoke_decorations(*args) true end
+ def options() [] end
end
@instance = @klass.new
end
@@ -213,6 +216,23 @@ describe Puppet::Interface::ActionManager do
end
end
+ describe "#action" do
+ it 'should add an action' do
+ subject.action(:foo) { }
+ subject.get_action(:foo).should be_a Puppet::Interface::Action
+ end
+
+ it 'should support default actions' do
+ subject.action(:foo) { default }
+ subject.get_default_action.should == subject.get_action(:foo)
+ end
+
+ it 'should not support more than one default action' do
+ subject.action(:foo) { default }
+ expect { subject.action(:bar) { default } }.should raise_error
+ end
+ end
+
describe "#get_action" do
let :parent_class do
parent_class = Class.new(Puppet::Interface)
diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb
index 8c6782976..0eb450ee2 100755
--- a/spec/unit/interface/action_spec.rb
+++ b/spec/unit/interface/action_spec.rb
@@ -66,8 +66,8 @@ describe Puppet::Interface::Action do
let :face do
Puppet::Interface.new(:ruby_api, '1.0.0') do
action :bar do
- when_invoked do |options|
- options
+ when_invoked do |*args|
+ args.last
end
end
end
@@ -79,9 +79,14 @@ describe Puppet::Interface::Action do
end
it "should work when options are supplied" do
- options = face.bar :bar => "beer"
+ options = face.bar(:bar => "beer")
options.should == { :bar => "beer" }
end
+
+ it "should call #validate_args on the action when invoked" do
+ face.get_action(:bar).expects(:validate_args).with([1, :two, 'three', {}])
+ face.bar 1, :two, 'three'
+ end
end
end
@@ -168,5 +173,220 @@ describe Puppet::Interface::Action do
end
}.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i
end
+
+ it "should fail when a required action option is not provided" do
+ face = Puppet::Interface.new(:required_action_option, '0.0.1') do
+ action(:bar) do
+ option('--foo') { required }
+ when_invoked { }
+ end
+ end
+ expect { face.bar }.to raise_error ArgumentError, /missing required options \(foo\)/
+ end
+
+ it "should fail when a required face option is not provided" do
+ face = Puppet::Interface.new(:required_face_option, '0.0.1') do
+ option('--foo') { required }
+ action(:bar) { when_invoked { } }
+ end
+ expect { face.bar }.to raise_error ArgumentError, /missing required options \(foo\)/
+ end
+ end
+
+ context "with action decorators" do
+ context "local only" do
+ let :face do
+ Puppet::Interface.new(:action_decorators, '0.0.1') do
+ action :bar do when_invoked do true end end
+ def report(arg) end
+ end
+ end
+
+ it "should call action before decorators" do
+ face.action(:baz) do
+ option "--baz" do
+ before_action do |action, args, options|
+ report(:action_option)
+ end
+ end
+ when_invoked do true end
+ end
+
+ face.expects(:report).with(:action_option)
+ face.baz :baz => true
+ end
+
+ it "should call action after decorators" do
+ face.action(:baz) do
+ option "--baz" do
+ after_action do |action, args, options|
+ report(:action_option)
+ end
+ end
+ when_invoked do true end
+ end
+
+ face.expects(:report).with(:action_option)
+ face.baz :baz => true
+ end
+
+ it "should call local before decorators" do
+ face.option "--foo FOO" do
+ before_action do |action, args, options|
+ report(:before)
+ end
+ end
+ face.expects(:report).with(:before)
+ face.bar({:foo => 12})
+ end
+
+ it "should call local after decorators" do
+ face.option "--foo FOO" do
+ after_action do |action, args, options| report(:after) end
+ end
+ face.expects(:report).with(:after)
+ face.bar({:foo => 12})
+ end
+
+ context "with inactive decorators" do
+ it "should not invoke a decorator if the options are empty" do
+ face.option "--foo FOO" do
+ before_action do |action, args, options|
+ report :before_action
+ end
+ end
+ face.expects(:report).never # I am testing the negative.
+ face.bar
+ end
+
+ context "with some decorators only" do
+ before :each do
+ face.option "--foo" do
+ before_action do |action, args, options| report :foo end
+ end
+ face.option "--bar" do
+ before_action do |action, args, options| report :bar end
+ end
+ end
+
+ it "should work with the foo option" do
+ face.expects(:report).with(:foo)
+ face.bar(:foo => true)
+ end
+
+ it "should work with the bar option" do
+ face.expects(:report).with(:bar)
+ face.bar(:bar => true)
+ end
+
+ it "should work with both options" do
+ face.expects(:report).with(:foo)
+ face.expects(:report).with(:bar)
+ face.bar(:foo => true, :bar => true)
+ end
+ end
+ end
+ end
+
+ context "with inherited decorators" do
+ let :parent do
+ parent = Class.new(Puppet::Interface)
+ parent.script :on_parent do :on_parent end
+ parent.define_method :report do |arg| arg end
+ parent
+ end
+
+ let :child do
+ child = parent.new(:inherited_decorators, '0.0.1') do
+ script :on_child do :on_child end
+ end
+ end
+
+ context "with a child decorator" do
+ subject do
+ child.option "--foo FOO" do
+ before_action do |action, args, options|
+ report(:child_before)
+ end
+ end
+ child.expects(:report).with(:child_before)
+ child
+ end
+
+ it "child actions should invoke the decorator" do
+ subject.on_child({:foo => true, :bar => true}).should == :on_child
+ end
+
+ it "parent actions should invoke the decorator" do
+ subject.on_parent({:foo => true, :bar => true}).should == :on_parent
+ end
+ end
+
+ context "with a parent decorator" do
+ subject do
+ parent.option "--foo FOO" do
+ before_action do |action, args, options|
+ report(:parent_before)
+ end
+ end
+ child.expects(:report).with(:parent_before)
+ child
+ end
+
+ it "child actions should invoke the decorator" do
+ subject.on_child({:foo => true, :bar => true}).should == :on_child
+ end
+
+ it "parent actions should invoke the decorator" do
+ subject.on_parent({:foo => true, :bar => true}).should == :on_parent
+ end
+ end
+
+ context "with child and parent decorators" do
+ subject do
+ parent.option "--foo FOO" do
+ before_action { |action, args, options| report(:parent_before) }
+ after_action { |action, args, options| report(:parent_after) }
+ end
+ child.option "--bar BAR" do
+ before_action { |action, args, options| report(:child_before) }
+ after_action { |action, args, options| report(:child_after) }
+ end
+
+ child.expects(:report).with(:child_before)
+ child.expects(:report).with(:parent_before)
+ child.expects(:report).with(:parent_after)
+ child.expects(:report).with(:child_after)
+
+ child
+ end
+
+ it "child actions should invoke all the decorator" do
+ subject.on_child({:foo => true, :bar => true}).should == :on_child
+ end
+
+ it "parent actions should invoke all the decorator" do
+ subject.on_parent({:foo => true, :bar => true}).should == :on_parent
+ end
+ end
+ end
+ end
+
+ it_should_behave_like "documentation on faces" do
+ subject do
+ face = Puppet::Interface.new(:action_documentation, '0.0.1') do
+ action :documentation do end
+ end
+ face.get_action(:documentation)
+ end
+ end
+
+ context "#when_rendering" do
+ it "should fail if no type is given when_rendering"
+ it "should accept a when_rendering block"
+ it "should accept multiple when_rendering blocks"
+ it "should fail if when_rendering gets a non-symbol identifier"
+ it "should fail if a second block is given for the same type"
+ it "should return the block if asked"
end
end
diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb
index d1114dde7..f9498cbb8 100755
--- a/spec/unit/interface/face_collection_spec.rb
+++ b/spec/unit/interface/face_collection_spec.rb
@@ -24,10 +24,6 @@ describe Puppet::Interface::FaceCollection do
$".clear ; @original_required.each do |item| $" << item end
end
- describe "::faces" do
- it "REVISIT: should have some tests here, if we describe it"
- end
-
describe "::validate_version" do
it 'should permit three number versions' do
subject.validate_version('10.10.10').should == true
diff --git a/spec/unit/interface/option_builder_spec.rb b/spec/unit/interface/option_builder_spec.rb
index fae48324e..e9346852c 100755
--- a/spec/unit/interface/option_builder_spec.rb
+++ b/spec/unit/interface/option_builder_spec.rb
@@ -8,22 +8,68 @@ describe Puppet::Interface::OptionBuilder do
should be_an_instance_of Puppet::Interface::Option
end
- describe "when using the DSL block" do
- it "should work with an empty block" do
+ it "should work with an empty block" do
+ option = Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ # This block deliberately left blank.
+ end
+
+ option.should be_an_instance_of Puppet::Interface::Option
+ end
+
+ it "should support documentation declarations" do
+ text = "this is the description"
+ option = Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ desc text
+ end
+ option.should be_an_instance_of Puppet::Interface::Option
+ option.desc.should == text
+ end
+
+ context "before_action hook" do
+ it "should support a before_action hook" do
option = Puppet::Interface::OptionBuilder.build(face, "--foo") do
- # This block deliberately left blank.
+ before_action do |a,b,c| :whatever end
end
+ option.before_action.should be_an_instance_of UnboundMethod
+ end
- option.should be_an_instance_of Puppet::Interface::Option
+ it "should fail if the hook block takes too few arguments" do
+ expect do
+ Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ before_action do |one, two| true end
+ end
+ end.to raise_error ArgumentError, /takes three arguments/
end
- it "should support documentation declarations" do
- text = "this is the description"
- option = Puppet::Interface::OptionBuilder.build(face, "--foo") do
- desc text
+ it "should fail if the hook block takes too many arguments" do
+ expect do
+ Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ before_action do |one, two, three, four| true end
+ end
+ end.to raise_error ArgumentError, /takes three arguments/
+ end
+
+ it "should fail if the hook block takes a variable number of arguments" do
+ expect do
+ Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ before_action do |*blah| true end
+ end
+ end.to raise_error ArgumentError, /takes three arguments/
+ end
+
+ it "should support simple required declarations" do
+ opt = Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ required
end
- option.should be_an_instance_of Puppet::Interface::Option
- option.desc.should == text
+ opt.should be_required
end
+
+ it "should support arguments to the required property" do
+ opt = Puppet::Interface::OptionBuilder.build(face, "--foo") do
+ required(false)
+ end
+ opt.should_not be_required
+ end
+
end
end
diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb
index 3bcd121e2..e77b46e79 100755
--- a/spec/unit/interface/option_spec.rb
+++ b/spec/unit/interface/option_spec.rb
@@ -1,3 +1,4 @@
+require 'puppet/interface'
require 'puppet/interface/option'
describe Puppet::Interface::Option do
@@ -72,4 +73,28 @@ describe Puppet::Interface::Option do
option.to_s.should == "foo-bar"
end
end
+
+ %w{before after}.each do |side|
+ describe "#{side} hooks" do
+ subject { Puppet::Interface::Option.new(face, "--foo") }
+ let :proc do Proc.new do :from_proc end end
+
+ it { should respond_to "#{side}_action" }
+ it { should respond_to "#{side}_action=" }
+
+ it "should set the #{side}_action hook" do
+ subject.send("#{side}_action").should be_nil
+ subject.send("#{side}_action=", proc)
+ subject.send("#{side}_action").should be_an_instance_of UnboundMethod
+ end
+
+ data = [1, "foo", :foo, Object.new, method(:hash), method(:hash).unbind]
+ data.each do |input|
+ it "should fail if a #{input.class} is added to the #{side} hooks" do
+ expect { subject.send("#{side}_action=", input) }.
+ to raise_error ArgumentError, /not a proc/
+ end
+ end
+ end
+ end
end
diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb
index e52b45d8a..b4fef0307 100755
--- a/spec/unit/interface_spec.rb
+++ b/spec/unit/interface_spec.rb
@@ -33,7 +33,7 @@ describe Puppet::Interface do
describe "#define" do
it "should register the face" do
- face = subject.define(:face_test_register, '0.0.1')
+ face = subject.define(:face_test_register, '0.0.1')
face.should == subject[:face_test_register, '0.0.1']
end
@@ -51,22 +51,16 @@ describe Puppet::Interface do
subject.new(:foo, '1.0.0').should respond_to(:summary=).with(1).arguments
end
- it "should set the summary text" do
- text = "hello, freddy, my little pal"
- subject.define(:face_test_summary, '1.0.0') do
- summary text
- end
- subject[:face_test_summary, '1.0.0'].summary.should == text
- end
-
- it "should support mutating the summary" do
- text = "hello, freddy, my little pal"
- subject.define(:face_test_summary, '1.0.0') do
- summary text
+ # Required documentation methods...
+ { :summary => "summary",
+ :description => "This is the description of the stuff\n\nWhee"
+ }.each do |attr, value|
+ it "should support #{attr} in the builder" do
+ face = subject.new(:builder, '1.0.0') do
+ self.send(attr, value)
+ end
+ face.send(attr).should == value
end
- subject[:face_test_summary, '1.0.0'].summary.should == text
- subject[:face_test_summary, '1.0.0'].summary = text + text
- subject[:face_test_summary, '1.0.0'].summary.should == text + text
end
end
@@ -99,16 +93,6 @@ describe Puppet::Interface do
subject.new(:me, '0.0.1').to_s.should =~ /\bme\b/
end
- it "should allow overriding of the default format" do
- face = subject.new(:me, '0.0.1')
- face.set_default_format :foo
- face.default_format.should == :foo
- end
-
- it "should default to :pson for its format" do
- subject.new(:me, '0.0.1').default_format.should == :pson
- end
-
# Why?
it "should create a class-level autoloader" do
subject.autoloader.should be_instance_of(Puppet::Util::Autoload)
@@ -204,4 +188,10 @@ describe Puppet::Interface do
end
end
end
+
+ it_should_behave_like "documentation on faces" do
+ subject do
+ Puppet::Interface.new(:face_documentation, '0.0.1')
+ end
+ end
end
diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb
index 83969c504..c709d82fe 100755
--- a/spec/unit/network/http/handler_spec.rb
+++ b/spec/unit/network/http/handler_spec.rb
@@ -240,34 +240,30 @@ describe Puppet::Network::HTTP::Handler do
describe "when performing head operation" do
before do
- @irequest = stub 'indirection_request', :method => :head, :indirection_name => "my_handler", :to_hash => {}, :key => "my_result", :model => @model_class
+ @handler.stubs(:model).with("my_handler").returns(stub 'model', :indirection => @model_class)
+ @handler.stubs(:http_method).with(@request).returns("HEAD")
+ @handler.stubs(:path).with(@request).returns("/production/my_handler/my_result")
+ @handler.stubs(:params).with(@request).returns({})
@model_class.stubs(:head).returns true
end
- it "should use the indirection request to find the model class" do
- @irequest.expects(:model).returns @model_class
-
- @handler.do_head(@irequest, @request, @response)
- end
-
it "should use the escaped request key" do
@model_class.expects(:head).with do |key, args|
key == "my_result"
end.returns true
- @handler.do_head(@irequest, @request, @response)
+ @handler.process(@request, @response)
end
it "should not generate a response when a model head call succeeds" do
@handler.expects(:set_response).never
- @handler.do_head(@irequest, @request, @response)
+ @handler.process(@request, @response)
end
it "should return a 404 when the model head call returns false" do
- @model_class.stubs(:name).returns "my name"
@handler.expects(:set_response).with { |response, body, status| status == 404 }
@model_class.stubs(:head).returns(false)
- @handler.do_head(@irequest, @request, @response)
+ @handler.process(@request, @response)
end
end
diff --git a/spec/unit/node/facts_spec.rb b/spec/unit/node/facts_spec.rb
index a130ae3f8..07f5f7e1b 100755
--- a/spec/unit/node/facts_spec.rb
+++ b/spec/unit/node/facts_spec.rb
@@ -1,6 +1,6 @@
#!/usr/bin/env rspec
require 'spec_helper'
-
+require 'matchers/json'
require 'puppet/node/facts'
describe Puppet::Node::Facts, "when indirecting" do
@@ -110,7 +110,11 @@ describe Puppet::Node::Facts, "when indirecting" do
end
it "should accept properly formatted pson" do
- pson = %Q({"name": "foo", "expiration": "#{@expiration}", "timestamp": "#{@timestamp}", "values": {"a": "1", "b": "2", "c": "3"}})
+ facts = Puppet::Node::Facts.new("foo")
+ facts.values = {"a" => "1", "b" => "2", "c" => "3"}
+ facts.expiration = Time.now
+ #pson = %Q({"document_type": "Puppet::Node::Facts", "data: {"name": "foo", "expiration": "#{@expiration}", "timestamp": "#{@timestamp}", "values": {"a": "1", "b": "2", "c": "3"}}})
+ pson = %Q({"data": {"name":"foo", "expiration":"#{@expiration}", "timestamp": "#{@timestamp}", "values":{"a":"1","b":"2","c":"3"}}, "document_type":"Puppet::Node::Facts"})
format = Puppet::Network::FormatHandler.format('pson')
facts = format.intern(Puppet::Node::Facts,pson)
facts.name.should == 'foo'
@@ -122,8 +126,30 @@ describe Puppet::Node::Facts, "when indirecting" do
Time.stubs(:now).returns(@timestamp)
facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3})
facts.expiration = @expiration
- pson = PSON.parse(facts.to_pson)
- pson.should == {"name"=>"foo", "timestamp"=>@timestamp.to_s, "expiration"=>@expiration.to_s, "values"=>{"a"=>1, "b"=>2, "c"=>3}}
+ result = PSON.parse(facts.to_pson)
+ result.name.should == facts.name
+ result.values.should == facts.values
+ result.timestamp.should == facts.timestamp
+ result.expiration.should == facts.expiration
+ result.type.should == Puppet::Node::Facts
+ end
+
+ it "should not include nil values" do
+ facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3})
+
+ # XXX:LAK For some reason this is resurrection the full instance, instead
+ # of just returning the hash. This code works, but I can't figure out what's
+ # going on.
+ newfacts = PSON.parse(facts.to_pson)
+ newfacts.expiration.should be_nil
+ end
+
+ it "should be able to handle nil values" do
+ pson = %Q({"name": "foo", "values": {"a": "1", "b": "2", "c": "3"}})
+ format = Puppet::Network::FormatHandler.format('pson')
+ facts = format.intern(Puppet::Node::Facts,pson)
+ facts.name.should == 'foo'
+ facts.expiration.should be_nil
end
end
end
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 7d813ba30..e8f826dca 100755
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -1,5 +1,6 @@
#!/usr/bin/env rspec
require 'spec_helper'
+require 'matchers/json'
describe Puppet::Node do
describe "when managing its environment" do
@@ -35,6 +36,69 @@ describe Puppet::Node do
node.environment.name.should == :bar
end
end
+
+ describe "when converting to json" do
+ before do
+ @node = Puppet::Node.new("mynode")
+ end
+
+ it "should provide its name" do
+ @node.should set_json_attribute('name').to("mynode")
+ end
+
+ it "should include the classes if set" do
+ @node.classes = %w{a b c}
+ @node.should set_json_attribute("classes").to(%w{a b c})
+ end
+
+ it "should not include the classes if there are none" do
+ @node.should_not set_json_attribute('classes')
+ end
+
+ it "should include parameters if set" do
+ @node.parameters = {"a" => "b", "c" => "d"}
+ @node.should set_json_attribute('parameters').to({"a" => "b", "c" => "d"})
+ end
+
+ it "should not include the parameters if there are none" do
+ @node.should_not set_json_attribute('parameters')
+ end
+
+ it "should include the environment" do
+ @node.environment = "production"
+ @node.should set_json_attribute('environment').to('production')
+ end
+ end
+
+ describe "when converting from json" do
+ before do
+ @node = Puppet::Node.new("mynode")
+ @format = Puppet::Network::FormatHandler.format('pson')
+ end
+
+ def from_json(json)
+ @format.intern(Puppet::Node, json)
+ end
+
+ it "should set its name" do
+ Puppet::Node.should read_json_attribute('name').from(@node.to_pson).as("mynode")
+ end
+
+ it "should include the classes if set" do
+ @node.classes = %w{a b c}
+ Puppet::Node.should read_json_attribute('classes').from(@node.to_pson).as(%w{a b c})
+ end
+
+ it "should include parameters if set" do
+ @node.parameters = {"a" => "b", "c" => "d"}
+ Puppet::Node.should read_json_attribute('parameters').from(@node.to_pson).as({"a" => "b", "c" => "d"})
+ end
+
+ it "should include the environment" do
+ @node.environment = "production"
+ Puppet::Node.should read_json_attribute('environment').from(@node.to_pson).as(Puppet::Node::Environment.new(:production))
+ end
+ end
end
describe Puppet::Node, "when initializing" do
diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb
index ced760b76..48aeb98dd 100755
--- a/spec/unit/parser/compiler_spec.rb
+++ b/spec/unit/parser/compiler_spec.rb
@@ -32,6 +32,14 @@ class CompilerTestResource
def evaluate
end
+
+ def file
+ "/fake/file/goes/here"
+ end
+
+ def line
+ "42"
+ end
end
describe Puppet::Parser::Compiler do
@@ -413,52 +421,6 @@ describe Puppet::Parser::Compiler do
@compiler.catalog.should be_edge(@scope.resource, resource)
end
- it "should add an edge to any specified stage for class resources" do
- other_stage = resource(:stage, "other")
- @compiler.add_resource(@scope, other_stage)
- resource = resource(:class, "foo")
- resource[:stage] = 'other'
-
- @compiler.add_resource(@scope, resource)
-
- @compiler.catalog.edge?(other_stage, resource).should be_true
- end
-
- it "should fail if a non-class resource attempts to set a stage" do
- other_stage = resource(:stage, "other")
- @compiler.add_resource(@scope, other_stage)
- resource = resource(:file, "foo")
- resource[:stage] = 'other'
-
- lambda { @compiler.add_resource(@scope, resource) }.should raise_error(ArgumentError)
- end
-
- it "should fail if an unknown stage is specified" do
- resource = resource(:class, "foo")
- resource[:stage] = 'other'
-
- lambda { @compiler.add_resource(@scope, resource) }.should raise_error(ArgumentError)
- end
-
- it "should add edges from the class resources to the parent's stage if no stage is specified" do
- main = @compiler.catalog.resource(:stage, :main)
- foo_stage = resource(:stage, :foo_stage)
- @compiler.add_resource(@scope, foo_stage)
- resource = resource(:class, "foo")
- @scope.stubs(:resource).returns(:stage => :foo_stage)
- @compiler.add_resource(@scope, resource)
-
- @compiler.catalog.should be_edge(foo_stage, resource)
- end
-
- it "should add edges from top-level class resources to the main stage if no stage is specified" do
- main = @compiler.catalog.resource(:stage, :main)
- resource = resource(:class, "foo")
- @compiler.add_resource(@scope, resource)
-
- @compiler.catalog.should be_edge(main, resource)
- end
-
it "should not add non-class resources that don't specify a stage to the 'main' stage" do
main = @compiler.catalog.resource(:stage, :main)
resource = resource(:file, "foo")
diff --git a/spec/unit/parser/resource_spec.rb b/spec/unit/parser/resource_spec.rb
index b03c18e5f..0d9cba60b 100755
--- a/spec/unit/parser/resource_spec.rb
+++ b/spec/unit/parser/resource_spec.rb
@@ -131,9 +131,19 @@ describe Puppet::Parser::Resource do
end
describe "when evaluating" do
+ before do
+ @node = Puppet::Node.new "test-node"
+ @compiler = Puppet::Parser::Compiler.new @node
+ @catalog = Puppet::Resource::Catalog.new
+ source = stub('source')
+ source.stubs(:module_name)
+ @scope = Puppet::Parser::Scope.new(:compiler => @compiler, :source => source)
+ @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @scope))
+ end
+
it "should evaluate the associated AST definition" do
definition = newdefine "mydefine"
- res = Puppet::Parser::Resource.new("mydefine", "whatever", :scope => @scope, :source => @source)
+ res = Puppet::Parser::Resource.new("mydefine", "whatever", :scope => @scope, :source => @source, :catalog => @catalog)
definition.expects(:evaluate_code).with(res)
res.evaluate
@@ -141,17 +151,65 @@ describe Puppet::Parser::Resource do
it "should evaluate the associated AST class" do
@class = newclass "myclass"
- res = Puppet::Parser::Resource.new("class", "myclass", :scope => @scope, :source => @source)
+ res = Puppet::Parser::Resource.new("class", "myclass", :scope => @scope, :source => @source, :catalog => @catalog)
@class.expects(:evaluate_code).with(res)
res.evaluate
end
it "should evaluate the associated AST node" do
nodedef = newnode("mynode")
- res = Puppet::Parser::Resource.new("node", "mynode", :scope => @scope, :source => @source)
+ res = Puppet::Parser::Resource.new("node", "mynode", :scope => @scope, :source => @source, :catalog => @catalog)
nodedef.expects(:evaluate_code).with(res)
res.evaluate
end
+
+ it "should add an edge to any specified stage for class resources" do
+ @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", '')
+
+ other_stage = Puppet::Parser::Resource.new(:stage, "other", :scope => @scope, :catalog => @catalog)
+ @compiler.add_resource(@scope, other_stage)
+ resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog)
+ resource[:stage] = 'other'
+ @compiler.add_resource(@scope, resource)
+
+ resource.evaluate
+
+ @compiler.catalog.edge?(other_stage, resource).should be_true
+ end
+
+ it "should fail if an unknown stage is specified" do
+ @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", '')
+
+ resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog)
+ resource[:stage] = 'other'
+
+ lambda { resource.evaluate }.should raise_error(ArgumentError, /Could not find stage other specified by/)
+ end
+
+ it "should add edges from the class resources to the parent's stage if no stage is specified" do
+ main = @compiler.catalog.resource(:stage, :main)
+ foo_stage = Puppet::Parser::Resource.new(:stage, :foo_stage, :scope => @scope, :catalog => @catalog)
+ @compiler.add_resource(@scope, foo_stage)
+ @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", '')
+ resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog)
+ resource[:stage] = 'foo_stage'
+ @compiler.add_resource(@scope, resource)
+
+ resource.evaluate
+
+ @compiler.catalog.should be_edge(foo_stage, resource)
+ end
+
+ it "should add edges from top-level class resources to the main stage if no stage is specified" do
+ main = @compiler.catalog.resource(:stage, :main)
+ @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", '')
+ resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog)
+ @compiler.add_resource(@scope, resource)
+
+ resource.evaluate
+
+ @compiler.catalog.should be_edge(main, resource)
+ end
end
describe "when finishing" do
diff --git a/spec/unit/parser/scope_spec.rb b/spec/unit/parser/scope_spec.rb
index bf4d1e29e..5308856ed 100755
--- a/spec/unit/parser/scope_spec.rb
+++ b/spec/unit/parser/scope_spec.rb
@@ -121,7 +121,11 @@ describe Puppet::Parser::Scope do
def create_class_scope(name)
klass = newclass(name)
- Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source')).evaluate
+
+ catalog = Puppet::Resource::Catalog.new
+ catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => Puppet::Parser::Scope.new))
+
+ Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source'), :catalog => catalog).evaluate
@scope.class_scope(klass)
end
diff --git a/spec/unit/provider/cisco_spec.rb b/spec/unit/provider/cisco_spec.rb
new file mode 100644
index 000000000..08320731c
--- /dev/null
+++ b/spec/unit/provider/cisco_spec.rb
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/provider/cisco'
+
+describe Puppet::Provider::Cisco do
+ it "should implement a device class method" do
+ Puppet::Provider::Cisco.should respond_to(:device)
+ end
+
+ it "should create a cisco device instance" do
+ Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).returns :device
+ Puppet::Provider::Cisco.device(:url).should == :device
+ end
+end \ No newline at end of file
diff --git a/spec/unit/provider/interface/cisco_spec.rb b/spec/unit/provider/interface/cisco_spec.rb
index d1f70609f..c18f87cf8 100755
--- a/spec/unit/provider/interface/cisco_spec.rb
+++ b/spec/unit/provider/interface/cisco_spec.rb
@@ -8,12 +8,13 @@ provider_class = Puppet::Type.type(:interface).provider(:cisco)
describe provider_class do
before do
+ @device = stub_everything 'device'
@resource = stub("resource", :name => "Fa0/1")
- @provider = provider_class.new(@resource)
+ @provider = provider_class.new(@device, @resource)
end
- it "should have a parent of Puppet::Provider::NetworkDevice" do
- provider_class.should < Puppet::Provider::NetworkDevice
+ it "should have a parent of Puppet::Provider::Cisco" do
+ provider_class.should < Puppet::Provider::Cisco
end
it "should have an instances method" do
@@ -22,31 +23,24 @@ describe provider_class do
describe "when looking up instances at prefetch" do
before do
- @device = stub_everything 'device'
- Puppet::Util::NetworkDevice::Cisco::Device.stubs(:new).returns(@device)
@device.stubs(:command).yields(@device)
end
- it "should initialize the network device with the given url" do
- Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).with(:url).returns(@device)
- provider_class.lookup(:url, "Fa0/1")
- end
-
it "should delegate to the device interface fetcher" do
@device.expects(:interface)
- provider_class.lookup("", "Fa0/1")
+ provider_class.lookup(@device, "Fa0/1")
end
it "should return the given interface data" do
@device.expects(:interface).returns({ :description => "thisone", :mode => :access})
- provider_class.lookup("", "Fa0").should == {:description => "thisone", :mode => :access }
+ provider_class.lookup(@device, "Fa0").should == {:description => "thisone", :mode => :access }
end
end
describe "when an instance is being flushed" do
it "should call the device interface update method with current and past properties" do
- @instance = provider_class.new(:ensure => :present, :name => "Fa0/1", :description => "myinterface")
+ @instance = provider_class.new(@device, :ensure => :present, :name => "Fa0/1", :description => "myinterface")
@instance.description = "newdesc"
@instance.resource = @resource
@resource.stubs(:[]).with(:name).returns("Fa0/1")
diff --git a/spec/unit/provider/network_device_spec.rb b/spec/unit/provider/network_device_spec.rb
index 83d2bdc01..e2a87cf4e 100755
--- a/spec/unit/provider/network_device_spec.rb
+++ b/spec/unit/provider/network_device_spec.rb
@@ -3,10 +3,15 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/provider/network_device'
+require 'ostruct'
Puppet::Type.type(:vlan).provide :test, :parent => Puppet::Provider::NetworkDevice do
mk_resource_methods
- def self.lookup(device_url, name)
+ def self.lookup(device, name)
+ end
+
+ def self.device(url)
+ :device
end
end
@@ -34,7 +39,7 @@ describe provider_class do
end
it "should lookup an entry for each passed resource" do
- provider_class.expects(:lookup).with(nil, "200").returns nil
+ provider_class.expects(:lookup).with{ |device,value| value == "200" }.returns nil
provider_class.stubs(:new)
@resource.stubs(:provider=)
@@ -44,7 +49,7 @@ describe provider_class do
describe "resources that do not exist" do
it "should create a provider with :ensure => :absent" do
provider_class.stubs(:lookup).returns(nil)
- provider_class.expects(:new).with(:ensure => :absent).returns "myprovider"
+ provider_class.expects(:new).with(:device, :ensure => :absent).returns "myprovider"
@resource.expects(:provider=).with("myprovider")
provider_class.prefetch(@resources)
end
@@ -54,7 +59,7 @@ describe provider_class do
it "should create a provider with the results of the find and ensure at present" do
provider_class.stubs(:lookup).returns({ :name => "200", :description => "myvlan"})
- provider_class.expects(:new).with(:name => "200", :description => "myvlan", :ensure => :present).returns "myprovider"
+ provider_class.expects(:new).with(:device, :name => "200", :description => "myvlan", :ensure => :present).returns "myprovider"
@resource.expects(:provider=).with("myprovider")
provider_class.prefetch(@resources)
@@ -74,7 +79,7 @@ describe provider_class do
end
it "should store a copy of the hash as its vlan_properties" do
- instance = provider_class.new(:one => :two)
+ instance = provider_class.new(:device, :one => :two)
instance.former_properties.should == {:one => :two}
end
end
@@ -82,7 +87,7 @@ describe provider_class do
describe "when an instance" do
before do
- @instance = provider_class.new
+ @instance = provider_class.new(:device)
@property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property
@resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:description]
@@ -98,12 +103,12 @@ describe provider_class do
end
it "should indicate when the instance already exists" do
- @instance = provider_class.new(:ensure => :present)
+ @instance = provider_class.new(:device, :ensure => :present)
@instance.exists?.should be_true
end
it "should indicate when the instance does not exist" do
- @instance = provider_class.new(:ensure => :absent)
+ @instance = provider_class.new(:device, :ensure => :absent)
@instance.exists?.should be_false
end
diff --git a/spec/unit/provider/package/pkgutil_spec.rb b/spec/unit/provider/package/pkgutil_spec.rb
new file mode 100755
index 000000000..5549b3f6d
--- /dev/null
+++ b/spec/unit/provider/package/pkgutil_spec.rb
@@ -0,0 +1,182 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+provider = Puppet::Type.type(:package).provider(:pkgutil)
+
+describe provider do
+ before(:each) do
+ @resource = Puppet::Type.type(:package).new(
+ :name => "TESTpkg",
+ :ensure => :present,
+ :provider => :pkgutil
+ )
+ @provider = provider.new(@resource)
+
+ # Stub all file and config tests
+ provider.stubs(:healthcheck)
+ end
+
+ it "should have an install method" do
+ @provider.should respond_to(:install)
+ end
+
+ it "should have a latest method" do
+ @provider.should respond_to(:uninstall)
+ end
+
+ it "should have an update method" do
+ @provider.should respond_to(:update)
+ end
+
+ it "should have a latest method" do
+ @provider.should respond_to(:latest)
+ end
+
+ describe "when installing" do
+ it "should use a command without versioned package" do
+ @resource[:ensure] = :latest
+ @provider.expects(:pkguti).with('-y', '-i', 'TESTpkg')
+ @provider.install
+ end
+ end
+
+ describe "when updating" do
+ it "should use a command without versioned package" do
+ @provider.expects(:pkguti).with('-y', '-u', 'TESTpkg')
+ @provider.update
+ end
+ end
+
+ describe "when uninstalling" do
+ it "should call the remove operation" do
+ @provider.expects(:pkguti).with('-y', '-r', 'TESTpkg')
+ @provider.uninstall
+ end
+ end
+
+ describe "when getting latest version" do
+ it "should return TESTpkg's version string" do
+ fake_data = "
+noisy output here
+TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == "1.4.5,REV=2007.11.20"
+ end
+
+ it "should handle TESTpkg's 'SAME' version string" do
+ fake_data = "
+noisy output here
+TESTpkg 1.4.5,REV=2007.11.18 SAME"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == "1.4.5,REV=2007.11.18"
+ end
+
+ it "should handle a non-existent package" do
+ fake_data = "noisy output here
+Not in catalog"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == nil
+ end
+
+ it "should warn on unknown pkgutil noise" do
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns("testingnoise")
+ @provider.latest.should == nil
+ end
+
+ it "should ignore pkgutil noise/headers to find TESTpkg" do
+ fake_data = "# stuff
+=> Fetching new catalog and descriptions (http://mirror.opencsw.org/opencsw/unstable/i386/5.11) if available ...
+2011-02-19 23:05:46 URL:http://mirror.opencsw.org/opencsw/unstable/i386/5.11/catalog [534635/534635] -> \"/var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11.tmp\" [1]
+Checking integrity of /var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11 with gpg.
+gpg: Signature made February 17, 2011 05:27:53 PM GMT using DSA key ID E12E9D2F
+gpg: Good signature from \"Distribution Manager <dm@blastwave.org>\"
+==> 2770 packages loaded from /var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11
+package installed catalog
+TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == "1.4.5,REV=2007.11.20"
+ end
+
+ it "should find REALpkg via an alias (TESTpkg)" do
+ fake_data = "
+noisy output here
+REALpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:name].should == "TESTpkg"
+ end
+ end
+
+ describe "when querying current version" do
+ it "should return TESTpkg's version string" do
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:ensure].should == "1.4.5,REV=2007.11.18"
+ end
+
+ it "should handle a package that isn't installed" do
+ fake_data = "TESTpkg notinst 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:ensure].should == :absent
+ end
+
+ it "should handle a non-existent package" do
+ fake_data = "noisy output here
+Not in catalog"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:ensure].should == :absent
+ end
+ end
+
+ describe "when querying current instances" do
+ it "should warn on unknown pkgutil noise" do
+ provider.expects(:pkguti).with(['-a']).returns("testingnoise")
+ provider.expects(:pkguti).with(['-c']).returns("testingnoise")
+ Puppet.expects(:warning).times(2)
+ provider.expects(:new).never
+ provider.instances.should == []
+ end
+
+ it "should return TESTpkg's version string" do
+ fake_data = "TESTpkg TESTpkg 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-a']).returns fake_data
+
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c']).returns fake_data
+
+ testpkg = mock 'pkg1'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg
+ provider.instances.should == [testpkg]
+ end
+
+ it "should also return both TESTpkg and mypkg alias instances" do
+ fake_data = "mypkg TESTpkg 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-a']).returns fake_data
+
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c']).returns fake_data
+
+ testpkg = mock 'pkg1'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg
+
+ aliaspkg = mock 'pkg2'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "mypkg", :provider => :pkgutil).returns aliaspkg
+
+ provider.instances.should == [testpkg,aliaspkg]
+ end
+
+ it "shouldn't mind noise in the -a output" do
+ fake_data = "noisy output here"
+ provider.expects(:pkguti).with(['-a']).returns fake_data
+
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c']).returns fake_data
+
+ testpkg = mock 'pkg1'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg
+
+ provider.instances.should == [testpkg]
+ end
+ end
+
+end
diff --git a/spec/unit/provider/vlan/cisco_spec.rb b/spec/unit/provider/vlan/cisco_spec.rb
index bb243a75e..a67290eb4 100755
--- a/spec/unit/provider/vlan/cisco_spec.rb
+++ b/spec/unit/provider/vlan/cisco_spec.rb
@@ -8,12 +8,13 @@ provider_class = Puppet::Type.type(:vlan).provider(:cisco)
describe provider_class do
before do
+ @device = stub_everything 'device'
@resource = stub("resource", :name => "200")
- @provider = provider_class.new(@resource)
+ @provider = provider_class.new(@device, @resource)
end
- it "should have a parent of Puppet::Provider::NetworkDevice" do
- provider_class.should < Puppet::Provider::NetworkDevice
+ it "should have a parent of Puppet::Provider::Cisco" do
+ provider_class.should < Puppet::Provider::Cisco
end
it "should have an instances method" do
@@ -22,31 +23,24 @@ describe provider_class do
describe "when looking up instances at prefetch" do
before do
- @device = stub_everything 'device'
- Puppet::Util::NetworkDevice::Cisco::Device.stubs(:new).returns(@device)
@device.stubs(:command).yields(@device)
end
- it "should initialize the network device with the given url" do
- Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).with(:url).returns(@device)
- provider_class.lookup(:url, "200")
- end
-
it "should delegate to the device vlans" do
@device.expects(:parse_vlans)
- provider_class.lookup("", "200")
+ provider_class.lookup(@device, "200")
end
it "should return only the given vlan" do
@device.expects(:parse_vlans).returns({"200" => { :description => "thisone" }, "1" => { :description => "nothisone" }})
- provider_class.lookup("", "200").should == {:description => "thisone" }
+ provider_class.lookup(@device, "200").should == {:description => "thisone" }
end
end
describe "when an instance is being flushed" do
it "should call the device update_vlan method with its vlan id, current attributes, and desired attributes" do
- @instance = provider_class.new(:ensure => :present, :name => "200", :description => "myvlan")
+ @instance = provider_class.new(@device, :ensure => :present, :name => "200", :description => "myvlan")
@instance.description = "myvlan2"
@instance.resource = @resource
@resource.stubs(:[]).with(:name).returns("200")
diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb
index ae65aa91a..ebf523eab 100755
--- a/spec/unit/resource/catalog_spec.rb
+++ b/spec/unit/resource/catalog_spec.rb
@@ -592,6 +592,7 @@ describe Puppet::Resource::Catalog, "when compiling" do
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:add_times)
+ @transaction.stubs(:for_network_device=)
Puppet.settings.stubs(:use)
end
diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb
index 4157e58ac..d7788c06c 100755
--- a/spec/unit/transaction_spec.rb
+++ b/spec/unit/transaction_spec.rb
@@ -270,6 +270,24 @@ describe Puppet::Transaction do
@resource.stubs(:virtual?).returns true
@transaction.should be_skip(@resource)
end
+
+ it "should skip device only resouce on normal host" do
+ @resource.stubs(:appliable_to_device?).returns true
+ @transaction.for_network_device = false
+ @transaction.should be_skip(@resource)
+ end
+
+ it "should not skip device only resouce on remote device" do
+ @resource.stubs(:appliable_to_device?).returns true
+ @transaction.for_network_device = true
+ @transaction.should_not be_skip(@resource)
+ end
+
+ it "should skip host resouce on device" do
+ @resource.stubs(:appliable_to_device?).returns false
+ @transaction.for_network_device = true
+ @transaction.should be_skip(@resource)
+ end
end
describe "when determining if tags are missing" do
diff --git a/spec/unit/type/interface_spec.rb b/spec/unit/type/interface_spec.rb
index 68f4c765f..12ba225d9 100755
--- a/spec/unit/type/interface_spec.rb
+++ b/spec/unit/type/interface_spec.rb
@@ -3,6 +3,7 @@
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Type.type(:interface) do
+
it "should have a 'name' parameter'" do
Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1")[:name].should == "FastEthernet 0/1"
end
@@ -15,6 +16,10 @@ describe Puppet::Type.type(:interface) do
Puppet::Type.type(:interface).attrtype(:ensure).should == :property
end
+ it "should be applied on device" do
+ Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1").should be_appliable_to_device
+ end
+
[:description, :speed, :duplex, :native_vlan, :encapsulation, :mode, :allowed_trunk_vlans, :etherchannel, :ipaddress].each do |p|
it "should have a #{p} property" do
Puppet::Type.type(:interface).attrtype(p).should == :property
diff --git a/spec/unit/type/schedule_spec.rb b/spec/unit/type/schedule_spec.rb
index 7599411e4..33064ca6f 100755
--- a/spec/unit/type/schedule_spec.rb
+++ b/spec/unit/type/schedule_spec.rb
@@ -36,6 +36,14 @@ describe Puppet::Type.type(:schedule) do
describe Puppet::Type.type(:schedule) do
include ScheduleTesting
+ it "should apply to device" do
+ @schedule.should be_appliable_to_device
+ end
+
+ it "should apply to host" do
+ @schedule.should be_appliable_to_host
+ end
+
it "should default to :distance for period-matching" do
@schedule[:periodmatch].should == :distance
end
diff --git a/spec/unit/type/vlan_spec.rb b/spec/unit/type/vlan_spec.rb
index 2983a58e9..3bee14bbd 100755
--- a/spec/unit/type/vlan_spec.rb
+++ b/spec/unit/type/vlan_spec.rb
@@ -3,6 +3,7 @@
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Type.type(:vlan) do
+
it "should have a 'name' parameter'" do
Puppet::Type.type(:vlan).new(:name => "200")[:name].should == "200"
end
@@ -11,6 +12,10 @@ describe Puppet::Type.type(:vlan) do
Puppet::Type.type(:vlan).new(:name => "200", :device_url => :device)[:device_url].should == :device
end
+ it "should be applied on device" do
+ Puppet::Type.type(:vlan).new(:name => "200").should be_appliable_to_device
+ end
+
it "should have an ensure property" do
Puppet::Type.type(:vlan).attrtype(:ensure).should == :property
end
diff --git a/spec/unit/util/network_device/cisco/device_spec.rb b/spec/unit/util/network_device/cisco/device_spec.rb
index 82b0666e6..33242a1ab 100755
--- a/spec/unit/util/network_device/cisco/device_spec.rb
+++ b/spec/unit/util/network_device/cisco/device_spec.rb
@@ -392,130 +392,17 @@ eos
@cisco.parse_interface_config("Gi0/17").should == {:etherchannel=>"1"}
end
end
+
+ describe "when finding device facts" do
+ it "should delegate to the cisco facts entity" do
+ facts = stub 'facts'
+ Puppet::Util::NetworkDevice::Cisco::Facts.expects(:new).returns(facts)
+
+ facts.expects(:retrieve).returns(:facts)
+
+ @cisco.facts.should == :facts
+ end
+ end
+
end
-# static access
-# Switch#sh interfaces FastEthernet 0/1 switchport
-# Name: Fa0/1
-# Switchport: Enabled
-# Administrative mode: static access
-# Operational Mode: static access
-# Administrative Trunking Encapsulation: isl
-# Operational Trunking Encapsulation: isl
-# Negotiation of Trunking: Disabled
-# Access Mode VLAN: 100 (SHDSL)
-# Trunking Native Mode VLAN: 1 (default)
-# Trunking VLANs Enabled: NONE
-# Pruning VLANs Enabled: NONE
-#
-# Priority for untagged frames: 0
-# Override vlan tag priority: FALSE
-# Voice VLAN: none
-# Appliance trust: none
-# Self Loopback: No
-# Switch#
-
-# c2960#sh interfaces GigabitEthernet 0/1 switchport
-# Name: Gi0/1
-# Switchport: Enabled
-# Administrative Mode: trunk
-# Operational Mode: trunk
-# Administrative Trunking Encapsulation: dot1q
-# Operational Trunking Encapsulation: dot1q
-# Negotiation of Trunking: On
-# Access Mode VLAN: 1 (default)
-# Trunking Native Mode VLAN: 1 (default)
-# Administrative Native VLAN tagging: enabled
-# Voice VLAN: none
-# Administrative private-vlan host-association: none
-# Administrative private-vlan mapping: none
-# Administrative private-vlan trunk native VLAN: none
-# Administrative private-vlan trunk Native VLAN tagging: enabled
-# Administrative private-vlan trunk encapsulation: dot1q
-# Administrative private-vlan trunk normal VLANs: none
-# Administrative private-vlan trunk associations: none
-# Administrative private-vlan trunk mappings: none
-# Operational private-vlan: none
-# Trunking VLANs Enabled: 1,99
-# Pruning VLANs Enabled: 2-1001
-# Capture Mode Disabled
-# Capture VLANs Allowed: ALL
-#
-# Protected: false
-# Unknown unicast blocked: disabled
-# Unknown multicast blocked: disabled
-# Appliance trust: none
-# c2960#
-
-# c2960#sh interfaces GigabitEthernet 0/2 switchport
-# Name: Gi0/2
-# Switchport: Enabled
-# Administrative Mode: static access
-# Operational Mode: static access
-# Administrative Trunking Encapsulation: dot1q
-# Operational Trunking Encapsulation: native
-# Negotiation of Trunking: Off
-# Access Mode VLAN: 99 (MGMT)
-# Trunking Native Mode VLAN: 1 (default)
-# Administrative Native VLAN tagging: enabled
-# Voice VLAN: none
-# Administrative private-vlan host-association: none
-# Administrative private-vlan mapping: none
-# Administrative private-vlan trunk native VLAN: none
-# Administrative private-vlan trunk Native VLAN tagging: enabled
-# Administrative private-vlan trunk encapsulation: dot1q
-# Administrative private-vlan trunk normal VLANs: none
-# Administrative private-vlan trunk associations: none
-# Administrative private-vlan trunk mappings: none
-# Operational private-vlan: none
-# Trunking VLANs Enabled: ALL
-# Pruning VLANs Enabled: 2-1001
-# Capture Mode Disabled
-# Capture VLANs Allowed: ALL
-#
-# Protected: false
-# Unknown unicast blocked: disabled
-# Unknown multicast blocked: disabled
-# Appliance trust: none
-# c2960#
-
-# c877#sh interfaces FastEthernet 1 switchport
-# Name: Fa1
-# Switchport: Enabled
-# Administrative Mode: trunk
-# Operational Mode: trunk
-# Administrative Trunking Encapsulation: dot1q
-# Operational Trunking Encapsulation: dot1q
-# Negotiation of Trunking: Disabled
-# Access Mode VLAN: 0 ((Inactive))
-# Trunking Native Mode VLAN: 1 (default)
-# Trunking VLANs Enabled: ALL
-# Trunking VLANs Active: 1
-# Protected: false
-# Priority for untagged frames: 0
-# Override vlan tag priority: FALSE
-# Voice VLAN: none
-# Appliance trust: none
-
-
-# c2960#sh etherchannel summary
-# Flags: D - down P - bundled in port-channel
-# I - stand-alone s - suspended
-# H - Hot-standby (LACP only)
-# R - Layer3 S - Layer2
-# U - in use f - failed to allocate aggregator
-#
-# M - not in use, minimum links not met
-# u - unsuitable for bundling
-# w - waiting to be aggregated
-# d - default port
-#
-#
-# Number of channel-groups in use: 1
-# Number of aggregators: 1
-#
-# Group Port-channel Protocol Ports
-# ------+-------------+-----------+-----------------------------------------------
-# 1 Po1(SU) LACP Gi0/17(P) Gi0/18(P)
-#
-# c2960#
diff --git a/spec/unit/util/network_device/cisco/facts_spec.rb b/spec/unit/util/network_device/cisco/facts_spec.rb
new file mode 100644
index 000000000..bb29ac292
--- /dev/null
+++ b/spec/unit/util/network_device/cisco/facts_spec.rb
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../../spec_helper'
+
+require 'puppet/util/network_device'
+require 'puppet/util/network_device/cisco/facts'
+
+describe Puppet::Util::NetworkDevice::Cisco::Facts do
+ before(:each) do
+ @transport = stub_everything 'transport'
+ @facts = Puppet::Util::NetworkDevice::Cisco::Facts.new(@transport)
+ end
+
+ {
+ "cisco WS-C2924C-XL (PowerPC403GA) processor (revision 0x11) with 8192K/1024K bytes of memory." => {:hardwaremodel => "WS-C2924C-XL", :memorysize => "8192K", :processor => "PowerPC403GA", :hardwarerevision => "0x11" },
+ "Cisco 1841 (revision 5.0) with 355328K/37888K bytes of memory." => {:hardwaremodel=>"1841", :memorysize => "355328K", :hardwarerevision => "5.0" },
+ "Cisco 877 (MPC8272) processor (revision 0x200) with 118784K/12288K bytes of memory." => {:hardwaremodel=>"877", :memorysize => "118784K", :processor => "MPC8272", :hardwarerevision => "0x200" },
+ "cisco WS-C2960G-48TC-L (PowerPC405) processor (revision C0) with 61440K/4088K bytes of memory." => {:hardwaremodel=>"WS-C2960G-48TC-L", :memorysize => "61440K", :processor => "PowerPC405", :hardwarerevision => "C0" },
+ "cisco WS-C2950T-24 (RC32300) processor (revision R0) with 19959K bytes of memory." => {:hardwaremodel=>"WS-C2950T-24", :memorysize => "19959K", :processor => "RC32300", :hardwarerevision => "R0" }
+ }.each do |ver, expected|
+ it "should parse show ver output for hardware device facts" do
+ @transport.stubs(:command).with("sh ver").returns(<<eos)
+Switch>sh ver
+#{ver}
+Switch>
+eos
+ @facts.parse_show_ver.should == expected
+ end
+ end
+
+ {
+"Switch uptime is 1 year, 12 weeks, 6 days, 22 hours, 32 minutes" => { :hostname => "Switch", :uptime => "1 year, 12 weeks, 6 days, 22 hours, 32 minutes", :uptime_seconds => 39393120, :uptime_days => 455 },
+"c2950 uptime is 3 weeks, 1 day, 23 hours, 36 minutes" => { :hostname => "c2950", :uptime => "3 weeks, 1 day, 23 hours, 36 minutes", :uptime_days => 22, :uptime_seconds => 1985760},
+"router uptime is 5 weeks, 1 day, 3 hours, 30 minutes" => { :hostname => "router", :uptime => "5 weeks, 1 day, 3 hours, 30 minutes", :uptime_days => 36, :uptime_seconds => 3123000 },
+"c2950 uptime is 1 minute" => { :hostname => "c2950", :uptime => "1 minute", :uptime_days => 0, :uptime_seconds => 60 }
+ }.each do |ver, expected|
+ it "should parse show ver output for device uptime facts" do
+ @transport.stubs(:command).with("sh ver").returns(<<eos)
+Switch>sh ver
+#{ver}
+Switch>
+eos
+ @facts.parse_show_ver.should == expected
+ end
+ end
+
+ {
+"IOS (tm) C2900XL Software (C2900XL-C3H2S-M), Version 12.0(5)WC10, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.0(5)WC10", :operatingsystemmajrelease => "12.0WC", :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.1EA", :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.2SE", :operatingsystemfeature => "LANBASEK9"},
+"Cisco IOS Software, C870 Software (C870-ADVIPSERVICESK9-M), Version 12.4(11)XJ4, RELEASE SOFTWARE (fc2)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(11)XJ4", :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"},
+ }.each do |ver, expected|
+ it "should parse show ver output for device software version facts" do
+ @transport.stubs(:command).with("sh ver").returns(<<eos)
+Switch>sh ver
+#{ver}
+Switch>
+eos
+ @facts.parse_show_ver.should == expected
+ end
+ end
+end
diff --git a/spec/unit/util/network_device/config_spec.rb b/spec/unit/util/network_device/config_spec.rb
new file mode 100644
index 000000000..52796f30b
--- /dev/null
+++ b/spec/unit/util/network_device/config_spec.rb
@@ -0,0 +1,102 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+require 'puppet/util/network_device/config'
+
+describe Puppet::Util::NetworkDevice::Config do
+ before(:each) do
+ Puppet[:deviceconfig] = "/dummy"
+ FileTest.stubs(:exists?).with("/dummy").returns(true)
+ end
+
+ describe "when initializing" do
+ before :each do
+ Puppet::Util::NetworkDevice::Config.any_instance.stubs(:read)
+ end
+
+ it "should use the deviceconfig setting as pathname" do
+ Puppet.expects(:[]).with(:deviceconfig).returns("/dummy")
+
+ Puppet::Util::NetworkDevice::Config.new
+ end
+
+ it "should raise an error if no file is defined finally" do
+ Puppet.expects(:[]).with(:deviceconfig).returns(nil)
+
+ lambda { Puppet::Util::NetworkDevice::Config.new }.should raise_error(Puppet::DevError)
+ end
+
+ it "should read and parse the file" do
+ Puppet::Util::NetworkDevice::Config.any_instance.expects(:read)
+
+ Puppet::Util::NetworkDevice::Config.new
+ end
+ end
+
+ describe "when parsing device" do
+ before :each do
+ @config = Puppet::Util::NetworkDevice::Config.new
+ @config.stubs(:changed?).returns(true)
+ @fd = stub 'fd'
+ File.stubs(:open).yields(@fd)
+ end
+
+ it "should skip comments" do
+ @fd.stubs(:each).yields(' # comment')
+
+ OpenStruct.expects(:new).never
+
+ @config.read
+ end
+
+ it "should increment line number even on commented lines" do
+ @fd.stubs(:each).multiple_yields(' # comment','[router.puppetlabs.com]')
+
+ @config.read
+ @config.devices.should be_include('router.puppetlabs.com')
+ end
+
+ it "should skip blank lines" do
+ @fd.stubs(:each).yields(' ')
+
+ @config.read
+ @config.devices.should be_empty
+ end
+
+ it "should produce the correct line number" do
+ @fd.stubs(:each).multiple_yields(' ', '[router.puppetlabs.com]')
+
+ @config.read
+ @config.devices['router.puppetlabs.com'].line.should == 2
+ end
+
+ it "should throw an error if the current device already exists" do
+ @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', '[router.puppetlabs.com]')
+
+ lambda { @config.read }.should raise_error
+ end
+
+ it "should create a new device for each found device line" do
+ @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', '[swith.puppetlabs.com]')
+
+ @config.read
+ @config.devices.size.should == 2
+ end
+
+ it "should parse the device type" do
+ @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco')
+
+ @config.read
+ @config.devices['router.puppetlabs.com'].provider.should == 'cisco'
+ end
+
+ it "should parse the device url" do
+ @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/')
+
+ @config.read
+ @config.devices['router.puppetlabs.com'].url.should == 'ssh://test/'
+ end
+ end
+
+end \ No newline at end of file
diff --git a/spec/unit/util/network_device/transport/ssh_spec.rb b/spec/unit/util/network_device/transport/ssh_spec.rb
index 0e91ed9f9..18d22a953 100755
--- a/spec/unit/util/network_device/transport/ssh_spec.rb
+++ b/spec/unit/util/network_device/transport/ssh_spec.rb
@@ -30,6 +30,14 @@ describe Puppet::Util::NetworkDevice::Transport::Ssh, :if => Puppet.features.ssh
@transport.connect
end
+ it "should raise a Puppet::Error when encountering an authentication failure" do
+ Net::SSH.expects(:start).raises Net::SSH::AuthenticationFailed
+ @transport.host = "localhost"
+ @transport.user = "user"
+
+ lambda { @transport.connect }.should raise_error Puppet::Error
+ end
+
describe "when connected" do
before(:each) do
@ssh = stub_everything 'ssh'
diff --git a/spec/unit/util/network_device_spec.rb b/spec/unit/util/network_device_spec.rb
new file mode 100644
index 000000000..0f7c6036b
--- /dev/null
+++ b/spec/unit/util/network_device_spec.rb
@@ -0,0 +1,50 @@
+#!/usr/bin/env rspec
+require 'spec_helper'
+
+require 'ostruct'
+require 'puppet/util/network_device'
+
+describe Puppet::Util::NetworkDevice do
+
+ before(:each) do
+ @device = OpenStruct.new(:name => "name", :provider => "test")
+ end
+
+ after(:each) do
+ Puppet::Util::NetworkDevice.teardown
+ end
+
+ class Puppet::Util::NetworkDevice::Test
+ class Device
+ def initialize(device)
+ end
+ end
+ end
+
+ describe "when initializing the remote network device singleton" do
+ it "should load the network device code" do
+ Puppet::Util::NetworkDevice.expects(:require)
+ Puppet::Util::NetworkDevice.init(@device)
+ end
+
+ it "should create a network device instance" do
+ Puppet::Util::NetworkDevice.stubs(:require)
+ Puppet::Util::NetworkDevice::Test::Device.expects(:new)
+ Puppet::Util::NetworkDevice.init(@device)
+ end
+
+ it "should raise an error if the remote device instance can't be created" do
+ Puppet::Util::NetworkDevice.stubs(:require).raises("error")
+ lambda { Puppet::Util::NetworkDevice.init(@device) }.should raise_error
+ end
+
+ it "should let caller to access the singleton device" do
+ device = stub 'device'
+ Puppet::Util::NetworkDevice.stubs(:require)
+ Puppet::Util::NetworkDevice::Test::Device.expects(:new).returns(device)
+ Puppet::Util::NetworkDevice.init(@device)
+
+ Puppet::Util::NetworkDevice.current.should == device
+ end
+ end
+end
diff --git a/spec/unit/util/queue/stomp_spec.rb b/spec/unit/util/queue/stomp_spec.rb
index f67189cf5..99c77d0b4 100755
--- a/spec/unit/util/queue/stomp_spec.rb
+++ b/spec/unit/util/queue/stomp_spec.rb
@@ -63,26 +63,26 @@ describe 'Puppet::Util::Queue::Stomp', :if => Puppet.features.stomp? do
end
end
- describe "when sending a message" do
+ describe "when publishing a message" do
before do
@client = stub 'client'
Stomp::Client.stubs(:new).returns @client
@queue = Puppet::Util::Queue::Stomp.new
end
- it "should send it to the queue client instance" do
- @client.expects(:send).with { |queue, msg, options| msg == "Smite!" }
- @queue.send_message('fooqueue', 'Smite!')
+ it "should publish it to the queue client instance" do
+ @client.expects(:publish).with { |queue, msg, options| msg == "Smite!" }
+ @queue.publish_message('fooqueue', 'Smite!')
end
- it "should send it to the transformed queue name" do
- @client.expects(:send).with { |queue, msg, options| queue == "/queue/fooqueue" }
- @queue.send_message('fooqueue', 'Smite!')
+ it "should publish it to the transformed queue name" do
+ @client.expects(:publish).with { |queue, msg, options| queue == "/queue/fooqueue" }
+ @queue.publish_message('fooqueue', 'Smite!')
end
- it "should send it as a persistent message" do
- @client.expects(:send).with { |queue, msg, options| options[:persistent] == true }
- @queue.send_message('fooqueue', 'Smite!')
+ it "should publish it as a persistent message" do
+ @client.expects(:publish).with { |queue, msg, options| options[:persistent] == true }
+ @queue.publish_message('fooqueue', 'Smite!')
end
end
diff --git a/spec/watchr.rb b/spec/watchr.rb
index 26919d1a1..c0f1d0257 100755
--- a/spec/watchr.rb
+++ b/spec/watchr.rb
@@ -85,7 +85,11 @@ def run_spec_files(files)
else
opts = File.readlines('spec/spec.opts').collect { |l| l.chomp }.join(" ")
end
- run_spec("rspec #{opts} --tty #{files.join(' ')}")
+ begin
+ run_spec("rspec #{opts} --tty #{files.join(' ')}")
+ rescue => detail
+ puts "Failed to load #{files}: #{detail}"
+ end
end
def run_all_tests
diff --git a/test/lib/puppettest/railstesting.rb b/test/lib/puppettest/railstesting.rb
index e05511e3b..f5666f2c4 100644
--- a/test/lib/puppettest/railstesting.rb
+++ b/test/lib/puppettest/railstesting.rb
@@ -14,39 +14,5 @@ module PuppetTest::RailsTesting
def railsinit
Puppet::Rails.init
end
-
- def railsteardown
- Puppet::Rails.teardown if Puppet[:dbadapter] != "sqlite3"
- end
-
- def railsresource(type = "file", title = "/tmp/testing", params = {})
- railsteardown
- railsinit
-
- # We need a host for resources
- #host = Puppet::Rails::Host.new(:name => Facter.value("hostname"))
-
- # Now build a resource
- resources = []
-
- resources << mkresource(
- :type => type, :title => title, :exported => true,
-
- :parameters => params)
-
- # Now collect our facts
- facts = Facter.to_hash
-
- # Now try storing our crap
- host = nil
- node = mknode(facts["hostname"])
- node.parameters = facts
- assert_nothing_raised {
- host = Puppet::Rails::Host.store(node, resources)
- }
-
- # Now save the whole thing
- host.save
- end
end