Chapter 27. Plug-and-play scripting

Table of Contents

27.1. Introduction
27.2. Script storage
27.3. Script interface
27.4. Loading of scripts
27.5. Command scripts
27.6. Policy scripts
27.7. Post-commit scripts

27.1. Introduction

This chapter defines a scripting mechanism to be used together with the CLI (scripting is not available for any other northbound interfaces). The chapter is intended for users that are familiar with UNIX shell scripting and/or programming. With the scripting mechanism it is possible for an end-user to add new functionality to ConfD in a plug-and-play like manner. No special tools are needed. There are three categories of scripts:

command scripts

used to add new commands to the CLI.

policy scripts

invoked at validation time and may control the outcome of a transaction. Policy scripts have the mandate to cause a transaction to abort.

post-commit scripts

invoked when a transaction has been committed. Post-commit scripts can for example be used for logging, sending external events etc.

The terms "script" and "scripting" used throughout this description refer to how functionality can be added without a requirement for integration using the ConfD programming APIs. ConfD will only run the scripts as UNIX executables. Thus they may be written as shell scripts, or using some other scripting language that is supported by the OS, or even be compiled code. The scripts are run with the same user id as ConfD.

27.2. Script storage

Scripts are stored in a directory tree with a predefined structure where there is a sub-directory for each script category:

scripts/
        command/
        policy/
        post-commit/
        

For all script categories it suffices to just add a valid script in the correct sub-directory in order to enable the script. See the details for each script category for how a valid script of that category is defined. Scripts with a name beginning with a dot character ('.') are ignored.

The directory path to the location of the scripts is configured with the /confdConf/scripts/dir configuration parameter. It is possible to have several scripts directories.

27.3. Script interface

All scripts are required to provide a formal description of their interface. When the scripts are loaded, ConfD will invoke the scripts with (one of)

--command

--policy

--post-commit

as argument depending of the script category.

The script must respond by writing its formal interface description on stdout and exit normally. Such a description consists of one or more sections. Which sections that are required depends on the category of the script.

The sections do however have a common syntax. Each section begins with the keyword "begin" followed by the type of section. After that one or more lines of settings follows. Each such setting begins with a name, followed by a colon character (':') and after that the value is stated. The section ends with the keyword "end". Empty lines and spaces may be used to improve the readability.

For examples see each corresponding section below.

27.4. Loading of scripts

Scripts are automatically loaded at startup and may also be manually reloaded with the CLI command script reload. The command takes an optional verbosity parameter which may have one of the following values:

diff

Shows info about those scripts that have been changed since the latest (re)load. This is the default.

all

Shows info about all scripts regardless of whether they have been changed or not.

errors

Shows info about those scripts that are erroneous, regardless of whether they have been changed or not. Typical errors are invalid file permissions and syntax errors in the interface description.

Yet another parameter may be useful when debugging reload of scripts:

debug

Shows additional debug info about the scripts.

An example session reloading scripts:

admin@ncs# script reload all
$NCS_DIR/examples.ncs/getting-started/using-ncs/7-scripting/scripts:
ok
command:
    add_user.sh: unchanged
    echo.sh: unchanged
policy:
    check_dir.sh: unchanged
post-commit:
    show_diff.sh: unchanged
/opt/ncs/scripts: ok
command:
    device_brief.sh: unchanged
    device_brief_c.sh: unchanged
    device_list.sh: unchanged
    device_list_c.sh: unchanged
    device_save.sh: unchanged
       

27.5. Command scripts

Command scripts are used to add new commands to the CLI. The scripts are executed in the context of a transaction. When the script is run in oper mode, this is a read-only transaction, when it is run in config mode, it is a read-write transaction. In that context the script may make use of the environment variables CONFD_MAAPI_USID and CONFD_MAAPI_THANDLE in order to attach to the active transaction. This makes it simple to make use of the maapi command (see the ???? manual page) for various purposes.

Each command script must be able to handle the argument --command and, when invoked, write a command section to stdout. If the CLI command is intended to take parameters, one param section per CLI parameter must also be emitted.

The command is not paginated by default in the CLI and will only do so if it is piped to more.

          joe@io> example_command_script | more
        

27.5.1. Command section

The following settings can be used to define a command:

modes

Defines in which CLI mode(s) that the command should be available. The value can be oper, config or both (separated with space).

styles

Defines in which CLI styles that the command should be available. The value can be one or more of c, i and j (separated with space). c means Cisco style, i, means Cisco IOS and j for J-style.

cmdpath

Is the full CLI command path. For example the command path my script echo implies that the command will be called my script echo in the CLI.

help

Command help text.

An example of a command section is:

begin command
  modes: oper
  styles: c i j
  cmdpath: my script echo
  help: Display a line of text
end
         

27.5.2. Param section

In this section various aspects of a parameter is specified. This may both affect the parameter syntax for the end-user in the CLI as well as what the command script will get as arguments. The following settings can be used to customize each CLI parameter:

name

Optional name of the parameter. If provided, the CLI will prompt for this name before the value. By default the name is not forwarded to the script. See flag and prefix.

type

The type of the parameter. By default each parameter has a value, but by setting the type to void the CLI will not prompt for a value. In order to be useful the void type must be combined with name and either flag or prefix.

presence

Controls whether the parameter must be present in the CLI input or not. Can be set to optional or mandatory.

words

Controls the number of words that the parameter value may consist of. By default the value must consist of just one word (possibly quoted if it contains spaces). If set to any, the parameter may consist of any number of words. This setting is only valid for the last parameter.

flag

Extra word added before the parameter value. For example if set to -f and the user enters logfile, the script will get -f logfile as arguments.

prefix

Extra string prepended to the parameter value (as a single word). For example if set to --file= and the user enters logfile, the script will get --file=logfile as argument.

help

Parameter help text.

If the command takes a parameter to redirect the output to a file, a param section might look like this:

begin param
 name: file
 presence: optional
 flag: -f
 help: Redirect output to file
end
        

27.5.3. Full command example

A command denying changes the configured trace-dir for a set of devices it can use the check_dir.sh script.

#!/bin/bash

set -e

while [ $# -gt 0 ]; do
    case "$1" in
        --command)
            # Configuration of the command
            #
            # modes   - CLI mode (oper config)
            # styles  - CLI style (c i j)
            # cmdpath - Full CLI command path
            # help    - Command help text
            #
            # Configuration of each parameter
            #
            # name     - (optional) name of the parameter
            # more     - (optional) true or false
            # presence - optional or mandatory
            # type     - void - A parameter without a value
            # words    - any - Multi word param. Only valid for the last param
            # flag     - Extra word added before the parameter value
            # prefix   - Extra string prepended to the parameter value
            # help     - Command help text
            cat << EOF

begin command
  modes: config
  styles: c i j
  cmdpath: user-wizard
  help: Add a new user
end
EOF
            exit
            ;;
        *)
            break
            ;;
    esac
    shift
done

## Ask for user name
while true; do
    echo -n "Enter user name: "
    read user

    if [ ! -n "${user}" ]; then
        echo "You failed to supply a user name."
    elif ncs-maapi --exists "/aaa:aaa/authentication/users/user{${user}}"; then
        echo "The user already exists."
    else
        break
    fi
done

## Ask for password
while true; do
    echo -n "Enter password: "
    read -s pass1
    echo

    if [ "${pass1:0:1}" == "$" ]; then
        echo -n "The password must not start with $. Please choose a "
        echo    "different password."
    else
        echo -n "Confirm password: "
        read -s pass2
        echo

        if [ "${pass1}" != "${pass2}" ]; then
            echo "Passwords do not match."
        else
            break
        fi
    fi
done

groups=`ncs-maapi --keys "/nacm/groups/group"`
while true; do
    echo "Choose a group for the user."
    echo -n "Available groups are: "
    for i in ${groups}; do echo -n "${i} "; done
    echo
    echo -n "Enter group for user: "
    read group

    if [ ! -n "${group}" ]; then
        echo "You must enter a valid group."
    else
        for i in ${groups}; do
            if [ "${i}" == "${group}" ]; then
                # valid group found
                break 2;
            fi
        done
        echo "You entered an invalid group."
    fi
    echo
done

echo "Creating user"

ncs-maapi --create "/aaa:aaa/authentication/users/user{${user}}"
ncs-maapi --set "/aaa:aaa/authentication/users/user{${user}}/password" \
                "${pass1}"

echo "Setting home directory to: /homes/${user}"
ncs-maapi --set "/aaa:aaa/authentication/users/user{${user}}/homedir" \
            "/homes/${user}"

echo "Setting ssh key directory to: /homes/${user}/ssh_keydir"
ncs-maapi --set "/aaa:aaa/authentication/users/user{${user}}/ssh_keydir" \
            "/homes/${user}/ssh_keydir"

ncs-maapi --set "/aaa:aaa/authentication/users/user{${user}}/uid" "1000"
ncs-maapi --set "/aaa:aaa/authentication/users/user{${user}}/gid" "100"

echo "Adding user to the ${group} group."
gusers=`ncs-maapi --get "/nacm/groups/group{${group}}/user-name"`

for i in ${gusers}; do
    if [ "${i}" == "${user}" ]; then
        echo "User already in group"
        exit 0
    fi
done

ncs-maapi --set "/nacm/groups/group{${group}}/user-name" "${gusers} ${user}"


        

Calling examples.confd/scripting/scripts/command/echo.sh with the argument --command argument produces a command section and a couple of param sections:

$ ./echo.sh --command
begin command
  modes: oper
  styles: c i j
  cmdpath: my script echo
  help: Display a line of text
end

begin param
 name: nolf
 type: void
 presence: optional
 flag: -n
 help: Do not output the trailing newline
end

begin param
 name: file
 presence: optional
 flag: -f
 help: Redirect output to file
end

begin param
 presence: mandatory
 words: any
 help: String to be displayed
end

In the complete example examples.confd/scripting there is a README file and a simple command script scripts/command/echo.sh.

27.6. Policy scripts

Policy scripts are invoked at validation time, before a change is committed. They provide a simplified way of defining validation points with callbacks. A policy script can reject the data, accept it, or accept it with a warning. If a warning is produced, it will be displayed for interactive users (e.g. through the CLI or Web UI). The user may choose to abort or continue to commit the transaction.

Policy scripts are typically assigned to individual leafs or containers. In some cases it may be feasible to use a single policy script, e.g. on the top level node of the configuration. In such a case, this script is responsible for the validation of all values and their relationships throughout the configuration.

All policy scripts are invoked on every configuration change. The policy scripts can be configured to depend on certain subtrees of the configuration, which can save time but it is very important that all dependencies are stated and also updated when the validation logic of the policy script is updated. Otherwise an update may be accepted even though a dependency should have denied it.

There can be multiple dependency declarations for a policy script. Each declaration consists of a dependency element specifying a configuration subtree that the validation code is dependent upon. If any element in any of the subtrees is modified, the policy script is invoked. A subtree is specified as an absolute path.

If there are no declared dependencies, the root of the configuration tree (/) is used, which means that the validation code is executed when any configuration element is modified. If dependencies are declared on a leaf element, an implicit dependency on the leaf itself is added.

Each policy script must handle the argument --policy and, when invoked, write a policy section to stdout. The script must also perform the actual validation when invoked with the argument --keypath.

27.6.1. Policy section

The following settings can be used to configure a policy script:

keypath

Mandatory. Keypath is a path to a node in the configuration data tree. The policy script (and the automatically created validation point) will be associated with this node. The path must be absolute. A keypath can for example be /devices/device/c0. The script will be invoked if the configuration node, referred to by by the keypath, is changed or if any node in the subtree under the node (if the node is a container or list) is changed.

dependency

Declaration of a dependency. The dependency must be an absolute keypath. Multiple dependency settings can be declared. Default is /.

priority

An optional integer parameter specifying the order policy scripts and other validation callbacks will be evaluated, in order of increasing priority, where lower value is higher priority. The default priority is 0.

call

This optional setting can only be used if the associated node, declared as keypath, is a list. If set to once, the policy script is only called once even though there exists many list entries in the data store. This is useful if we have a huge amount of instances or if values assigned to each instance have to be validated in comparison with its siblings. Default is each.

A policy that will be run for every change on or under /devices/device.

begin policy
 keypath: /devices/device
 dependency: /devices/global-settings
 priority: 4
 call: each
end
        

27.6.2. Validation

When ConfD has come to the conclusion that the policy script should be invoked to perform its validation logic, the script is invoked with the option --keypath. If the registered node is a leaf, its value will be given with the --value option. For example --keypath /devices/device/c0 or if the node is a leaf --keypath /devices/device/c0/address --value 127.0.0.1.

Once the script has performed its validation logic it must exit with a proper status. The following exit statuses are valid:

0

Validation ok. Vote for commit.

1

When the outcome of the validation is dubious it is possible for the script to issue a warning message. The message is extracted from the script output on stdout. An interactive user has the possibility to choose to abort or continue to commit the transaction. Non-interactive users automatically vote for commit.

2

When the validation fails it is possible for the script to issue an error message. The message is extracted from the script output on stdout. The transaction will be aborted.

27.6.3. Full policy example

A policy denying changes the configured trace-dir for a set of devices it can use the check_dir.sh script.

#!/bin/sh

usage_and_exit() {
    cat << EOF
Usage: $0 -h
       $0 --policy
       $0 --keypath <keypath> [--value <value>]

  -h                    display this help and exit
  --policy              display policy configuration and exit
  --keypath <keypath>   path to node
  --value <value>       value of leaf

Return codes:

  0 - ok
  1 - warning message is printed on stdout
  2 - error message   is printed on stdout
EOF
    exit 1
}

while [ $# -gt 0 ]; do
    case "$1" in
        -h)
            usage_and_exit
            ;;
        --policy)
            cat << EOF
begin policy
  keypath: /devices/global-settings/trace-dir
  dependency: /devices/global-settings
  priority: 2
  call: each
end
EOF
            exit 0
            ;;
        --keypath)
            if [ $# -lt 2 ]; then
                echo "<ERROR> --keypath <keypath> - path omitted"
                usage_and_exit
            else
                keypath=$2
                shift
            fi
            ;;
        --value)
            if [ $# -lt 2 ]; then
                echo "<ERROR> --value <value> - leaf value omitted"
                usage_and_exit
            else
                value=$2
                shift
            fi
            ;;
        *)
            usage_and_exit
            ;;
    esac
    shift
done

if [ -z "${keypath}" ]; then
    echo "<ERROR> --keypath <keypath> is mandatory"
    usage_and_exit
fi

if [ -z "${value}" ]; then
    echo "<ERROR> --value <value> is mandatory"
    usage_and_exit
fi

orig="./logs"
dir=${value}
# dir=`ncs-maapi --get /devices/global-settings/trace-dir`
if [ "${dir}" != "${orig}" ] ; then
    echo "/devices/global-settings/trace-dir: must retain it original value (${orig})"
    exit 2
fi
       

Trying to change that parameter would result in an aborted transaction

admin@ncs(config)# devices global-settings trace-dir ./testing
admin@ncs(config)# commit
Aborted: /devices/global-settings/trace-dir: must retain it original
value (./logs)
       

In the complete example examples.confd/scripting there is a README file and a simple policy script scripts/policy/check_number_of_hosts.sh .

27.7. Post-commit scripts

Post-commit scripts are run when a transaction has been committed, but before any locks have been released. The transaction hangs until the script has returned. The script cannot change the outcome of the transaction. Post-commit scripts can for example be used for logging, sending external events etc. The scripts run as the same user id as ConfD.

The script is invoked with --post-commit at script (re)load. In future releases it is possible that the post-commit section will be used for control of post-commit scripts behavior.

At post-commit, the script is invoked without parameters. In that context the script may make use of the environment variables CONFD_MAAPI_USID and CONFD_MAAPI_THANDLE in order to attach to the active (read-only) transaction.

This makes it simple to make use of the maapi command. Especially the command maapi --keypath-diff / may turn out to be useful, as it provides a listing of all updates within the transaction on a format that is easy to parse.

27.7.1. Post-commit section

All post-commit scripts must be able to handle the argument --post-commit and, when invoked, write an empty post-commit section to stdout:

begin post-commit
end
          

27.7.2. Full post-commit example

Assume the administrator of a system would want to have a mail each time a change is performed on the system, a script such as mail_admin.sh:

#!/bin/bash

set -e

if [ $# -gt 0 ]; then
    case "$1" in
        --post-commit)
            cat &lt;&lt; EOF
begin post-commit
end
EOF
            exit 0
            ;;
        *)
            echo
            echo "Usage: $0 [--post-commit]"
            echo
            echo "  --post-commit Mandatory for post-commit scripts"
            exit 1
            ;;
    esac
else
    file="mail_admin.log"
    NCS_DIFF=$(ncs-maapi --keypath-diff /)
    mail -s "NCS Mailer" admin@example.com &lt;&lt;EOF
AutoGenerated mail from NCS

$NCS_DIFF
EOF
fi

       

If the admin then loads this script

admin@ncs# script reload debug
$NCS_DIR/examples.ncs/getting-started/using-ncs/1-simulated-cisco-ios/scripts:
ok
    post-commit:
        mail_admin.sh: new
--- Output from
$NCS_DIR/examples.ncs/getting-started/using-ncs/1-simulated-cisco-ios/scripts/post-commit/mail_admin.sh
--post-commit ---
1: begin post-commit
2: end
3:
---
admin@ncs# config
Entering configuration mode terminal
admin@ncs(config)# devices global-settings trace-dir ./again
admin@ncs(config)# commit
Commit complete.
        

This configuration change will produce an email to admin@example.com with subject NCS Mailer and body

AutoGenerated mail from NCS
value set  : /devices/global-settings/trace-dir
        

In the complete example examples.confd/scripting there is a README file and a simple post-commit script scripts/post-commit/show_diff.sh.