- Overview
- Before You Begin
- Start here: Clone the Repo
- Basic Example: feature tunnel
- Complex Example: router eigrp
- Copy Manifests Back to
examplesdirectory - Next Steps
This document is a guide for writing new Puppet resource types and providers for Cisco NX-OS.
There are multiple components involved when creating new resources, it would be suggested to review custom resources as it outlines the various ways a custom type and provider can be written for Puppet. This document mainly focuses on the low-level type and provider files, for historical context.
Any new types should follow the Resource API reference, which is easy to get started with the PDK using pdk new provider:
pdk new provider fooMake sure to edit the features of the newly generated type to support dual-mode:
require 'puppet/resource_api'
Puppet::ResourceApi.register_type(
name: 'foo',
...
features: ['canonicalize','simple_get_filter'] + ( Puppet::Util::NetworkDevice.current.nil? ? [] : ['remote_resource'] ),
attributes: {
...
},
)-
Every resource is associated with a resource type, which determines the kind of configuration it manages.
-
Resource providers are essentially backends that implement support for a specific implementation of a given resource type.
-
The types and providers work in conjunction with a node_utils API, which is the interface between Puppet agent and the NX-OS CLI. Please see the [README-develop-node-utils-APIs.md] (https://github.com/cisco/cisco-network-node-utils/blob/master/docs/README-develop-node-utils-APIs.md) guide for more information on writing node_utils APIs.
This document relies heavily on example code. The examples in this document can be written independently, but they are intended to work in conjuction with the example node_utils APIs created in the README-develop-node_utils-APIs.md guide. The examples in that guide are based on code templates for the feature tunnel CLI and the router eigrp CLI. Note that some people prefer to write the node_utils API before the resource types and providers, while others might prefer the opposite workflow.
This development guide uses tools which are packaged as a gems that need to be installed on your development server.
gem install rubocop
gem install puppet-lintNOTE: If you are working from a server where you don't have admin/root privilages, use the following commands to install the gems and then update the PATH to include ~/.gem/ruby/x.x.x/bin
gem install --user-install rubocop
gem install --user-install puppet-lintThis development guide assumes that the puppetlabs-ciscopuppet module is installed on your puppet master. The simplest way to do this is to clone the cisco-network-puppet-module git repository on your puppet master, however we do not recommend that your puppet master act as your primary development server.
Follow the Initial Setup step to build and install the puppetlabs-ciscopuppet module on your puppet master.
Please see the CONTRIBUTING document for workflow instructions. In general, fork the ciscopuppet repository for your changes and submit a pull request when it is ready for commit.
First fork the cisco-network-puppet-module git repository
Clone the cisco-network-puppet-module repo from your fork into a workspace on your development server.
git clone https://github.com/YOUR-USERNAME/cisco-network-puppet-module.git
cd cisco-network-puppet-moduleAs a best practice go ahead and create a topic/feature branch for your feature work using the git branch feature/<feature_name> command.
git branch feature/tunnel
git branch feature/eigrp
git branch
* develop
feature/tunnel
feature/eigrpBefore you start working on the tunnel feature, checkout the feature branch you created earlier.
git checkout feature/tunnel
git branch
develop
* feature/tunnel
feature/eigrpThe NX-OS CLI for feature tunnel is a simple on / off style configuration:
[no] feature tunnel
This resource has no other properties.
- There are template files in
/docsthat might help when you write new types and providers. These templates provide most of the necessary code with a few customizations required for a new resource. Copy thetemplate-type-feature.rbfile to use as the basis for our newcisco_tunnel.rbtype file:
cp docs/template-type-feature.rb lib/puppet/type/cisco_tunnel.rb- Edit
cisco_tunnel.rband substitute the placeholder text as shown here:
/X__RESOURCE_NAME__X/tunnel/This is the completed tunnel resource type based on template-type-feature.rb:
#
# Puppet resource type for feature tunnel
#
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Puppet::Type.newtype(:cisco_tunnel) do
@doc = "Manages configuration of feature tunnel
~~~
cisco_tunnel {'<title>':
..attributes..
}
~~~
Example:
~~~
cisco_tunnel {'xxxxx' :
ensure => present,
}
~~~
"
ensurable
apply_to_all
newparam(:name, namevar: true) do
desc 'Resource title. Valid values are string.'
end
# There are no additional properties for this command.
end- The provider files for Cisco OS are named cisco.rb and are each stored in a unique provider directory. Create a new directory for the tunnel provider and use
template-provider-feature.rbto populate the new provider file:
mkdir lib/puppet/provider/cisco_tunnel
cp docs/template-provider-feature.rb lib/puppet/provider/cisco_tunnel/cisco.rb- Edit
cisco.rband substitute the placeholder text as shown here:
/X__RESOURCE_NAME__X/tunnel/
/X__CLASS_NAME__X/Tunnel/This is the completed tunnel provider based on template-provider-feature.rb:
:Type.type(:cisco_tunnel).provide(:cisco) do
confine feature: :cisco_node_utils
mk_resource_methods
def initialize(value={})
super(value)
@property_flush = {}
end
def self.instances
inst = []
return inst unless Cisco::Tunnel.feature_enabled
current_state = { name: 'default', ensure: :present}
inst << new(current_state)
inst
end
def self.prefetch(resources)
provider = instances
resources.values.first.provider = provider.first unless provider.first.nil?
end
def exists?
@property_hash[:ensure] == :present
end
def create
@property_flush[:ensure] = :present
end
def destroy
@property_flush[:ensure] = :absent
end
def flush
case @property_flush[:ensure]
when :present
Cisco::Tunnel.new.feature_enable
when :absent
Cisco::Tunnel.new.feature_disable
end
end
endTest the new resource using the guestshell environment. See README-agent-install.md for using Puppet agent in guestshell.
NOTE: Before you can test your puppet provider code, you need to install the cisco_node_utils gem that contains the supporting APIs for your provider.
- Copy your completed
lib/puppet/type/cisco_tunnel.rbtype file to the/etc/puppetlabs/code/environments/production/modules/ciscopuppet/lib/puppet/typedirectory on your puppet master. - Copy your completed
lib/puppet/provider/cisco_tunnel/cisco.rbprovider file to the/etc/puppetlabs/code/environments/production/modules/ciscopuppet/lib/puppet/cisco_tunneldirectory on your puppet master. - Copy all of the manifest files under the
examplesdirectory to the/etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifestsdirectory on your puppet master. - On your puppet master, create a manifest for the new resource under
/etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifestsin a file calleddemo_tunnel.pp - Add the following content to the file:
class ciscopuppet::demo_tunnel {
cisco_tunnel { 'tunnel_on' :
ensure => present,
}
}- On your puppet master, modify
/etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifests/demo_all.ppto include the following:
include ciscopuppet::demo_tunnelNOTE: To isolate testing to your provider, comment out all of the other include statements in the demo_all.pp file.
- Run puppet-lint against the modified manifest files and correct any errors.
puppetmaster#cd /etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifests
puppetmaster#puppet-lint demo_tunnel.pp
puppetmaster#puppet-lint demo_all.pp- Manually check that the state of the resource is disabled on the switch. In this case the NX-OS CLI config is not present when feature tunnel is disabled.
n3k# sh run | i 'feature tunnel'
n3k#
- Run the Puppet agent:
Note. The --trace option is helpful when troubleshooting agent failures
[root@guestshell guestshell]# puppet agent -t --trace
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for n3k.cisco.com
Info: Applying configuration version '1438270388'
Notice: /Stage[main]/Main/Node[n3k]/Cisco_tunnel[tunnel_on]/ensure: created
Notice: Applied catalog in 0.26 seconds
- Check state on the switch again:
n3k# sh run | i 'feature tunnel'
feature tunnel
- We now have the expected state. Next, test the Puppet resource command while the feature is still enabled:
[root@guestshell guestshell]# puppet resource cisco_tunnel
cisco_tunnel { 'default':
ensure => 'present',
}
Note. This test manifest should be added to examples/demo_install.rb
- Change the manifest to ensure => absent to disable the state, then repeat the tests:
cisco_tunnel { 'tunnel_off' :
ensure => absent,
}n3k# sh run | i 'feature tunnel'
feature tunnel
[root@guestshell guestshell]# puppet agent -t
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for n3k.cisco.com
Info: Applying configuration version '1438270530'
Notice: /Stage[main]/Main/Node[n3k]/Cisco_tunnel[tunnel_off]/ensure: removed
Notice: Applied catalog in 0.35 seconds
n3k# sh run | i 'feature tunnel'
n3k#
[root@guestshell guestshell]# puppet resource cisco_tunnel
(a blank response is correct here)
puppet resourcecan also be used for testing changes to provider states. This method is often easier and doesn't require a manifest:
puppet resource cisco_tunnel 'test_on' ensure=present
puppet resource cisco_tunnel 'test_off' ensure=absent- rubocop is a Ruby static analysis tool. Run rubocop to validate the new code:
% rubocop type/cisco_tunnel.rb provider/cisco_tunnel/cisco.rb
Inspecting 2 files
..
2 files inspected, no offenses detectedBefore you start working on the eigrp feature, checkout the feature branch you created earlier.
git checkout feature/eigrp
git branch
develop
feature/tunnel
* feature/eigrpThis resource type and provider exercise will build on the router_eigrp API example shown in the cisco node_utils README-develop-node-utils-APIs document. The router_eigrp node_utils example created a new API for the cli below:
[no] feature eigrp
[no] router eigrp [name] (string)
maximum-paths [n] (integer)
[no] shutdown (boolean)
This example needs to support:
- multiple router eigrp instances, identified by 'name'
- an integer property
- a boolean property
Router eigrp also supports vrf and address-family sub-modes, which further complicate the configuration but are not included in this exercise.
The Puppet type and provider code doesn't need any knowledge of feature eigrp because that configuration is controlled automatically by the router_eigrp node_utils API; therefore, we need to implement only the router commands themselves.
- Copy the
template-type-router.rbfile to use as the basis for thecisco_router_eigrp.rbtype file:
cp docs/template-type-router.rb lib/puppet/type/cisco_router_eigrp.rb- Edit
cisco_router_eigrp.rband substitute the placeholder text as shown here:
/X__CLASS_NAME__X/RouterEigrp/
/X__RESOURCE_NAME__X/router_eigrp/
/X__PROPERTY_INT__X/maximum_paths/
/X__PROPERTY_BOOL__X/shutdown/There might be additional steps to follow in the template.
This is the completed router_eigrp type based on template-type-router.rb:
#
# Puppet resource type for router_eigrp
#
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Puppet::Type.newtype(:cisco_router_eigrp) do
@doc = "Manages configuration of a router_eigrp instance
~~~
cisco_router_eigrp {'<string>':
..attributes..
}
~~~
`<string>` is the name of the router_eigrp instance.
Example:
~~~
cisco_router_eigrp { 'green' :
ensure => present,
maximum_paths => 5,
shutdown => true,
}
~~~
"
ensurable
###################
# Resource Naming #
###################
# Parse the title to populate the attributes in these patterns.
# These attributes might be overwritten later.
def self.title_patterns
identity = lambda { |x| x }
patterns = []
# Below pattern matches the resource name.
patterns << [
/^(\S+)$/,
[
[:name, identity]
]
]
return patterns
end
newparam(:name, namevar: true) do
desc 'Name of the router_eigrp instance. Valid values are string.'
end
newproperty(:maximum_paths) do
desc "Sets the number of equal cost paths that EIGRP accepts in the route table.
Valid values are integer, keyword 'default'."
munge { |value|
value = :default if value == 'default'
begin
value = Integer(value) unless value == :default
rescue
fail "maximum_paths must be a valid integer, or default."
end
value
}
end
newproperty(:shutdown) do
desc 'shutdown state of the interface.'
newvalues(:true, :false, :default)
end
end- Create a new directory for the router_eigrp provider and use
template-provider-router.rbto populate the new provider file:
mkdir lib/puppet/provider/cisco_router_eigrp
cp docs/template-provider-router.rb lib/puppet/provider/cisco_router_eigrp/cisco.rb- Edit
cisco.rband substitute the placeholder text as shown here:
/X__CLASS_NAME__X/RouterEigrp/
/X__RESOURCE_NAME__X/router_eigrp/
/X__CONSTANT_NAME__X/ROUTER_EIGRP/
/X__PROPERTY_INT__X/maximum_paths/
/X__PROPERTY_BOOL__X/shutdown/There might be additional steps to follow in the template.
#
# The Cisco provider for cisco_router_eigrp.
#
# Copyright (c) 2015 Cisco and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'cisco_node_utils' if Puppet.features.cisco_node_utils?
begin
require 'puppet_x/cisco/autogen'
rescue LoadError # seen on master, not on agent
# See longstanding Puppet issues #4248, #7316, #14073, #14149, etc. Ugh.
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..',
'puppet_x', 'cisco', 'autogen.rb'))
end
Puppet::Type.type(:cisco_router_eigrp).provide(:cisco) do
desc 'The Cisco provider for cisco_router_eigrp.'
confine feature: :cisco_node_utils
mk_resource_methods
# Property symbol arrays for method auto-generation. There are separate arrays
# because the boolean-based methods are processed slightly different.
ROUTER_EIGRP_NON_BOOL_PROPS = [
:maximum_paths,
]
ROUTER_EIGRP_BOOL_PROPS = [
:shutdown,
]
ROUTER_EIGRP_ALL_PROPS =
ROUTER_EIGRP_NON_BOOL_PROPS + ROUTER_EIGRP_BOOL_PROPS
# Dynamic method generation for getters & setters
PuppetX::Cisco::AutoGen.mk_puppet_methods(:non_bool, self, "@router_eigrp",
ROUTER_EIGRP_NON_BOOL_PROPS)
PuppetX::Cisco::AutoGen.mk_puppet_methods(:bool, self, "@router_eigrp",
ROUTER_EIGRP_BOOL_PROPS)
def initialize(value={})
super(value)
@router_eigrp = Cisco::RouterEigrp.routers[@property_hash[:name]]
@property_flush = {}
end
def self.properties_get(instance_name, inst)
debug 'Checking instance, #{instance_name}.'
current_state = {
name: instance_name,
ensure: :present,
}
# Call node_utils getter for each property
ROUTER_EIGRP_NON_BOOL_PROPS.each do |prop|
current_state[prop] = inst.send(prop)
end
ROUTER_EIGRP_BOOL_PROPS.each do |prop|
val = inst.send(prop)
if val.nil?
current_state[prop] = nil
else
current_state[prop] = val ? :true : :false
end
end
new(current_state)
end # self.properties_get
def self.instances
instance_array = []
Cisco::RouterEigrp.routers.each do | instance_name, inst |
begin
instance_array << properties_get(instance_name, inst)
end
end
instance_array
end # self.instances
def self.prefetch(resources)
instance_array = instances
resources.keys.each do |name|
provider = instance_array.find { |inst| inst.name == name }
resources[name].provider = provider unless provider.nil?
end
end # self.prefetch
def exists?
@property_hash[:ensure] == :present
end
def create
@property_flush[:ensure] = :present
end
def destroy
@property_flush[:ensure] = :absent
end
def properties_set(new_instance=false)
ROUTER_EIGRP_ALL_PROPS.each do |prop|
if @resource[prop]
if new_instance
# Call puppet setter to set @property_flush[prop]
self.send("#{prop}=", @resource[prop])
end
unless @property_flush[prop].nil?
# Call node_utils setter to update node
@router_eigrp.send("#{prop}=", @property_flush[prop]) if
@router_eigrp.respond_to?("#{prop}=")
end
end
end
end
def flush
if @property_flush[:ensure] == :absent
@router_eigrp.destroy
@router_eigrp = nil
else
# Create/Update
if @router_eigrp.nil?
new_instance = true
@router_eigrp = Cisco::RouterEigrp.new(@resource[:name])
end
properties_set(new_instance)
end
end
endNOTE: Before you can test your puppet provider code, you need to install the cisco_node_utils gem that contains the supporting APIs for your provider.
- Copy your completed
lib/puppet/type/cisco_router_eigrp.rbtype file to the/etc/puppetlabs/code/environments/production/modules/ciscopuppet/lib/puppet/typedirectory on your puppet master. - Copy your completed
lib/puppet/provider/cisco_router_eigrp/cisco.rbprovider file to the/etc/puppetlabs/code/environments/production/modules/ciscopuppet/lib/puppet/cisco_router_eigrpdirectory on your puppet master. - Copy all of the manifest files under the
examplesdirectory to the/etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifestsdirectory on your puppet master unless you did this ealier while developing the tunnel provider. - On your puppet master, create a manifest for the new resource under
/etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifestsin a file calleddemo_eigrp.pp - Add the following content to the file:
class ciscopuppet::demo_eigrp {
cisco_router_eigrp { 'test' :
ensure => present,
maximum_paths => 5,
shutdown => true,
}
}- On your puppet master, modify
/etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifests/demo_all.ppto include the following:
include ciscopuppet::demo_eigrpNOTE: To isolate testing to your provider, comment out all of the other include statements in the demo_all.pp file.
- Run puppet-lint against the modified manifest files and correct any errors.
puppetmaster#cd /etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifests
puppetmaster#puppet-lint demo_eigrp.pp
puppetmaster#puppet-lint demo_all.pp- Manually check that the state of the resource is disabled on the switch.
n3k# sh run eigrp
^
% Invalid command at '^' marker.
(feature eigrp is disabled so this error is expected)
- Run the Puppet agent:
Note. The --trace option is helpful when troubleshooting agent failures
[root@guestshell guestshell]# puppet agent -t
[root@guestshell guestshell]# puppet agent -t
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for n3k.cisco.com
Info: Applying configuration version '1438344401'
Notice: /Stage[main]/Main/Node[n3k]/Cisco_router_eigrp[test]/ensure: created
Notice: Applied catalog in 4.65 seconds
- Check state on the switch again:
n3k# sh run eigrp
feature eigrp
router eigrp test
maximum-paths 5
shutdown
- Run Puppet agent again to test for idempotency. You should NOT see
Cisco_router_eigrp[test]/ensure: createdin the log, indicating that the state has not changed:
[root@guestshell guestshell]# puppet agent -t
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for n3k.cisco.com
Info: Applying configuration version '1438344623'
Notice: Applied catalog in 0.16 seconds
- Test the
puppet resourcecommand while the feature is enabled:
[root@guestshell guestshell]# puppet resource cisco_router_eigrp
cisco_router_eigrp { 'test':
ensure => 'present',
maximum_paths => '5',
shutdown => 'true',
}
Note. This test manifest should be added to examples/demo_install.rb
- Alternative tests with
puppet resource:
puppet resource cisco_router_eigrp "xyz" ensure=present shutdown=true maximum_paths=3
puppet resource cisco_router_eigrp "xyz" shutdown='default'
puppet resource cisco_router_eigrp "xyz" ensure=absent- Run rubocop to validate the new code:
% rubocop type/cisco_router_eigrp.rb provider/cisco_router_eigrp/cisco.rb
Inspecting 2 files
..
2 files inspected, no offenses detectedNow that you have completed your providers and created sample manifests to test them, go ahead and copy the new and modified manifest files from the /etc/puppetlabs/code/environments/production/modules/ciscopuppet/manifests directory on your puppet master to the examples directory under your cisco-network-puppet-module git repository.
Make sure you can run the basic demo with your new providers included.
Please see the CONTRIBUTING document for workflow instructions.
