|
<%#
|
|
kind: provision
|
|
name: Junos default SLAX
|
|
-%>
|
|
version 1.0;
|
|
|
|
/* ------------------------------------------------------------------
|
|
* LICENSE
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* Copyright (c) 2013, Juniper Networks
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* (1) Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* (2) Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* The views and conclusions contained in the software and documentation are those
|
|
* of the authors and should not be interpreted as representing official policies,
|
|
* either expressed or implied, of Juniper Networks.
|
|
*
|
|
*
|
|
* ------------------------------------------------------------------
|
|
* AUTHORS AND CONTRIBUTORS
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* Jeremy Schulman, Juniper Networks
|
|
* - initial release (https://github.com/jeremyschulman/jctyztp/)
|
|
*
|
|
* Frank Wall, noris network AG
|
|
* - adapt for integration in The Foreman
|
|
* - fix compatibility with older Junos releases
|
|
*
|
|
*
|
|
* ------------------------------------------------------------------
|
|
* LIMITATIONS
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* To maintain backwards compatibility with JunOS 11.x (and maybe
|
|
* even 10.x) you MUST AVOID all of these:
|
|
* - global variable $junos-context (introduced with Junos 11.1)
|
|
* - mutable variables (introduced with JunOS 12.2 -> SLAX 1.1)
|
|
* - native functions (introduced with JunOS 12.2 -> SLAX 1.1)
|
|
*
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* XML namespaces */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Juniper */
|
|
ns junos = "http://xml.juniper.net/junos/*/junos";
|
|
ns xnm = "http://xml.juniper.net/xnm/1.1/xnm";
|
|
ns jcs = "http://xml.juniper.net/junos/commit-scripts/1.0";
|
|
ns ztp = "http://xml.juniper.net/junos/ztp";
|
|
|
|
/* EXSLT */
|
|
ns exsl extension = "http://exslt.org/common";
|
|
ns func extension = "http://exslt.org/functions";
|
|
ns str extension = "http://exslt.org/strings";
|
|
|
|
/* private namespace for this script */
|
|
ns jctyztp = "http://xml.juniper.com/jctyztp/1.0";
|
|
ns fmztp = "http://xml.juniper.com/fmztp/1.0";
|
|
|
|
/* depending on Junos version relative path may be broken */
|
|
/* import '../import/junos.xsl'; */
|
|
import '/usr/libdata/cscript/import/junos.xsl';
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Script parameters */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
param $server = 'ztpserver';
|
|
param $mediapath = '<%= @mediapath %>';
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Constants */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
var $APPNAME = 'foreman-ztp';
|
|
var $SYSLOG = 'user.info';
|
|
var $TMPDIR = '/var/tmp';
|
|
var $JUNOS_CONF = '/var/tmp/junos.conf';
|
|
|
|
var $ZTP_GROUP_NAME = "fmztp";
|
|
var $ZTP_MACRO_NAME = "conf";
|
|
var $ZTP_LOCKFILE = '/tmp/fmztp.lock';
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Global variables */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Open a connection to the device API */
|
|
var $jnx = jcs:open();
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* MAIN */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
match / {
|
|
|
|
/* Terminate on connection error */
|
|
if(not( $jnx )) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ":ERROR: unable to connect to Junos API");
|
|
expr fmztp:terminate();
|
|
}
|
|
|
|
var $running = fmztp:only_once();
|
|
if( $running ) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": start op script: already running, exiting..." );
|
|
<xsl:message terminate="yes">;
|
|
}
|
|
|
|
/* if the $JUNOS_CONF file is not on the device, then */
|
|
/* download it from the server */
|
|
if(not( fmztp:file-exists( $JUNOS_CONF ))) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": obtaining device config file");
|
|
var $cp = fmztp:dl_junos_conf();
|
|
}
|
|
|
|
/* now load $JUNOS_CONF into the candidate configuration so we can */
|
|
/* extract the ZTP config */
|
|
var $ztp_conf = fmztp:ld_junos_conf();
|
|
var $has_version = $ztp_conf/has_version;
|
|
var $new_package = $ztp_conf/package;
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": preparing update from ", $has_version, " to ", $new_package );
|
|
|
|
/* if we have a version difference, then we will install the new OS */
|
|
/* and reboot the device. the $JUNOS_CONF file will be loaded on */
|
|
/* after the install process completes */
|
|
if( $ztp_conf/install ) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": Junos install required" );
|
|
var $os = fmztp:install_os( $ztp_conf );
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": rebooting in 60 seconds" );
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": SCRIPT-END");
|
|
expr fmztp:reboot( 1 );
|
|
expr jcs:close( $jnx );
|
|
}
|
|
else {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": no Junos install required" );
|
|
/* puppet agent is only supported on Junos 12.3R2.5 */
|
|
if( jcs:regex( "12.3R2.5", $ztp_conf/has_version )) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": puppet agent install required" );
|
|
var $puppet = fmztp:install_puppet( $ztp_conf );
|
|
}
|
|
else {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": os version is ", $has_version, ", but puppet agent is only available on 12.3R2.5" );
|
|
}
|
|
var $fini = fmztp:finalize();
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": SCRIPT-END");
|
|
expr jcs:close( $jnx );
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* HTTP Junos configuration file onto the device */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:dl_junos_conf">
|
|
{
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": downloading new Junos conf...");
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": URL: ", '<%= foreman_url('finish') %>');
|
|
|
|
var $get = <file-copy> {
|
|
<source> '<%= foreman_url('finish') %>';
|
|
<destination> $JUNOS_CONF;
|
|
<staging-directory> $TMPDIR;
|
|
};
|
|
|
|
var $got = jcs:execute( $jnx, $get );
|
|
|
|
if(not( fmztp:file-exists( $JUNOS_CONF ))) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ":ERROR: unable to find new Junos conf at ", $JUNOS_CONF);
|
|
expr fmztp:terminate();
|
|
}
|
|
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Load the $JUNOS_CONF file into the candidate config and extract */
|
|
/* the ZTP config from the [edit groups] area. Do *NOT* commit */
|
|
/* this configuration yet, since we may need to install the OS first */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:ld_junos_conf">
|
|
{
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": loading new Junos conf...");
|
|
|
|
/* get the current version from the configuration file */
|
|
|
|
var $get_cur_ver = <get-configuration database='committed'> { <configuration> { <version>; }};
|
|
var $got_cur_ver = jcs:execute( $jnx, $get_cur_ver );
|
|
|
|
/* now load the configuration file we got from the ztp server */
|
|
var $do_load = <load-configuration action="override" url=$JUNOS_CONF format="text">;
|
|
var $did_load = jcs:execute( $jnx, $do_load );
|
|
if(not( $did_load/load-success )) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ":ERROR: unable to load config ", $JUNOS_CONF );
|
|
expr fmztp:terminate();
|
|
}
|
|
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": extracting Junos config parameters...");
|
|
var $get = <get-configuration> { <configuration> {
|
|
<version>;
|
|
<groups> { <name> $ZTP_GROUP_NAME;
|
|
<apply-macro> { <name> $ZTP_MACRO_NAME;
|
|
}
|
|
}
|
|
}};
|
|
|
|
var $got = jcs:execute( $jnx, $get );
|
|
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": package: ", $got//data[name = 'package']/value);
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": URL: ", $mediapath);
|
|
|
|
/* create a node-set to store the following elements */
|
|
/* has_version = current Junos version string */
|
|
/* package = filename of Junos package (*.tgz) */
|
|
/* mediapath = URL where package is obtained from */
|
|
/* install = present if a install is requeired */
|
|
var $ver = $got_cur_ver/version;
|
|
var $package = $got//data[name = 'package']/value;
|
|
var $puppet = $got//data[name = 'puppet']/value;
|
|
var $conf := {
|
|
<has_version> $ver;
|
|
<package> $package;
|
|
<puppet> $puppet;
|
|
<url> $mediapath;
|
|
if(not( jcs:regex( $ver, $package ))) {
|
|
<install>;
|
|
}
|
|
}
|
|
|
|
/* @@@ should put some trap here on ensuring that the config */
|
|
/* @@@ file actually had the correct group/macro defined */
|
|
|
|
<func:result select="$conf">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Junos Software Installation - download the software from the HTTP */
|
|
/* server and perform the 'request system software add' operation */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:install_os">
|
|
{
|
|
param $ztp_conf;
|
|
|
|
var $local_image = $TMPDIR _ "/" _ $ztp_conf/package;
|
|
|
|
if( fmztp:file-exists( $local_image )) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": junos image exists, no download needed" );
|
|
}
|
|
else {
|
|
|
|
/* request system storage cleanup */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": cleaning filesystem" );
|
|
var $clean = jcs:execute( $jnx, 'request-system-storage-cleanup' );
|
|
|
|
/* file copy .... */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": downloading junos image..." );
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": URL: ", $ztp_conf/url, $ztp_conf/package );
|
|
var $do_copy = <file-copy> {
|
|
<source> $ztp_conf/url _ $ztp_conf/package;
|
|
<destination> $TMPDIR;
|
|
<staging-directory> $TMPDIR;
|
|
};
|
|
var $did_copy = jcs:execute( $jnx, $do_copy );
|
|
|
|
/* trap error here */
|
|
if( not(fmztp:file-exists( $local_image )) ) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": ERROR: unable to download junos image" );
|
|
expr fmztp:terminate();
|
|
}
|
|
}
|
|
|
|
/* request system software add ... */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": installing junos image" );
|
|
var $do_install = <request-package-add> {
|
|
<no-validate>;
|
|
<package-name> $local_image;
|
|
};
|
|
var $did_install = jcs:execute( $jnx, $do_install );
|
|
/* @@@ need to trap error here on $did_install */
|
|
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": completed installing junos image" );
|
|
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Reboot the device given a delay, in minutes */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:reboot">
|
|
{
|
|
param $in_min;
|
|
|
|
var $do_reboot = <request-reboot> { <in> $in_min; };
|
|
var $did_reboot = jcs:execute( $jnx, $do_reboot );
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Puppet Agent Installation - download the software from the HTTP */
|
|
/* server and perform the 'request system software add' operation */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:install_puppet">
|
|
{
|
|
param $ztp_conf;
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": starting puppet installation..." );
|
|
|
|
if (fmztp:is-installed("puppet")) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": package puppet is already installed" );
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
var $local_image = $TMPDIR _ "/" _ $ztp_conf/puppet;
|
|
|
|
if( fmztp:file-exists( $local_image )) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": package puppet exists, no download needed" );
|
|
}
|
|
else {
|
|
|
|
/* request system storage cleanup */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": cleaning filesystem" );
|
|
var $clean = jcs:execute( $jnx, 'request-system-storage-cleanup' );
|
|
|
|
/* file copy .... */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": downloading puppet image..." );
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": URL: ", $ztp_conf/url, $ztp_conf/puppet );
|
|
var $do_copy = <file-copy> {
|
|
<source> $ztp_conf/url _ $ztp_conf/puppet;
|
|
<destination> $TMPDIR;
|
|
<staging-directory> $TMPDIR;
|
|
};
|
|
var $did_copy = jcs:execute( $jnx, $do_copy );
|
|
|
|
/* trap error on $did_copy */
|
|
if( not(fmztp:file-exists( $local_image )) ) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": ERROR: failed to download puppet image" );
|
|
expr fmztp:terminate();
|
|
}
|
|
}
|
|
|
|
/* request system software add ... */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": installing puppet image" );
|
|
var $do_install = <request-package-add> {
|
|
<no-validate>;
|
|
<package-name> $local_image;
|
|
};
|
|
var $did_install = jcs:execute( $jnx, $do_install );
|
|
|
|
/* NOTE: To complete the puppet installation you need to take manual steps, see:
|
|
* http://www.juniper.net/techpubs/en_US/release-independent/junos-puppet/information-products/pathway-pages/index.html
|
|
*/
|
|
|
|
/* validate installation */
|
|
if (not(fmztp:is-installed("puppet"))) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": ERROR: failed to install package puppet" );
|
|
expr fmztp:terminate();
|
|
}
|
|
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": completed installing puppet image" );
|
|
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Finalize the ZTP process; i.e. after the OS is correct. Remove */
|
|
/* the $JUNOS_CONF file and committing the configuration to make */
|
|
/* it active. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:finalize">
|
|
{
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": deleting temp config file...");
|
|
var $rm1 = fmztp:file-delete( $JUNOS_CONF );
|
|
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": sending finish signal to foreman...");
|
|
var $do_foreman = <file-copy> {
|
|
<source> '<%= foreman_url %>';
|
|
<destination> "/tmp";
|
|
<staging-directory> "/tmp";
|
|
};
|
|
var $did_foreman = jcs:execute( $jnx, $do_foreman );
|
|
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": deleting ztp lock file...");
|
|
var $rm2 = fmztp:file-delete( $ZTP_LOCKFILE );
|
|
|
|
/* commit the configuration that was previously loaded */
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": committing configuration...");
|
|
var $commit = jcs:execute( $jnx, 'commit-configuration' );
|
|
if( $commit//self::xnm:error ) {
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ":ERROR: unable to commit configuration: ", $commit//self::xnm:error/message );
|
|
var $die = fmztp:terminate();
|
|
}
|
|
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Helper routine: check to see if a file exists on the device, */
|
|
/* returns [ true | false ] */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:file-exists">
|
|
{
|
|
param $filename;
|
|
var $ls_file = <file-list> { <path> $filename; };
|
|
var $ls_got = jcs:execute( $jnx, $ls_file );
|
|
var $retval = boolean( $ls_got//file-information );
|
|
|
|
<func:result select="$retval">;
|
|
}
|
|
|
|
<func:function name="fmztp:file-delete">
|
|
{
|
|
param $filename;
|
|
var $do_rm = <file-delete> { <path> $filename; };
|
|
var $did_rm = jcs:execute( $jnx, $do_rm );
|
|
/* @@@ trap error */
|
|
|
|
<func:result select="true()">;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Helper routine: create a lockfile to make sure the script only */
|
|
/* runs once, and terminate if it is already running. */
|
|
/* returns [ true | false ] */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
<func:function name="fmztp:only_once">
|
|
{
|
|
if( fmztp:file-exists( $ZTP_LOCKFILE )) {
|
|
<func:result select="true()">;
|
|
}
|
|
else {
|
|
var $do_lock = <file-put> {
|
|
<filename> $ZTP_LOCKFILE;
|
|
<encoding> 'ascii';
|
|
<file-contents> 'locked';
|
|
};
|
|
var $did_lock = jcs:execute( $jnx, $do_lock );
|
|
<func:result select="false()">;
|
|
}
|
|
}
|
|
|
|
<func:function name="fmztp:terminate">
|
|
{
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": SCRIPT-FAILED" );
|
|
var $rm_lock = fmztp:file-delete( $ZTP_LOCKFILE );
|
|
<xsl:message terminate="yes">;
|
|
}
|
|
|
|
/* ------------------------------------------------- */
|
|
/* check if software package is installed */
|
|
/* ------------------------------------------------- */
|
|
<func:function name="fmztp:is-installed">
|
|
{
|
|
param $string;
|
|
|
|
var $do_query = <get-software-information>;
|
|
var $did_query = jcs:execute( $jnx, $do_query );
|
|
|
|
if( jcs:regex( $string, $did_query )) {
|
|
expr jcs:output("package found: ", $string);
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": package found: ", $string);
|
|
<func:result select="true()">;
|
|
}
|
|
else {
|
|
expr jcs:output("package NOT found: ", $string);
|
|
expr jcs:syslog( $SYSLOG, $APPNAME, ": package NOT found: ", $string);
|
|
<func:result select="false()">;
|
|
}
|
|
}
|