strict warning: Only variables should be passed by reference in /var/www/sites/www.netomata.com/sites/all/themes/clean/template.php on line 126.

ncg file -- Netomata Config Generator (ncg) template files

Description

Netomata Config Generator (NCG) template files (which are commonly named with the extension .ncg) are used by ncg as templates for generating config files (or subsections of those files) for various network devices and services, using the Embedded Ruby (ERB) mechanism (which is sometimes also referred to as "eRuby").

Format

In a nutshell, ncg files are text files with embedded Ruby (ERB) statements to control the final output when the file is interpreted. ERB is much like PHP and other embedded templating languages.

To use ERB, you embed Ruby language syntax into your text file, and mark the Ruby language segments like this:

<% ... Ruby code ... %>
so that the ERB interpreter can recognize them and apply them.

The ERB interpreter as used for ncg files recognizes the following variations of these tags:

<% Ruby code %>

The Ruby code is executed, but nothing is output.

<%= Ruby code %>

The Ruby code is executed, and its resulting value is output in place of the whole <%= Ruby code %> block.

<%# Comment %>

The block is treated as a comment and ignored. (This can be very useful in testing, to temporarily disable a certain block of Ruby code.)

<%% or %%>

Replaced in the output with '<%' or '%>', respectively.

Examples

Here's how you might use a template to generate an /etc/network/interfaces file for an Ubuntu or Debian Linux server which has 4 Ethernet interfaces on 4 different networks. Interfaces are "eth0" through "eth3", and for each interface "ethN":
  • The host's address is 192.168.N.17
  • The netmask is 255.255.255.0
  • The broadcast address is 192.168.N.255
  • The gateway address is only defined on eth0, and is 192.168.0.1
Here's the template:
<% 
my_hostid = 17
(0..3).each do |i|
-%>
auto eth<%= i %>
interface eth<%= i %> inet static
    address 192.168.<%= i %>.<%= my_hostid %>
    netmask 255.255.255.0
<% if (i == 0) then -%>
    gateway 192.168.<%= i %>.1
<% end -%>
    broadcast 192.168.<%= i %>.255
<%# blank line %>
<% end -%>
When this template is processed by ERB, the result is:
auto eth0
interface eth0 inet static
    address 192.168.0.17
    netmask 255.255.255.0
    gateway 192.168.0.1
    broadcast 192.168.0.255

auto eth1
interface eth1 inet static
    address 192.168.1.17
    netmask 255.255.255.0
    broadcast 192.168.1.255

auto eth2
interface eth2 inet static
    address 192.168.2.17
    netmask 255.255.255.0
    broadcast 192.168.2.255

auto eth3
interface eth3 inet static
    address 192.168.3.17
    netmask 255.255.255.0
    broadcast 192.168.3.255

Usage Notes

Passing Data to Templates

Data is passed to templates through Ruby instance variables; in Ruby, instance variable names begin with a single "@" symbol).

The instance variables may be of any class, but are usually of either class "Netomata::Node" or the fundamental Ruby "String" class.

Currently, templates must agree with whatever invokes them on what variables are passed, and what the class of each of those variables is. There is no automatic checking of variable presence or type. If a template requires a variable by a particular name and/or of a particular class, then the template needs to explicitly check that it has been passed what it expects, and raise an exception if it discovers a problem.

For example, here is a template to configure an interface on a Cisco router/switch. It expects that it has been passed a variable named @interface, which is a Netomata::Node and has subkeys of "name", "target", and "active" which are strings.

<%
    template_arg("@interface")
    template_arg_node_key("@interface", "name")
    template_arg_node_key("@interface", "target")
    template_arg_node_key("@interface", "active")
-%>
interface <%= @interface["name"] %>
 description IPMI <%= @interface["target"] %>
 switchport access vlan 48
 switchport mode access
<%= apply_idiom("shutdown", @interface, {"@active" => @interface["active"]}) %>
!

In the above example, you can also see how to apply other templates ("idioms" are templates that the ncg program expects to find somewhere in (...)!idioms relative to the current node; see the documentation for apply_idiom() elsewhere in this document) from within a template, and how to pass arguments to these other templates.

Helper Methods

In addition to the standard Ruby library classes and methods, there are a handful of "helper" methods available for use within ncg files.

apply_idiom(idiom, node [ , vars])

Looks for a node named node[(...)!idioms!idiom] (i.e., checks node and each of its ancestor nodes in turn for a sub-node named "idioms" which itself has a sub-sub-node named "idiom"), and applies it with optional vars passed as variables.

Variables to pass are specified as a Ruby hash, with the following syntax:

{ "@name" => @var [, ...] }
So, for example, each of the following would be a valid vars argument to apply_idiom() (assuming the referenced variables exist):
{"@vlan" => @vlan}
{"@active" => @vlan["active"]}
{"@target" => @target, "@vlan" => @vlan}

buildkey(part1 [ , part2 [ ...]])

Returns a key built by combining all the partN arguments, using "!" as a separator. For example:

buildkey("a", "b", "c")
returns
"a!b!c"

compare_parts(a, b)

Compares two interface- or version-style names, and determines what order they should sort in. This is similar to the Ruby core String#<=> operator, but it works in a way that is more useful for comparing multi-part strings such as interface and subinterface names, version numbers, and so forth.

Returns:

  • -1 if a should sort before b
  • 0 if a and b are equivalent
  • 1 if b should sort before a

To compare the arguments a and b, they are first broken into parts. Each part, in turn, is the longest-possible string of either:

  • letters [a-zA-Z]
  • digits [0-9]
  • anything else (i.e., a sequence of 1 or more non-alphanumeric characters)

Then, the corresponding parts of each argument are compared in turn, until an answer is determined, subject to the following rules:

  • If both parts are integers, they are compared numerically (so that "2" is less than "10", even though alphabetically "10" would sort before "2")
  • Otherwise, both parts are compared as text strings (case sensitive).

If the corresponding parts of both arguments are equivalent by these rules, then the next pair of corresponding parts is considered, and so forth, until an answer is determined.

For example, consider a list of interfaces; sorted normally (not using compare_parts, their order would be:

  • FastEthernet1
  • FastEthernet10
  • FastEthernet10.10
  • FastEthernet10.2
  • FastEthernet2

compare_parts would break each of these down into the following parts (shown by spaces) for comparison:

  • FastEthernet 1
  • FastEthernet 10
  • FastEthernet 10 . 10
  • FastEthernet 10 . 2
  • FastEthernet 2

So, sorted using compare_parts for the comparison, the list would be:

  • FastEthernet1
  • FastEthernet2
  • FastEthernet10
  • FastEthernet10.2
  • FastEthernet10.10

This is a more "natural" order for a list of interfaces and subinterfaces.

emit_header([prefix [ , before [ , after ]]])

Returns a string that is a multi-line header suitable for placing as a comment at the beginning of a generated config file, describing what tool was used to generate the config, what target key it was generated for, what date it was generated on, what user, hostname, and directory name it was generated for, and what arguments the generating program was invoked with.

prefix, if defined, is prepended to every line of the generated header. It should be whatever marks the rest of the line as a comment in the generated config file.

before, if defined, is used as the very first line of the generated header. It should be whatever marks the beginning of a multi-line comment in the generated config file. prefix is not applied to before, so before should include prefix if necessary.

after, if defined, is used as the very last line of the generated header. It should be whatever marks the end of a multi-line comment in the generated config file. prefix is not applied to after, so after should include prefix if necessary.

For example, to generate headers suitable for a shell script, you would do

emit_header("# ", nil, nil)
which would return a header of the form
# line 1
# line 2
# ...

To generate headers for a C-style config, you would do

emit_header(" *  ", "/*", " */")
which would return a header of the form
/*
 * line 1
 * line 2
 * ...
 */

filename_to_key(filename)

Converts filename to a key, by

  1. converting ".." elements to "(..)"
  2. converting "." elements to "(.)"
  3. replacing "/" with "!"
For example:
filename_to_key("../a/b/c/./d")
returns
"(..)!a!b!c!(.)!d"

ip_union(ip_a, ip_b)

ip_a, ip_b, and the result are all IP addresses represented as strings. The result is the bitwise OR of the two arguments ip_a and ip_b. For example:

ip_union("10.5.0.0", "0.0.16.34") => "10.5.16.34"
ip_union("10.5.1.0", "0.0.16.34") => "10.5.17.34"

relative_filename(basename, filename)

Expands file name filename relative to another file or directory name basename, returning the expanded filename. For example:

relative_filename("/a/b/c", "d") => "/a/b/d"
relative_filename("/a/b/c", "d/e") => "/a/b/d/e"

Note: as a special case, if filename begins with "/", it is treated as an absolute filename and returned as-is, not relative to basename. For example:

relative_filename("/a/b/c", "/d/e") => "/d/e"

template_arg(name [ , expected_class ])

This method can be used at the beginning of a template to check whether a needed argument has been passed to the template as an instance variable (see the section elsewhere in this document on Passing Data to Templates). Raises a Ruby ArgumentError (which typically aborts the program) if name (which is passed to this method as a quoted string, i.e. "@interface") doesn't exist. Also raises a Ruby ArgumentError if the variable exists, but isn't of the expected Ruby class (Netomata::Node, by default).

For example, the following would check that a node named "@interfaces" had been passed into a template, and raise an error if it hadn't:

<%
    template_arg("@interfaces")
-%>

template_arg_node_key(name, key [ , expected_class ])

This method can be used at the beginning of a template to check whether a needed argument key is defined within a node that has been passed to the template as an instance variable (see the section elsewhere in this document on Passing Data to Templates). Raises a Ruby ArgumentError (which typically aborts the program) if any of the following tests fail:

  • name (which is passed to this method as a quoted string, i.e. "@interface") exists
  • name is a node (Ruby class Netomata::Node)
  • name has a key named key
  • name[key] is of the expected Ruby class (String, by default)

For example, the following would check that a node named "@vlan" had been passed into a template, and that it had a key named "id" of Ruby class String, raising an error if it hadn't:

<%
    template_arg_node_key("@vlan", "id")
-%>

Netomata::IPAddr methods

Ruby class Netomata::IPAddr is an extension of the standard Ruby IPAddr class. In addition to all the methods defined in the Ruby IPAddr class, the Netomata::IPAddr class adds a set of methods for obtaining IP netmasks in various formats.

ipaddr.netmask

Returns ipaddr's netmask as a string in canonical format (dotted-quad for IPv4, and ':'-separated hex for IPv6).

For example:

  • Netomata::IPAddr.new("198.102.244.34/24").netmask -> "255.255.255.0"
  • Netomata::IPAddr.new("dead:beef:0123:4567::cafe:babe/80").netmask -> "ffff:ffff:ffff:ffff:ffff:0000:0000:0000"

ipaddr.netmask_as_bits

Returns the number of significant bits in the netmask of ipaddr.

For example:

  • Netomata::IPAddr.new("172.16.32.16/252").netmask_as_bits -> 30
  • Netomata::IPAddr.new("198.102.244.34/24").netmask_as_bits -> 24

Netomata::IPAddr.netmask_as_bits(netmask)

Returns the number of significant bits in netmask, which is expressed in any of the following forms:

  • string in IPv4 dotted-quad notation (i.e., "255.255.252.0")
  • string in IPv6 colon-separated-hex notation (i.e., "ffff:ffff::")
  • string in IPv4 or IPv6 CIDR notation (i.e., "/24" or "/40")
  • string as number of bits (i.e., "30")
  • integer as number of bits (i.e., 27)

For example:

  • Netomata::IPAddr.netmask_as_bits("255.255.255.252") -> 30
  • Netomata::IPAddr.netmask_as_bits("ffff:ffff::") -> 32
  • Netomata::IPAddr.netmask_as_bits("/24") -> 24
  • Netomata::IPAddr.netmask_as_bits("18") -> 18
  • Netomata::IPAddr.netmask_as_bits(12) -> 12

ipaddr.netmask_as_cidr

Returns the netmask of ipaddr as a string in CIDR notation.

For example:

  • Netomata::IPAddr.new("10.5.16.0/255.255.255.252").netmask_as_cidr -> "/30"
  • Netomata::IPAddr.new("198.102.244.34/24").netmask_as_cidr -> "/24"
  • Netomata::IPAddr.new("cafe:babe:1234::/ffff:ffff::").netmask_as_cidr -> "/32"

Netomata::IPAddr.netmask_as_cidr(netmask)

Returns a netmask as a string in CIDR notation, from input netmask, which is expressed in any of the following forms:

  • string in IPv4 dotted-quad notation (i.e., "255.255.252.0")
  • string in IPv6 colon-separated-hex notation (i.e., "ffff:ffff::")
  • string in IPv4 or IPv6 CIDR notation (i.e., "/24" or "/40")
  • string as number of bits (i.e., "30")
  • integer as number of bits (i.e., 27)

For example:

  • Netomata::IPAddr.netmask_as_cidr("255.255.255.252") -> "/30"
  • Netomata::IPAddr.netmask_as_cidr("ffff:ffff:ffff::") -> "/48"
  • Netomata::IPAddr.netmask_as_cidr("/24") -> "/24"
  • Netomata::IPAddr.netmask_as_cidr("18") -> "/18"
  • Netomata::IPAddr.netmask_as_cidr(12) -> "/12"

ipaddr.netmask_as_ipv4_quads

Returns the netmask of ipaddr as a string in "dotted quad" (IPv4) notation.

For example:

  • Netomata::IPAddr.new("10.5.16.0/255.255.255.252").netmask_as_ipv4_quads -> "255.255.255.252"
  • Netomata::IPAddr.new("198.102.244.34/24").netmask_as_ipv4_quads -> "255.255.255.0"
  • Netomata::IPAddr.new("cafe:babe:1234::/ffff:ffff::").netmask_as_ipv4_quads -> nil

Netomata::IPAddr.netmask_as_ipv4_quads(netmask)

Returns a netmask as a string in CIDR notation, from input netmask, which is expressed in any of the following forms:

  • string in IPv4 dotted-quad notation (i.e., "255.255.252.0")
  • string in IPv4 or IPv6 CIDR notation (i.e., "/24" or "/40")
  • string as number of bits (i.e., "30")
  • integer as number of bits (i.e., 27)

netmask must be an IPv4 netmask (i.e., 0 to 32 bits). If netmask is not a valid IPv4 netmask, then an error is raised.

For example:

  • Netomata::IPAddr.netmask_as_ipv4_quads(30) -> "255.255.255.252"
  • Netomata::IPAddr.netmask_as_ipv4_quads("/28") -> "255.255.255.240"

End-of-line Handling

Normally, if '<% Ruby code %>' or '<%= Ruby code %>' appears as a line in a template, then after the Ruby code is processed, there will still be a "newline" character in the template (the newline that followed the '<% Ruby code %>' block), and ERB will copy this into the output. This can lead to unexpected results. For example, consider the following template:
foo
<% 3.times do %>
x
<% end %>
baz
This will result in the following output, with what many would consider to be extraneous newlines:
foo

x

x

x

baz
To eliminate the extraneous newlines, simply change the closing tag for the embedded Ruby blocks from '%>' to '-%>'. That will cause the ERB interpreter to suppress the newline that would otherwise immediately follow the closing tag:
foo
<% 3.times do -%>
x
<% end -%>
baz
Resulting in the following output, without the extraneous newlines:
foo
x
x
x
baz

Alternate ERB Syntax Not Supported

Some ERB documentation speaks of a syntax whereby lines begining with '%' are also treated as Ruby code (i.e., a line '% Ruby code' is treated the same as a line '<% Ruby code %>'). This syntax is not supported in ncg files.

Embedded Ruby (ERB) References

Documentation and examples for ERB can be found at

History

ncg was originally written in 2008 by Brent Chapman of Netomata, Inc.

Bugs

Please report any you find by email to bugs@netomata.com.

Author

Brent Chapman of Netomata, Inc.

Copyright

Copyright (C) 2009, 2010 Netomata, Inc. All Rights Reserved.

See Also

ncg program, neto file format, neto_table file format.

Version

This documentation is for ncg version 0.10.x.