[Pkg-puppet-devel] [SCM] Puppet packaging for Debian branch, experimental, updated. debian/2.6.8-1-844-g7ec39d5

Pieter van de Bruggen pieter at puppetlabs.com
Tue May 10 08:16:16 UTC 2011


The following commit has been merged in the experimental branch:
commit 07b677c5f6af8def03c5c30393fd83bc3986239a
Author: Pieter van de Bruggen <pieter at puppetlabs.com>
Date:   Mon Apr 18 13:40:31 2011 -0700

    Merge remote-tracking branch 'community/feature/puppet-device' into 2.7.x
    
    Reviewed-By: Mike Stahnke

diff --git a/lib/puppet/application/device.rb b/lib/puppet/application/device.rb
new file mode 100644
index 0000000..df5bac2
--- /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/defaults.rb b/lib/puppet/defaults.rb
index 680762b..dbd5a94 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -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",
diff --git a/lib/puppet/indirector/facts/network_device.rb b/lib/puppet/indirector/facts/network_device.rb
new file mode 100644
index 0000000..c9bac80
--- /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/provider/cisco.rb b/lib/puppet/provider/cisco.rb
new file mode 100644
index 0000000..918982f
--- /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 f3bd202..795a7f1 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 58865fd..b178df9 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.
diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb
index 46e172c..3421d35 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 98c2965..a6cff9b 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 d7845fb..3728a2f 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 c0e5d39..656b8f2 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 7560a05..d3b5cb0 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 5fb008f..f60f96f 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 6708ea4..e39daf9 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 a884b86..714d03f 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 bca6601..d9c1aa3 100644
--- a/lib/puppet/util/network_device.rb
+++ b/lib/puppet/util/network_device.rb
@@ -1,2 +1,12 @@
-module Puppet::Util::NetworkDevice
+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
 end
\ No newline at end of file
diff --git a/lib/puppet/util/network_device/base.rb b/lib/puppet/util/network_device/base.rb
index ff96c86..7d6c3fc 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 1f35099..005470e 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 0000000..40e2b37
--- /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 0000000..17f4e25
--- /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 e64fe9b..cef8f38 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 b3cf51b..bf0e719 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/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb
index 41a1eba..78d62fc 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/unit/application/device_spec.rb b/spec/unit/application/device_spec.rb
new file mode 100644
index 0000000..c3c2209
--- /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.stubs(:terminus_class=)
+    Puppet::Node.stubs(:cache_class=)
+    Puppet::Node::Facts.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.stubs(:terminus_class=)
+      Puppet::Resource::Catalog.stubs(:terminus_class=)
+      Puppet::Resource::Catalog.stubs(:cache_class=)
+      Puppet::Node::Facts.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/indirector/facts/network_device_spec.rb b/spec/unit/indirector/facts/network_device_spec.rb
new file mode 100644
index 0000000..302a810
--- /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/provider/cisco_spec.rb b/spec/unit/provider/cisco_spec.rb
new file mode 100644
index 0000000..0832073
--- /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 d1f7060..c18f87c 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 83d2bdc..e2a87cf 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/vlan/cisco_spec.rb b/spec/unit/provider/vlan/cisco_spec.rb
index bb243a7..a67290e 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 ae65aa9..ebf523e 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 4157e58..d7788c0 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 68f4c76..12ba225 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 7599411..33064ca 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 2983a58..3bee14b 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 82b0666..33242a1 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 0000000..bb29ac2
--- /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 0000000..52796f3
--- /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 0e91ed9..18d22a9 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 0000000..70cb509
--- /dev/null
+++ b/spec/unit/util/network_device_spec.rb
@@ -0,0 +1,46 @@
+#!/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
+
+  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
\ No newline at end of file

-- 
Puppet packaging for Debian



More information about the Pkg-puppet-devel mailing list