Resource API transports

A transport connects providers to remote resources, such as a device, cloud infrastructure, or a REST API.

The transport class contains the code for managing connections and processing information to and from the remote resource. The transport schema, similar to a resource type, describes the structure of the data that is passed for it to make a connection.

Transport implementation methods

When you are writing a transport class to manage remote resources, use the following methods as appropriate:

  • initialize(context, connection_info)

    • The connection_info contains a hash that matches the schema. After you run the initialize method, the provider assumes that you have defined your transport in such as way as to ensure that it’s ready for processing requests. The transport will report connection errors by throwing an exception, for example, if the network is unreachable or the credentials are rejected. In some cases, for example when the target is a REST API, no processing happens during initialization.

  • verify(context)

    • Use this method to check whether the transport can connect to the remote target. If the connection fails, the transport will raise an exception.

  • facts(context)

    • Use this method to access the target and the facts hash which contains a subset of default facts from Facter, and more specific facts appropriate for the target.

  • close(context)

    • Use this method to close the connection. Calling this method releases the transport so that you can't use it any more and frees up cache and operating system resources, such as open connections. For implementation quality, the library will ignore exceptions that are thrown.

The context is the primary way to signal changes, successes, and failures to the runtime environment. For more information, see Runtime environment.

An example of a transport class:

# lib/puppet/transport/device_type.rb
module Puppet::Transport
  # The main connection class to a Device endpoint
  class DeviceType
    def initialize(context, connection_info)
      # Add additional validation for connection_info
      # and pre-load a connection if it is useful for your target
    end

    def verify(context)
      # Test that transport can talk to the remote target
    end

    def facts(context)
      # Access target, return a Facter facts hash
    end

    def close(context)
      # Close connection, free up resources
    end
  end
end

An example of a corresponding schema:

# lib/puppet/transport/device_type.rb
Puppet::ResourceAPI.register_transport(
  name: 'device_type', # points at class Puppet::Transport::DeviceType
  desc: 'Connects to a device_type',
  connection_info: {
    host: {
      type: 'String',
      desc: 'The host to connect to.',
    },
    user: {
      type: 'String',
      desc: 'The user.',
    },
    password: {
      type: 'String',
      sensitive: true,
      desc: 'The password to connect.',
    },
    enable_password: {
      type: 'String',
      sensitive: true,
      desc: 'The password escalate to enable access.',
    },
    port: {
      type: 'Integer',
      desc: 'The port to connect to.',
    },
  },
)

If the following attributes apply to your target, use these names for consistency across transports:

  • uri: use to specify which URL to connect to.

  • host: use to specify an IP or address to connect to.

  • protocol: use to specify which protocol the transport uses, for example http, https, ssh or tcp.

  • user: the user you want the transport to connect as.

  • port: the port you want the transport to connect to.

Do not use the following keywords in when writing the connection_info:

  • name

  • path

  • query

  • run-on

  • remote-transport

  • remote-*

  • implementations

To ensure that the data the schema passes to the implementation is handled securely, set password attributes to sensitive: true. Attributes marked with the sensitive flag allow a user interface based on this schema to make appropriate presentation choices, such as obscuring the password field. Values that you’ve marked sensitive are passed to the transport wrapped in the type Puppet::Pops::Types::PSensitiveType::Sensitive. This keeps the value from being logged or saved inadvertently while it is being transmitted between components. To access the sensitive value within the transport, use the unwrap method, for example, connection_info[:password].unwrap.

Errors and retry handling in transport implementation

The Resource API does not put many constraints on when and how a transport can fail. The remote resource you are connecting to will have it's own device specific connection and error handling capabilities. Be aware of the following issues that may arise:

  • Your initial connection might fail. To retry making the connection, verify whether you have network problems or whether there has been a service restart of the target that you are trying to connect to. As part of the retry logic, the transport avoids passing these issues to other parts of your system and waits up to 30 seconds for a single target to recover. When you execute a retry, the transport logs transient problems at the notice level.

  • After you make your connection and have run the initialize method, the transport might apply deeper validation to the passed connection information — like mutual exclusivity of optional values, for example, password or key — and throw an ArgumentError. The transport then tries to establish a connection to the remote target. If this fails due to unrecoverable errors, it throws another exception.

  • The verify and facts methods, like initialize, throw exceptions only when unrecoverable errors are encountered, or when the retry logic times out.

Port your existing device code to transports

If you have old code that uses Device, port it by updating your code with the following replacements as appropriate:

  • Move the device class to Puppet::Transport

  • Change Util::NetworkDevice::NAME::Device to ResourceApi::Transport::NAME

  • Change the initialization to accept and process a connection_info hash.

  • When accessing the connection_info in your new transport, change all string keys to symbols, for example, name to :name.

  • Add context as the first argument to your initialize and facts method.

  • Change puppet/util/network_device/NAME/device to puppet/transport/NAME

  • Replace calls to Puppet logging with calls to the context logger

If you can't port your code at this time, your providers can still access your Device through context.device, but you won't be able to make use of the functionality in Bolt plans.

After porting your code, you will have a transport class and a shim Device class that connects your transport in a way that Puppet understands. Specify the transport name in the super call to make the connection:

# lib/puppet/type/nx9k_vlan.rb
Puppet::ResourceApi.register_type(
  name: 'nx9k_vlan',
  features: [ 'remote_resource' ],
  # ...
)

# lib/puppet/util/network_device/nexus/device.rb
require 'puppet/resource_api/transport/wrapper'
# force registering the transport
require 'puppet/transport/schema/nexus'

module Puppet::Util::NetworkDevice::Nexus
  class Device < Puppet::ResourceApi::Transport::Wrapper
    def initialize(url_or_config, _options = {})
      super('nexus', url_or_config)
    end
  end
end

Agent versions 6.0 to 6.3 are incompatible with this way of executing remote content. These versions will not be supported after support for PE 2019.0 ends.