#!/bin/bash

# Copyright (c) 2011-2013 by Cisco Systems, Inc.
# All rights reserved.
# Please refer to "README-BDEO.txt" and "README-OVF.txt" for usage and documentation.

#************************************************#
#        bdeo - Build, Deploy, Execute OVF       #
#************************************************#
VER=v2.12

# Purpose:
# This is a tool predominantly to create an OVF or OVA from a CSR 1000V ISO image.
# User can choose to create this manually, or can deploy to an ESX Host, in which
# case the user also has a VM (pre-configured ISO image) to play with on the ESX Host.
# User inputs configure CPU's and Memory.
# Further inputs are specific to the vsphere configuration, and output name and directory
# When -d is added the tool uses ovftool (provided by vmware) in conjunction with an OVF template.
#
# See show_help function below for more details.
#
# What is an OVA?
#  OVA (Zipped OVF)
#     * OVF (directory of files)
#        * ISO file
#        * Optional: Hardware specific disk file: .vmdk etc
#        * XML: .ovf (file) : XML file that defines what's in a VM for resources: 8GM, 2CPU, *G HD, 3 NICS, etc
#        * Optional: Manifest - text file with SHA1 ran over other three files.
#
###INTERNAL...###
# BDEO 1.0 created in September 2011 by Rich Wellum (rwellum)
# BDEO 2.0 created in July 2012 by Glenn Matthews (glmatthe)
#
# This tool serves a double purpose.
# It's used internally within Cisco as part of the CSR 1000V build process
# and as a helper tool for developers and testers.
# It's also shipped externally to customers as sample code and as a demo tool.
# Some of the internal functionality and documentation should not be exposed
# to customers. Therefore the canonical version of this tool includes
# self-redacting capabilities. Any section wrapped with the INTERNAL...INTERNAL
# tags will be deleted when the tool is packaged into OVA for redistribution.
#
# The following EDCS documents are relevant references for bdeo:
# * EDCS-1161889 - OVF Descriptor Extensions and BDEO Enhancements SFS
# * EDCS-991326 - Virtual IOS VPN gateway SFS
# * EDCS-1024977 - vIOS-XE Platform And IO Control Plane SDS
# * EDCS-1214625 - Cisco Virtual Appliance Configuration SwFS
#
# BDEO demo slides and Webex recording:
# * EDCS-1187911
# * https://cisco.webex.com/ciscosales/lsr.php?AT=pb&SP=MC&rID=62931527&rKey=2a192506dbb18d29
#
# Other references:
# * http://www.davidpashley.com/articles/writing-robust-shell-scripts.html
#
# TODO items:
# * password hiding
#
###...INTERNAL###

SYS=`/bin/uname -s`
echo "$(basename $0) $VER($SYS)"
echo ""

# Error if unset variables are used
set -u
# Abort if any command fails and is uncaught by the script
set -e

# Create working directory in tmp
WORKING_DIR=$(mktemp -d) || exit 1

# Be sure to remove working directory when we're done
trap "rm -rf $WORKING_DIR" SIGHUP SIGINT SIGTERM EXIT

VERBOSE=                            # Not verbose by default

# Default values for input/output options
PWD=$(pwd)
OUTPUT_DIR=$PWD
STARTING_DIR=$PWD
BDEO_DIR=$(dirname $0)
IMAGE=                              # No default image file
IMAGE_NAME=                         # No default output image name
UNIQUE_NAME=                        # Output name not same as image file?
COMPOSITE_NAME=                     # Output name auto-generated from VM opts?
FORMAT=ova                          # Default output only .ova package
OVF_OUTPUT=                         # Default don't output uncompressed OVF
OVA_OUTPUT=                         # (will overwrite when parsing $FORMAT)
ZIP_OUTPUT=                         # Default don't output a compressed ZIP archive

# Default values for VM hardware options
CPUS=1                              # Default number of CPUs
MEMORY=2560                         # Default amount of RAM
DISK_SIZE=8                         # Default 8G Hard Drive
NICS=3                              # Default 3 NICS
ETHADAPTER='VMXNET3'                # Default adapter type for all NICs
VMNETWORK=                          # No default network for all NICs

# Default values for VM description options
PRODUCT='Cisco CSR 1000V Cloud Services Router'
                                    # Default product
VENDOR='Cisco Systems, Inc.'        # Default vendor
# Default short and long version strings
VERSION='DEV'
FULLVERSION='DEVELOPMENT IMAGE'
PURL='http://www.cisco.com/en/US/products/ps12559/index.html' # Default product URL
VURL='http://www.cisco.com'         # Default vendor URL

# Default values for ESXi/vSphere options
VSPHERE_ADDR=                       # Default do not use ESXi
ESXI_USERNAME=
ESXI_PASSWORD=
DATASTORE=datastore1                # Default datastore name
DISKMODE=thick                      # Default target disk format
PORTMAP=                            # Default use VMNETWORK values directly
OVERWRITE=true                      # Default is to overwrite if the image name already exists so multiple VM's are not created
POWERON=                            # Default is not to power on the vm so user has a chance to edit the config before deployment

# Default values for IOS configuration options
IOS_USERNAME=''                     # Default is none
IOS_PASSWORD=''                     # Default is none
ENABLE_PASSWORD=''                  # Default is none
IP_DOMAIN=''                        # Default no ip domain name
HOSTNAME=''                         # Default Hostname
MGMT_INTF='GigabitEthernet1'        # Default management interface
MGMT_VLAN=''                        # Default no vlan
MGMT_ADDR_V4=''                     # Default no IP addr on management interface
GATEWAY_V4=''                       # Default no default gateway for mgmt VRF
MGMT_NET_V4=''                      # Default no network and default route will be created
REMOTE_MGMT_ADDR_V4=''              # Default no IP addr for remote management
PNSC_ADDR_V4=''                     # Default no PNSC address
PNSC_AGENT_LOCAL_PORT=''            # Default no PNSC local port
PNSC_SHARED_SECRET=''               # Default no PNSC shared secret
SCP_SERVER=false                    # Default do not enable SCP server
SSH=false                           # Default do not set up SSH
CONFIG=                             # Default is no configuration file

# Locals
E_NOARGS=65
E_HELP=75
E_OTHER=85
DS=
DM=
InsID=11                            # Starting dynamic instance ID

show_args() {
echo -n "
Option               Params     Description {value}
-------------------  ---------  -----------------------------------------------

Help:
  -h | -help                    Display this help message and exit
  -verbose                      Produce verbose output while executing {$VERBOSE}

Input/Output Options:
  -i | -image        <path>     ISO Image to create OVF from, OR a .ovf or .ova
                                file to deploy to ESXi server. {$IMAGE}
  -o | -output       <path>     Output directory for OVF package and/or OVA file
                                {$OUTPUT_DIR}
  -n | -name         [<name>]   Create OVF/OVA with different name than image
                                If <name> is not specified, a name will be
                                automatically generated from the VM options
                                {$IMAGE_NAME}
  -format       [ovf|ova|zip]   Generate package in the given format(s)
                                (comma-separated list) {$FORMAT}

Virtual Machine Hardware Options:
  -c  | -cpus        <cpus>     Number of CPU's to provision {$CPUS}
  -m  | -memory      <MB>       Amount of memory to provision {$MEMORY}"
###INTERNAL...###
# Currently the disk size is not actually user-configurable,
# so we hide this option from end users
echo -n "
  -ds | -disksize    <GB>       Minimum size of hard drive to provision {$DISK_SIZE}"
###...INTERNAL###
echo -n "
  -ns | -nics        <nics>     Number of ethernet NICS to provision {$NICS}
  -ea | -eth_adapter <string>   Ethernet adapter type {$ETHADAPTER}
                                Valid values are: E1000, VMXNET3
  -nw | -network     <string>   VM Network name for all NICs, or comma-separated
                                list of one name per NIC {$VMNETWORK}

Virtual Machine Description Options:
  -p  | -product     <string>   Description of product {$PRODUCT}
  -v  | -vendor      <string>   Name of vendor {$VENDOR}
  -vs | -version_short <str>    Short version string {$VERSION}
  -vl | -version_long  <str>    Long version string {$FULLVERSION}
  -pu | -product_url <URL>      URL of product {$PURL}
  -vu | -vendor_url  <URL>      URL of vendor {$VURL}

ESXi/vSphere Options:
  -d  | -deploy      <URL>      Deploy OVA to the specified ESXi host {$VSPHERE_ADDR}
  -u  | -username    <string>   Username for ESXi login {$ESXI_USERNAME}
  -pw | -password    <string>   Password for ESXi login {$ESXI_PASSWORD}
  -s  | -store       <string>   Name of datastore {$DATASTORE}
  -dm | -diskmode    <option>   Type of diskmode for new VM. {$DISKMODE}
                                Valid values are:
                                  thick"
###INTERNAL...###
echo -n                               " | thin | sparse | flat |
                                  monolithicSparse | monolithicFlat |
                                  twoGbMaxExtentSparse | twoGbMaxExtentFlat |
                                  seSparse | eagerZeroedThick"
###...INTERNAL###
echo -n "
  -pm | -port_map    <list>     Comma separated list of port-map names to use
                                for each VM network from -network option. {$PORTMAP}
                                If not specified, will assume same as -network
  -nv | -nooverwrite            If set, don't overwrite an existing same-name VM
  -po | -poweron                If set, VM will power on automatically {$POWERON}

IOS Configuration Options:
  -iu  | -ios_username <string> IOS username (Required for remote login) {$IOS_USERNAME}
  -ipw | -ios_password <string> IOS password (Required for remote login) {$IOS_PASSWORD}
  -epw | -enable_password <str> IOS enable password {$ENABLE_PASSWORD}
  -ipd | -ip_domain  <string>   IP Domain Name {$IP_DOMAIN}
  -hn  | -hostname   <string>   Hostname {$HOSTNAME}
  -mi  | -mgmt_interface <intf> Management interface {$MGMT_INTF}
  -mv  | -mgmt_vlan <int>       Management VLAN (requires subinterface for
                                -mgmt-interface) {$MGMT_VLAN}
  -ip  | -ip_address <adr/mask> Address/mask for management interface, such as
                                '10.1.1.1/24' or '10.1.1.1 255.255.255.0' {$MGMT_ADDR_V4}
                                Can also specify the string 'dhcp' to use DHCP
  -mg  | -mgmt_gateway <addr>   Default gateway for management interface {$GATEWAY_V4}
                                Can also specify the string 'dhcp' to use DHCP
  -mn  | -mgmt_network <address/mask>
                                Network to route via management gateway.
                                {$MGMT_NET_V4}

  -rip | -remote_mgmt_ip_addr <addr>
                                IP address (without mask) for remote management.
                                Must belong to the same subnet as the management
                                interface IP address. {$REMOTE_MGMT_ADDR_V4}
  -pip | -pnsc_ip_addr <addr>   IP address (without mask) of the PNSC service
                                controller {$PNSC_ADDR_V4}
  -pp  | -pnsc_port <int>       Local SSL port to receive policies from PNSC
                                service manager {$PNSC_AGENT_LOCAL_PORT}
  -ps  | -pnsc_secret <string>  Shared secret to get SSL certificate from PNSC
                                service controller {$PNSC_SHARED_SECRET}
  -scp                          If set, enable SCP server {$SCP_SERVER}
  -ssh                          If set, enable SSH login (and disable telnet) {$SSH}
  -b   | -bootstrap  <path>     IOS configuration file (a la NVRAM output) to
                                add to bootstrap, for any configs not covered
                                by above options. {$CONFIG}
"
}

#=======================
show_help() {
#=======================
echo "
Build a Cisco CSR 1000V Cloud Services Router OVA from an ISO image.
Accepts user inputs for both Hardware and IOS configuration.
Optionally will deploy to an ESXi Host.
"
show_args
echo "
Examples:
  bdeo -i ultra.iso
    Build OVA 'ultra.ova' from ultra.iso with default values for all options

  bdeo -i ultra.iso -n my_vm
    Build OVA 'my_vm.ova' from ultra.iso with default values for all options

  bdeo -i ultra.iso -n
    Build a uniquely named OVA from ultra.iso with default options

  bdeo -i ultra.iso -o /auto/tftpboot
    Build OVA from ultra.iso and store the OVA in /auto/tftpboot

  bdeo -i ultra.iso -c 6 -m 8192 -ns 6
    Build OVA with 6 CPUs, 8GB memory, 6 NICs

  bdeo -i ultra.iso -d 10.122.84.23 -s datastore2
    Build OVA and deploy to vsphere ESXi 10.122.84.23 on datastore2

  bdeo -i ultra.iso -nics 3 -network 'Management Network,Network 1,Network 2' \\
       -d 10.122.84.3 -port_map 'VM Network,Net1,Net2'
    Build OVA with 3 NICs, labeled as 'Management Network', 'Network 1', and
    'Network 2' respectively. Then deploy this OVA to 10.122.84.3 and map the
    NICs to port-maps on the host defined as 'VM Network', 'Net1', and 'Net2'

  bdeo -i ultra.iso -o /ws/rwellum-sjc/OVA/Results/ -n bdeo_test_env \\
       -d '10.122.84.3/UCS Blade Server/host/10.122.84.25' \\
       -u administrator -pw root_1 -s 'datastore3' -b cfg.txt
    Build OVA with default options plus bootstrap IOS configuration 'cfg.txt',
    deploy via vcentre '.3' to ESXI '.25'
"
}

#=============================
gen_ovf_template() {
#============================
#Generates the base OVF Template that is then modified
cat << EOF_OVF_DESCRIPTOR > $IMAGE_NAME.ovf
<?xml version="1.0" encoding="UTF-8"?>
<!--OVF created by $USER, $(date)-->
<Envelope xmlns="http://schemas.dmtf.org/ovf/envelope/1"
          xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common"
          xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
          xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
          xmlns:vmw="http://www.vmware.com/schema/ovf"
          xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <References>
    <File ovf:href="$IMAGE_NAME.vmdk" ovf:id="file1" ovf:size="$VMDK_SIZE"/>
    <File ovf:href="$IMAGE_NAME.iso" ovf:id="file2" ovf:size="$ISO_SIZE"/>
    <!--FILE ADDITION-->
  </References>
  <DiskSection>
    <Info>Virtual disk information</Info>
    <Disk ovf:capacity="$DISK_SIZE" ovf:capacityAllocationUnits="byte * 2^30"
          ovf:diskId="vmdisk1" ovf:fileRef="file1"
          ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
          ovf:populatedSize="293011456"/>
  </DiskSection>
  <NetworkSection>
    <Info>The list of logical networks</Info>
    <!--NETWORK ADDITION-->
  </NetworkSection>
  <VirtualSystem ovf:id="$IMAGE_NAME">
    <Info>A virtual machine</Info>
    <Name>$IMAGE_NAME</Name>
    <OperatingSystemSection ovf:id="100" vmw:osType="other26xLinux64Guest">
      <Info>The kind of installed guest operating system</Info>
      <Description>$FULLVERSION</Description>
    </OperatingSystemSection>
    <VirtualHardwareSection ovf:transport="iso">
      <Info>Virtual hardware requirements</Info>
      <System>
        <vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
        <vssd:InstanceID>0</vssd:InstanceID>
        <vssd:VirtualSystemIdentifier>$IMAGE_NAME</vssd:VirtualSystemIdentifier>
        <vssd:VirtualSystemType>vmx-07 vmx-08</vssd:VirtualSystemType>
      </System>
      <Item>
        <rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
        <rasd:Description>Number of Virtual CPUs</rasd:Description>
        <rasd:ElementName>$CPUS virtual CPU(s)</rasd:ElementName>
        <rasd:InstanceID>1</rasd:InstanceID>
        <rasd:ResourceType>3</rasd:ResourceType>
        <rasd:VirtualQuantity>$CPUS</rasd:VirtualQuantity>
        <vmw:CoresPerSocket ovf:required="false">1</vmw:CoresPerSocket>
      </Item>
      <Item>
        <rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
        <rasd:Description>Memory Size</rasd:Description>
        <rasd:ElementName>${MEMORY}MB of memory</rasd:ElementName>
        <rasd:InstanceID>2</rasd:InstanceID>
        <rasd:ResourceType>4</rasd:ResourceType>
        <rasd:VirtualQuantity>$MEMORY</rasd:VirtualQuantity>
      </Item>
      <Item>
        <rasd:Address>0</rasd:Address>
        <rasd:Description>SCSI Controller</rasd:Description>
        <rasd:ElementName>SCSI Controller 0</rasd:ElementName>
        <rasd:InstanceID>3</rasd:InstanceID>
        <rasd:ResourceSubType>virtio lsilogic</rasd:ResourceSubType>
        <rasd:ResourceType>6</rasd:ResourceType>
      </Item>
      <Item>
        <rasd:Address>1</rasd:Address>
        <rasd:Description>IDE Controller</rasd:Description>
        <rasd:ElementName>VirtualIDEController 1</rasd:ElementName>
        <rasd:InstanceID>4</rasd:InstanceID>
        <rasd:ResourceType>5</rasd:ResourceType>
      </Item>
      <Item>
        <rasd:Address>0</rasd:Address>
        <rasd:Description>IDE Controller</rasd:Description>
        <rasd:ElementName>VirtualIDEController 0</rasd:ElementName>
        <rasd:InstanceID>5</rasd:InstanceID>
        <rasd:ResourceType>5</rasd:ResourceType>
      </Item>
      <Item>
        <rasd:AddressOnParent>0</rasd:AddressOnParent>
        <rasd:ElementName>Hard Drive</rasd:ElementName>
        <rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
        <rasd:InstanceID>6</rasd:InstanceID>
        <rasd:Parent>3</rasd:Parent>
        <rasd:ResourceType>17</rasd:ResourceType>
      </Item>
      <!-- CD-ROM drive for installation ISO -->
      <Item>
        <rasd:AddressOnParent>0</rasd:AddressOnParent>
        <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
        <rasd:ElementName>CD-ROM 1</rasd:ElementName>
        <rasd:HostResource>ovf:/file/file2</rasd:HostResource>
        <rasd:InstanceID>7</rasd:InstanceID>
        <rasd:Parent>4</rasd:Parent>
        <rasd:ResourceType>15</rasd:ResourceType>
      </Item>
      <!-- CD-ROM drive for ovf-env.xml -->
      <Item ovf:required="false">
       <rasd:AddressOnParent>1</rasd:AddressOnParent>
       <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
       <rasd:ElementName>CD-ROM 2</rasd:ElementName>
       <rasd:InstanceID>8</rasd:InstanceID>
       <rasd:Parent>4</rasd:Parent>
       <rasd:ResourceType>15</rasd:ResourceType>
      </Item>
      <!-- Console Port - ignored by ESXi as of version 5.0 -->
      <Item ovf:required="false">
       <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
       <rasd:Description>Serial Port acting as IOSd Console Port</rasd:Description>
       <rasd:ElementName>Serial 1</rasd:ElementName>
       <rasd:InstanceID>9</rasd:InstanceID>
       <rasd:ResourceType>21</rasd:ResourceType>
      </Item>
      <!-- Aux Port - ignored by ESXi as of version 5.0 -->
      <Item ovf:required="false">
       <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
       <rasd:Description>Serial Port acting as IOSd Aux Port</rasd:Description>
       <rasd:ElementName>Serial 2</rasd:ElementName>
       <rasd:InstanceID>10</rasd:InstanceID>
       <rasd:ResourceType>21</rasd:ResourceType>
      </Item>
      <!--NIC ADDITION-->
    </VirtualHardwareSection>
    <ProductSection ovf:required="false" ovf:class="com.cisco.csr1000v"
                    ovf:instance="1">
      <Info>Information about the installed software</Info>
      <Product>$PRODUCT</Product>
      <Vendor>$VENDOR</Vendor>
      <Version>$VERSION</Version>
      <FullVersion>$FULLVERSION</FullVersion>
      <ProductUrl>$PURL</ProductUrl>
      <VendorUrl>$VURL</VendorUrl>
      <!--VAPP ADDITION-->
    </ProductSection>
  </VirtualSystem>
</Envelope>
EOF_OVF_DESCRIPTOR
}

create_blank_vmdk() {
    # Create a blank vmdk hard drive disk, the original binary file was created from vsphere.
    # Zipped and uuencoded ascii stream.
    # bdeo echo's the stream out, unzips and decode it back to a binary file we can use.
    # cat blank-disk1.vmdk | bzip2 | uuencode -m blank-disk1.vmdk.bz2.enc
cat << EOF_VMDK > blank-disk1.vmdk.bz2.enc
begin-base64 644 blank-disk1.vmdk.bz2.enc
[payload removed: 636 characters in 11 lines]
====
EOF_VMDK

    uudecode blank-disk1.vmdk.bz2.enc -o blank-disk1.vmdk.bz2
    bunzip2 -c blank-disk1.vmdk.bz2 > $IMAGE_NAME.vmdk
    rm *bz2*
}

if [[ -z ${1:-} ]]; then
  show_help;
  exit $E_NOARGS
fi

ovftool_available() {
    # Check if user has access to ovftool
    # Did we already find it?
    if [[ -n ${OVFTOOL:-} ]] ; then
        return 0
    elif which ovftool &> /dev/null ; then
        # Check user's environment first for locally compiled version
        OVFTOOL=$( which ovftool )
        echo "$OVFTOOL found..."
        return 0
###INTERNAL...###
    elif [[ -f /auto/binos-tools/OVA/tools/vmware-ovftool/ovftool ]]; then
        # None found, attempt to use 64-bit red-hat version
        echo "'/auto/binos-tools/OVA/tools/vmware-ovftool/ovftool' found..."
        OVFTOOL=/auto/binos-tools/OVA/tools/vmware-ovftool/ovftool
        return 0
###...INTERNAL###
    else
        # Give up and warn user they need to go find ovftool
        echo ""
        echo "No ovftool found in your path, install 'ovftool' for your environment"
        echo ""
        return 1
    fi
}

#
# Helper function for parsing of script arguments
# $1 = argument (e.g. "-username")
# $2 = next parameter following argument (e.g., "administrator" or "-password")
# $3 = name of variable to write to
#
# If $2 is a valid value for $1 (not empty and not another argument) then copy
# it into the variable named $3 and return success. Else, return failure.
#
optional_parameter() {
    if [[ -z "${3:-}" ]]; then
	echo "Insufficient arguments to optional_parameter()"
	exit $E_OTHER
    fi

    local this_arg="$1"
    local next_arg="$2"
    local output_var="$3"

    if [[ (-n ${!output_var}) && ($next_arg == ${!output_var}) ]]; then
	echo "NOTE: '$this_arg $next_arg' is default value"
    fi

    # get next_arg if not empty, and does not start with -
    if [[ (-z $next_arg) || (${next_arg:0:1} == "-") ]]; then
	return 1
    else
	eval "$output_var=\$next_arg"
	return 0
    fi
}

#
# Helper function for parsing of script arguments
# $1 = argument (e.g., "-username")
# $2 = next parameter following argument (e.g., "administrator" or "-password")
# $3 = description of argument in $1, for error messages etc.
# $4 = name of variable to write to
#
# If $2 is a valid value for $1 (not empty and not another argument) then
# copy it into the variable named $4. Else, abort the script.
#
mandatory_parameter() {
    if [[ -z "${4:-}" ]]; then
	echo "Insufficient arguments to mandatory_parameter()"
	exit $E_OTHER
    fi

    local this_arg="$1"
    local next_arg="$2"
    local description="$3"
    local output_var="$4"

    if ! optional_parameter "$this_arg" "$next_arg" "$output_var" ; then
	echo "Following '$this_arg', expected $description, but got '$next_arg'"
	exit $E_HELP
    fi
}

# Suck in the user options
set +u
while [[ $# -ne 0 ]];
do
    case $1 in
	-h | -help )
	    show_help;
	    exit $E_NOARGS
	    ;;

        -verbose)
            VERBOSE=true
            ;;

	# Input/Output Options
	-i | -image)
	    mandatory_parameter "$1" "$2" "an image name" IMAGE
	    if [[ ! -f $IMAGE ]]; then
		echo "Image '$IMAGE' does not exist"
		exit $E_HELP
	    fi

            case "${IMAGE##*.}" in
                "ovf" | "ova" ) IMAGE_OVF=true;;
                "iso" ) IMAGE_OVF=;;
                *)
                    echo "-image ($IMAGE) must be a .iso, .ova, or .ovf file"
                    exit $ENOARGS
                    ;;
            esac
	    ;;

	-o | -output)
	    mandatory_parameter "$1" "$2" "an output directory" OUTPUT_DIR
	    if [[ ! -d $OUTPUT_DIR ]]; then
		echo "Output directory '$OUTPUT_DIR' does not exist"
		exit $E_HELP
	    fi
	    ;;

	-n | -name)
	    UNIQUE_NAME=true
	    if ! optional_parameter "$1" "$2" IMAGE_NAME ; then
		# '-n' by itself so create a composite unique name for the user
		COMPOSITE_NAME=true
	    fi
	    ;;

	-format)
            mandatory_parameter "$1" "$2" "comma-separated list of formats (ovf,ova,zip)" FORMAT
	    ;;

        # Virtual Machine Hardware Options
	-c | -cpus)
	    mandatory_parameter "$1" "$2" "a number of CPUs" CPUS
	    ;;

	-m | -memory)
	    mandatory_parameter "$1" "$2" "a memory amount (MB)" MEMORY
	    ;;

###INTERNAL...###
# This option doesn't work at present - only 8 GB disks are supported
	-ds | -disksize)
            if [[ $2 -lt $DISK_SIZE ]]; then
		echo "'$2' too low, minimum hard disk size is '$DISK_SIZE'"
		exit $E_HELP
	    fi
	    mandatory_parameter "$1" "$2" "a hard drive size (GB)" DISK_SIZE
	    ;;

###...INTERNAL###
	-ns | -nics)
	    mandatory_parameter "$1" "$2" "a number of NICs" NICS
	    ;;

	-ea | -eth_adapter)
	    mandatory_parameter "$1" "$2" "E1000 or VMXNET3" ETHADAPTER
	    ;;

	-nw | -network)
	    mandatory_parameter "$1" "$2" "a VM network name or list of names" VMNETWORK
	    ;;

	# Virtual Machine Description Options
	-p | -product)
	    mandatory_parameter "$1" "$2" "a product name" PRODUCT
	    ;;

	-v | -vendor)
	    mandatory_parameter "$1" "$2" "a vendor name" VENDOR
	    ;;

	-vs | -version_short)
	    mandatory_parameter "$1" "$2" "a short version string" VERSION
	    ;;

	-vl | -version_long)
	    mandatory_parameter "$1" "$2" "a full version string" FULLVERSION
	    ;;

	-pu | -product_url)
	    mandatory_parameter "$1" "$2" "a product URL" PURL
	    ;;

	-vu | -vendor_url)
	    mandatory_parameter "$1" "$2" "a vendor URL" VURL
	    ;;

        # ESXi/vsphere process options
	-d | -deploy)
	    mandatory_parameter "$1" "$2" "an IP address" VSPHERE_ADDR
	    ;;

	-u | -username)
	    mandatory_parameter "$1" "$2" "ESXi username" ESXI_USERNAME
	    ;;

	-pw | -password)
	    mandatory_parameter "$1" "$2" "ESXi password" ESXI_PASSWORD
	    ;;

	-s | -store)
	    mandatory_parameter "$1" "$2" "a datastore location" DATASTORE
	    DS=true
	    ;;

	-dm | -diskmode)
	    mandatory_parameter "$1" "$2" "a valid diskmode" DISKMODE
            case "$2" in
                "thin" | "thick" | "monolithicSparse" | "monolithicFlat" | \
                "twoGbMaxExtentSparse" | "twoGbMaxExtentFlag" | "streamOptimized" )
                    ;;
                *)
		    echo "-diskmode '$2' has to be one of:"
                    echo "     thin | thick | monolithicSparse | monolithicFlat |"
                    echo "     twoGbMaxExtentSparse | twoGbMaxExtentFlat | streamOptimized"
		    exit $E_HELP
                    ;;
	    esac
	    DM=true
	    ;;

	-pm | -port_map)
	    mandatory_parameter "$1" "$2" "a port-map name or list of names" PORTMAP
	    ;;

	-nv | -nooverwrite)
	    OVERWRITE=
	    ;;

	-po | -poweron)
	    POWERON=true
	    ;;

	-iu | -ios_username)
	    mandatory_parameter "$1" "$2" "an IOS username" IOS_USERNAME
	    ;;

	-ipw | -ios_password)
	    mandatory_parameter "$1" "$2" "an IOS password" IOS_PASSWORD
	    ;;

	-epw | -enable_password)
	    mandatory_parameter "$1" "$2" "an IOS enable password" ENABLE_PASSWORD
	    ;;

	-ipd | -ip_domain)
	    mandatory_parameter "$1" "$2" "an IP domain name" IP_DOMAIN
	    ;;

	-hn | -hostname)
	    mandatory_parameter "$1" "$2" "a hostname" HOSTNAME
	    ;;

        -mi | -mgmt_interface)
            mandatory_parameter "$1" "$2" "an interface name" MGMT_INTF
            ;;

        -mv | -mgmt_vlan)
            mandatory_parameter "$1" "$2" "a VLAN number" MGMT_VLAN
            ;;

        -ip | -ip_address)
	    mandatory_parameter "$1" "$2" "an IP address/mask or the string 'dhcp'" MGMT_ADDR_V4
            ;;

        -mg | -mgmt_gateway)
	    mandatory_parameter "$1" "$2" "an IP address or the string 'dhcp'" GATEWAY_V4
            ;;

        -mn | -mgmt_network)
            mandatory_parameter "$1" "$2" "an IP address/mask" MGMT_NET_V4
            ;;

        -rip | -remote_mgmt_ip_addr)
            mandatory_parameter "$1" "$2" "an IP address" REMOTE_MGMT_ADDR_V4
            ;;

        -pip | -pnsc_ip_addr)
            mandatory_parameter "$1" "$2" "an IP address" PNSC_ADDR_V4
            ;;

        -pp | -pnsc_port)
            mandatory_parameter "$1" "$2" "a port number" PNSC_AGENT_LOCAL_PORT
            ;;

        -ps | -pnsc_secret)
            mandatory_parameter "$1" "$2" "a string" PNSC_SHARED_SECRET
            ;;

        -scp)
            SCP_SERVER=true
            ;;

	-ssh | -ssh)
	    SSH=true
	    ;;

	-b | -bootstrap)
	    mandatory_parameter "$1" "$2" "a file name" CONFIG
	    if [[ ! -f $CONFIG ]]; then
                echo "File $CONFIG not found."
                exit $E_HELP
            fi
	    ;;

	-* )
	    echo "Unknown argument '$1'"
	    exit $E_HELP
	    ;;
    esac
    shift
done
set -u

# Image is mandatory
if [[ -z $IMAGE ]]; then
    echo "No image detected - '-image <image>' is mandatory"
    show_help;
    exit $E_NOARGS
fi

# Convert VMNETWORK and PORTMAP args from comma-separated lists to arrays
# Temporarily use comma as bash list-separator instead of default
OLD_IFS="${IFS}"
IFS=,

# FORMAT must be a list of one or more entries, case insensitive
for format in $FORMAT ; do
    case "$format" in
        "ovf" | "OVF" | ".ovf" ) OVF_OUTPUT=true;;
        "ova" | "OVA" | ".ova" ) OVA_OUTPUT=true;;
        "zip" | "ZIP" | ".zip" ) ZIP_OUTPUT=true;;
        *)
            echo "-format ($FORMAT) must be a comma-separated list of one or"
            echo "more of the following: ovf, ova, zip"
            exit $E_NOARGS
            ;;
    esac
done

if [[ -z "$VMNETWORK" ]]; then
    # Automatically assign each NIC to a different network
    for (( i = 0 ; i < $NICS; i++ )) ; do
        VMNETWORK_ARR[$i]="GigabitEthernet$(($i + 1))"
        if [[ $i == 0 ]]; then
            VMNETWORK=${VMNETWORK_ARR[$i]}
        else
            VMNETWORK="$VMNETWORK,${VMNETWORK_ARR[$i]}"
        fi
    done
else
    # VMNETWORK must be either a single entry or a list of up to $NICS entries
    i=0
    # Construct array from list
    for net in $VMNETWORK ; do
        VMNETWORK_ARR[$i]=$net
        i=$(($i + 1))
    done
fi
# Check array size versus number of NICS
if [[ ${#VMNETWORK_ARR[@]} -gt $NICS ]]; then
    echo "-network ($VMNETWORK) must be a single argument or a "
    echo "comma-separated list of at most NICS ($NICS) elements"
    exit $E_NOARGS
fi

# If specified, PORTMAP must not exceed size of VMNETWORK_ARR
if [[ -n "$PORTMAP" ]]; then
    i=0
    # Construct array from list
    for pm in $PORTMAP ; do
	PORTMAP_ARR[$i]=$pm
	i=$(($i + 1))
    done
    while [[ $i -lt ${#VMNETWORK_ARR[@]} ]] ; do
        PORTMAP_ARR[$i]=$pm
        i=$(($i + 1))
    done
    # Check portmap array size versus vmnetwork array size
    if [[ ${#PORTMAP_ARR[@]} -gt ${#VMNETWORK_ARR[@]} ]]; then
	echo "-port_map ($PORTMAP) must be a comma-separated list "
	echo "of same length as -vm ($VMNETWORK)"
	exit $E_NOARGS
    fi
fi

# Revert to default list-separator
IFS="$OLD_IFS"

# Deployment option
# If user is deploying, must provide username and password
if [[ -n $VSPHERE_ADDR ]]; then
    if ! ovftool_available; then
        echo "Unable to automatically deploy to ESXi - Please install ovftool"
        exit $E_NOARGS
    fi
else
    # User added deployment options but did not specify a - deploy so warn and ignore these options
    if [[ -n $ESXI_USERNAME ]]; then
	echo "OVA not deployed (-d); ignoring '-u $ESXI_USERNAME'"
    fi
    if [[ -n $ESXI_PASSWORD ]]; then
	echo "OVA not deployed (-d); ignoring '-pw <password>'"
    fi
    if [[ $DS ]]; then
	echo "OVA not deployed (-d); ignoring '-s $DATASTORE'"
    fi
    if [[ $DM ]]; then
	echo "OVA not deployed (-d); ignoring '-dm $DISKMODE'"
    fi
    if [[ -n $PORTMAP ]]; then
	echo "OVA not deployed (-d); ignoring '-pm \"$PORTMAP\"'"
    fi
    if [[ -z $OVERWRITE ]]; then
        echo "OVA not deployed (-d); ignoring '-nooverwrite'"
    fi
    if [[ $POWERON ]]; then
	echo "OVA not deployed (-d); ignoring '-poweron'"
    fi
fi

if [[ $SSH == true ]]; then
    if [[ -z $IOS_USERNAME || -z $IOS_PASSWORD ]]; then
        echo "Warning: SSH will be enabled, but remote login will not be "
        echo "         possible until an IOS username (-ios_username) and "
        echo "         IOS password (-ios_password) are configured"
    fi
fi

# Calls the ovftool to validate the provided OVF descriptor file.
# Also works for OVA archives (will auto-extract the OVF descriptor)
validate_ovf() {
    local validate_image=`basename "$1"`
    local validate_path=`dirname "$1"`
    if [[ -z $1 ]]; then
        echo "Insufficient arguments to validate_ovf()"
        exit $E_OTHER
    fi
    # Check validity of OVF
    echo ""
    echo "OVF Sanity Check"
    echo "----------------"
    pushd "$validate_path" > /dev/null
    set +e
    if [[ $VERBOSE ]]; then
        "$OVFTOOL" --schemaValidate $validate_image
    else
        "$OVFTOOL" --schemaValidate $validate_image > /dev/null
    fi
    set -e
    if [[ $? -ne 0 ]]; then
        echo ""
        echo "OVF descriptor schema invalid - exiting bdeo"
        echo ""
        exit $E_OTHER;
    fi
    popd > /dev/null
}

deploy_ovf() {
    local deploy_image=`basename "$1"`
    local deploy_path=`dirname "$1"`
    if [[ -z $1 ]]; then
        echo "Insufficient arguments to deploy_ovf()"
        exit $E_OTHER
    fi
    local vm_name="${2:-}" # Optional parameter
    echo ""
    echo "Deploying $deploy_image to '$VSPHERE_ADDR'"
    echo "------------------------------------------"
    pushd "$deploy_path" > /dev/null

    # Populate default ovftool parameters into an array
    ovf_args=('--powerOffTarget' "--diskMode=$DISKMODE" "-ds=$DATASTORE")
    # Append any additional parameters to the array
    if [[ $OVERWRITE ]]; then
	ovf_args=("${ovf_args[@]}" "--overwrite")
    fi
    if [[ $POWERON ]]; then
	ovf_args=("${ovf_args[@]}" "--powerOn")
    fi
    if [[ -n $PORTMAP ]]; then
	for (( i=0 ; i < "${#PORTMAP_ARR[@]}" ; i++ )) ; do
	    ovf_args=("${ovf_args[@]}" "--net:${VMNETWORK_ARR[$i]}=${PORTMAP_ARR[$i]}")
	done
    fi

    if [[ -n $vm_name ]]; then
        echo "VM name will be '$vm_name'"
        ovf_args=("${ovf_args[@]}" "--name=$vm_name")
    fi
    # Source and dest file must be the last arguments
    ovf_args_sanitized=("${ovf_args[@]}" "$deploy_image"
	"vi://${ESXI_USERNAME}:********@$VSPHERE_ADDR")
    ovf_args_real=("${ovf_args[@]}" "$deploy_image"
	"vi://${ESXI_USERNAME}:${ESXI_PASSWORD}@$VSPHERE_ADDR")

    # Call ovftool with the given arguments
    echo "$OVFTOOL ${ovf_args_sanitized[@]}"
    if ! "$OVFTOOL" "${ovf_args_real[@]}"; then
        echo ""
        echo "Could not deploy - exiting bdeo"
        echo ""
        exit 1
    fi
    popd > /dev/null
}


# Name the image correctly.
if [[ $UNIQUE_NAME ]]; then
    if [[ $COMPOSITE_NAME ]]; then
	TEMP_NAME=$(basename $IMAGE .iso)-'C'$CPUS-'M'$MEMORY-'N'$NICS-'DS'$DISK_SIZE
	IMAGE_NAME=$TEMP_NAME
    fi
else
   # user did not specify an unique name so use the image name
   # User could enter image pointing to a directory (like linkfarm/ultra-iso/ultra.iso)
   # Remove the .iso as well
    IMAGE_NAME=$(basename $IMAGE .iso)
fi
# Replace ':' with a '-'
IMAGE_NAME=`echo $IMAGE_NAME | sed -e 's/:/-/g'`

# If user has provided an .ovf or .ova file, then simply deploy it.
if [[ $IMAGE_OVF ]]; then
    if [[ $VERBOSE ]]; then
        # Tell the user what they get for free from the OVF template
        echo ""
        grep 'ElementName' "$IMAGE" | cut -d '>' -f 2 | cut -d '<' -f 1
    fi

    if ! ovftool_available; then
        echo "Could not locate ovftool; unable to validate or deploy OVF package"
        exit $E_OTHER
    fi

    validate_ovf $IMAGE

    deploy_ovf $IMAGE $IMAGE_NAME

    exit 0
fi

# We have an image and an image name - copy to our working directory
cp $IMAGE $WORKING_DIR/$IMAGE_NAME.iso # Good place to rename IMAGE

# Copy files from BDEO directory to working directory
if [[ -f "$BDEO_DIR/README-OVF.txt" ]]; then
    cp "$BDEO_DIR/README-OVF.txt" "$WORKING_DIR/README-OVF.txt"
fi
if [[ -f "$BDEO_DIR/README-BDEO.txt" ]]; then
    cp "$BDEO_DIR/README-BDEO.txt" "$WORKING_DIR/README-BDEO.txt"
    # TODO - should we error out here, since this README spells out the terms of use?
fi
# Copy this tool to the working directory too
cp "$0" "$WORKING_DIR/bdeo.sh"

if [[ -n $CONFIG ]]; then
    CONFIG_PATH=$CONFIG
    CONFIG=$(basename $CONFIG)

    # Properly escape special characters &><'" to avoid breaking the XML.
    # They will automatically be de-escaped when parsed on the other side.
    # Also replace tabs with spaces for easier parsing later in the script.
    sed "s/\&/\&amp;/g ;\
         s/>/\&gt;/g   ;\
         s/</\&lt;/g   ;\
         s/'/\&apos;/g ;\
         s/\"/\&quot;/g; \
         s/\t/ /g" $CONFIG_PATH > $WORKING_DIR/$CONFIG
fi

cd $WORKING_DIR
if [[ $VERBOSE ]]; then
    echo "Working directory is $WORKING_DIR"
    echo ""
fi

##################################################
# Create new ovf file with vm name and the image #
##################################################

echo "Generating OVF file with user params"
echo "------------------------------------"

# Create blank disk image in our working directory
create_blank_vmdk;

ISO_SIZE=$( stat -c %s $IMAGE_NAME.iso)
VMDK_SIZE=$( stat -c %s $IMAGE_NAME.vmdk)

# Generate the base ovf template
gen_ovf_template;

###########################
# Dynamic property section#
###########################

insert_xml_into_ovf () {
    local file="$1"
    local label="$2"

    if [[ -z $2 ]]; then
        echo "Insufficient arguments to insert_xml_into_ovf()"
        exit $E_OTHER
    fi

    # Find the line with the given label
    # Insert the file contents here
    # Delete the label line
    sed -i "/<!--${label}-->/{
                r $file
                /<!--${label}-->/d
            }" $IMAGE_NAME.ovf
    rm $file
    return 0
}

# Add VM Network information to OVF
for net_name in "${VMNETWORK_ARR[@]}" ; do
    # Don't add redundant Network entries!
    if ! grep -qs "<Network ovf:name=\"$net_name\"" network.txt; then
        cat << EOF >> network.txt
    <Network ovf:name="$net_name">
      <Description>$net_name</Description>
    </Network>
EOF
    fi
done
insert_xml_into_ovf network.txt "NETWORK ADDITION"

# Uses the Environment/Property within the OVF standard to pull in IOS CLI and populate
# them in an generated ovf-env.xml file. If you create this section in the .ovf descriptor,
# add in an empty CDROM and set 'transport=iso', then when the VM is powered up, the ovf-env.xml file
# is created within an ISO which is mounted on the CDROM. Then 'glue' code is required in binos to
# extract the CLI information from the XML, and save it on the bootflash where IOSd can access it. In
# IOSd, in the setup sequence, the presence of this file is checked and if it exists the commands can
# be parsed.

# TODO: At present, it is up to the user to pre-escape any special characters
# such as &'"<> in any of the following arguments provided to this script.
# If unescaped these characters will break the XML format.
# None of these characters are likely to be used except possibly in a password,
# so this is OK for now. But in the future it would be better to be safe.

cat << EOF_PROPERTY_ADDITIONS > vapp.txt
      <Property ovf:key="config-version" ovf:value="1.0"
                ovf:type="string" ovf:userConfigurable="false">
        <Description>DO NOT CHANGE THIS VALUE</Description>
      </Property>
      <Category>1. Bootstrap Properties</Category>
      <Property ovf:key="login-username" ovf:value="$IOS_USERNAME"
                ovf:type="string" ovf:qualifiers="MaxLen(64)"
                ovf:userConfigurable="true">
        <Label>Login Username</Label>
        <Description>Username for remote login</Description>
      </Property>
      <Property ovf:key="login-password" ovf:value="$IOS_PASSWORD"
                ovf:type="string" ovf:password="true"
                ovf:qualifiers="MaxLen(25)" ovf:userConfigurable="true">
        <Label>Login Password</Label>
        <Description>Password for remote login.
WARNING: While this password will be stored securely within IOS, the plain-text \
password will be recoverable from the OVF descriptor file.</Description>
      </Property>
      <Property ovf:key="mgmt-interface" ovf:value="$MGMT_INTF"
                ovf:type="string" ovf:userConfigurable="true">
        <Label>Management Interface</Label>
        <Description>Management interface (such as \
"GigabitEthernet1" or "GigabitEthernet1.100")</Description>
      </Property>
      <Property ovf:key="mgmt-vlan" ovf:value="$MGMT_VLAN"
                ovf:type="string" ovf:qualifiers="MaxLen(5)"
                ovf:userConfigurable="true">
        <Label>Management VLAN</Label>
        <Description>Management dot1Q VLAN (requires specifying a \
subinterface such as "GigabitEthernet1.100" for the Management Interface)</Description>
      </Property>
      <Property ovf:key="mgmt-ipv4-addr" ovf:value="$MGMT_ADDR_V4"
                ovf:type="string" ovf:qualifiers="MaxLen(33)"
                ovf:userConfigurable="true">
        <Label>Management Interface IPv4 Address/Mask</Label>
        <Description>IPv4 address and mask for management interface \
(such as "192.0.2.100/24" or "192.0.2.100 255.255.255.0"), \
or "dhcp" to configure via DHCP</Description>
      </Property>
      <Property ovf:key="mgmt-ipv4-gateway" ovf:value="$GATEWAY_V4"
                ovf:type="string" ovf:qualifiers="MaxLen(16)"
                ovf:userConfigurable="true">
        <Label>Management IPv4 Gateway</Label>
        <Description>IPv4 gateway address (such as "192.0.2.1") for \
management interface, or "dhcp" to configure via DHCP</Description>
      </Property>
      <Property ovf:key="mgmt-ipv4-network" ovf:value="$MGMT_NET_V4"
                ovf:type="string" ovf:qualifiers="MaxLen(33)"
                ovf:userConfigurable="true">
        <Label>Management IPv4 Network</Label>
        <Description>IPv4 network (such as "192.168.2.0/24" or "192.168.2.0 255.255.255.0") \
that the management gateway should route to.</Description>
      </Property>

      <Property ovf:key="remote-mgmt-ipv4-addr" ovf:value="$REMOTE_MGMT_ADDR_V4"
                ovf:type="string" ovf:qualifiers="MaxLen(15)"
                ovf:userConfigurable="true">
        <Label>Remote Management IPv4 Address</Label>
        <Description>IPv4 address without mask (such as "192.0.2.101") for \
access to remote management features (REST API, etc.). This should be in the \
same IP subnet as the Management Interface IPv4 Address entered \
above.</Description>
      </Property>
      <Property ovf:key="pnsc-ipv4-addr" ovf:value="$PNSC_ADDR_V4"
                ovf:type="string" ovf:qualifiers="MaxLen(15)"
                ovf:userConfigurable="true">
        <Label>PNSC IPv4 Address</Label>
        <Description>IPv4 address without mask (such as "192.0.2.110") of \
PNSC service controller</Description>
      </Property>
      <Property ovf:key="pnsc-agent-local-port" ovf:value="$PNSC_AGENT_LOCAL_PORT"
                ovf:type="string" ovf:qualifiers="MaxLen(5)"
                ovf:userConfigurable="true">
        <Label>PNSC Agent Local Port</Label>
        <Description>PNSC service agent SSL port (on local CSR) to receive \
policies from service manager</Description>
      </Property>
      <Property ovf:key="pnsc-shared-secret-key" ovf:value="$PNSC_SHARED_SECRET"
                ovf:type="string" ovf:password="true" ovf:qualifiers="MaxLen(64)"
                ovf:userConfigurable="true">
        <Label>PNSC Shared Secret Key</Label>
        <Description>PNSC service controller shared secret key (8-64 \
characters)for PNSC agent to get SSL certificate from the controller.
WARNING: While this password will be stored securely within IOS, the \
plain-text password will be recoverable from the OVF descriptor file.\
</Description>
      </Property>
      <Property ovf:key="hostname" ovf:value="$HOSTNAME"
                ovf:type="string" ovf:qualifiers="MaxLen(63)"
                ovf:userConfigurable="true">
        <Label>Router Name</Label>
        <Description>Hostname of this router</Description>
      </Property>
      <Category>2. Features</Category>
      <Property ovf:key="enable-scp-server" ovf:value="$SCP_SERVER"
                ovf:type="boolean" ovf:userConfigurable="true">
        <Label>Enable SCP Server</Label>
        <Description>Enable IOS SCP server feature</Description>
      </Property>
      <Property ovf:key="enable-ssh-server" ovf:value="$SSH"
                ovf:type="boolean" ovf:userConfigurable="true">
        <Label>Enable SSH Login and Disable Telnet Login</Label>
        <Description>Enable remote login via SSH and disable remote login \
via telnet. Requires login-username and login-password to be set!</Description>
      </Property>
      <Category>3. Additional Configuration Properties</Category>
      <Property ovf:key="privilege-password" ovf:value="$ENABLE_PASSWORD"
                ovf:type="string" ovf:password="true"
                ovf:qualifiers="MaxLen(25)" ovf:userConfigurable="true">
        <Label>Enable Password</Label>
        <Description>Password for privileged (enable) access.
WARNING: While this password will be stored securely within IOS, the plain-text \
password will be recoverable from the OVF descriptor file.</Description>
      </Property>
      <Property ovf:key="domain-name" ovf:value="$IP_DOMAIN"
                ovf:type="string" ovf:qualifiers="MaxLen(238)"
                ovf:userConfigurable="true">
        <Label>Domain Name</Label>
        <Description>Network domain name (such as "cisco.com")</Description>
      </Property>
EOF_PROPERTY_ADDITIONS

# Next, we pull in any general IOS CLI from a bootstrap config file, if provided:
if [[ -n $CONFIG ]]; then
    # Add to the property section - parse contents of $CONFIG file into separate key and value lines
    echo "      <Category>9999. IOS Config Commands</Category>" >> vapp.txt

    count=0
    while read LINE
    do
	if [[ "$LINE" =~ '^ *$' ]]; then
	    # Empty line - ignore
	    continue
	elif [[ "$LINE" =~ '^ *!' ]]; then
	    # Lines beginning with ! are IOS comments.
	    # Hence, no reason to pass through as config commands.
	    # If not empty (there is text after the !), convert to XML comment.
	    # ${LINE#*\!} strips everything up to and including the leading !
	    LINE=`echo ${LINE#*\!}`
	    if [[ -n $LINE ]]; then
		echo '      <!--' "$LINE" ' -->' >> vapp.txt
	    fi
	else
	    # An actual config command - convert to an XML property
	    let count=count+1
	    #Pad count because vmware sorts these lines using a string compare,
	    #so padding ensures we keep the order they were entered
	    #Note I am assuming no config over 9999 lines...
	    cnt_pad=`printf "%04d" $count`
cat << EOF >> vapp.txt
      <Property ovf:key="ios-config-$cnt_pad" ovf:value="$LINE"
                ovf:type="string" ovf:qualifiers="MaxLen(255)"
                ovf:userConfigurable="false" />
EOF
	fi
    done < $CONFIG
fi

# Insert all of the generated XML properties into the ovf file
insert_xml_into_ovf vapp.txt "VAPP ADDITION"

# Create appropriate number of NICS
# Have to play with the Instance ID somewhat this needs to be more systematic

InsIDMAX=$(($InsID + $NICS))
nic_index=0

for (( dynIns=InsID; dynIns<InsIDMAX; dynIns++ ))
do
    nic_index=$(($dynIns - $InsID))
    # If we have multiple VM networks, grab the correct one for this NIC
    if [[ ${#VMNETWORK_ARR[@]} -gt 1 ]]; then
	nic_network="${VMNETWORK_ARR[$nic_index]}"
    else
	nic_network="$VMNETWORK"
    fi
    # NICs start at GigabitEthernet1 currently
    nic_name="GigabitEthernet$(($nic_index + 1))"
    echo "\
      <Item>
        <rasd:AddressOnParent>$dynIns</rasd:AddressOnParent>
        <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
        <rasd:Connection>$nic_network</rasd:Connection>
        <rasd:Description>$ETHADAPTER ethernet adapter on \"$nic_network\"</rasd:Description>
        <rasd:ElementName>$nic_name</rasd:ElementName>
        <rasd:InstanceID>$dynIns</rasd:InstanceID>
        <rasd:ResourceSubType>$ETHADAPTER</rasd:ResourceSubType>
        <rasd:ResourceType>10</rasd:ResourceType>
      </Item>" >> nic.txt
done
insert_xml_into_ovf nic.txt "NIC ADDITION"


# Add bdeo and documentation files to OVF

# Replace default version/fullversion info in the script copy with the
# version/fullversion we're currently using, so this info can propagate forward.
sed -i "s/^VERSION='.*'/VERSION='$VERSION'/" bdeo.sh
sed -i "s/^FULLVERSION='.*'/FULLVERSION='$FULLVERSION'/" bdeo.sh
###INTERNAL...###
# Redact all internal-only sections
sed -i '/^###INTERNAL...###/,/^###...INTERNAL###/d' bdeo.sh
###...INTERNAL###

BDEO_SIZE=$( stat -c %s bdeo.sh)
echo "    <File ovf:href=\"bdeo.sh\" ovf:id=\"bdeo\" ovf:size=\"$BDEO_SIZE\"/>" >> file.txt

if [[ -f "README-OVF.txt" ]]; then
    README_OVF_SIZE=$( stat -c %s README-OVF.txt)
    echo "    <File ovf:href=\"README-OVF.txt\" ovf:id=\"readme-ovf\" ovf:size=\"$README_OVF_SIZE\"/>" >> file.txt
else
    echo ""
    echo "Warning: Could not locate README-OVF.txt to include in OVF package."
    echo "Do not redistribute without the original files provided by Cisco!"
    echo ""
fi

if [[ -f "README-BDEO.txt" ]]; then
    README_BDEO_SIZE=$( stat -c %s README-BDEO.txt)
    echo "    <File ovf:href=\"README-BDEO.txt\" ovf:id=\"readme-bdeo\" ovf:size=\"$README_BDEO_SIZE\"/>" >> file.txt
else
    echo ""
    echo "Warning: Could not locate README-BDEO.txt to include in OVF package."
    echo "Do not redistribute without the original files provided by Cisco!"
    echo ""
fi

insert_xml_into_ovf file.txt "FILE ADDITION"


if [[ $VERBOSE ]]; then
    # Tell the user what they have configured
    show_args

    if [[ -n $CONFIG ]]; then
        echo ""
        echo "Configuration file contents:"
        cat $CONFIG
    fi

    # Tell the user what they get for free from the OVF template
    # Note some overlap with the above
    echo ""
    grep 'ElementName' "$IMAGE_NAME.ovf" | cut -d '>' -f 2 | cut -d '<' -f 1
fi

if ovftool_available ; then
    validate_ovf $IMAGE_NAME.ovf
else
    echo "ovftool not available; unable to perform OVF sanity check. Continuing."
fi

echo ""
echo "Generating Manifest"
echo "---------------------"

# Create Manifest - note optional but a good check
touch $IMAGE_NAME.mf

# The OVF package must always contain these files in this order:
package_files="$IMAGE_NAME.ovf"
package_files="$package_files $IMAGE_NAME.mf"
package_files="$package_files $IMAGE_NAME.vmdk"
package_files="$package_files $IMAGE_NAME.iso"
# Add our additional special files to the package:
package_files="$package_files bdeo.sh"
if [[ -f "README-OVF.txt" ]]; then
    package_files="$package_files README-OVF.txt"
fi
if [[ -f "README-BDEO.txt" ]]; then
    package_files="$package_files README-BDEO.txt"
fi
# Add file checksums to manifest
for file in $package_files; do
    if [[ ${file##*.} == "mf" ]]; then
        continue # Don't checksum the manifest itself!
    fi
    echo "SHA1($file)= $(sha1sum $file | cut -d' ' -f1)" >> $IMAGE_NAME.mf
done

if [[ $VERBOSE ]]; then
    cat $IMAGE_NAME.mf
fi

if [[ $OVF_OUTPUT ]]; then
    echo ""
    echo "Creating OVF package"
    echo "--------------------"
    # Copy uncompressed OVF package to output subdirectory
    if [[ ! -d $OUTPUT_DIR/$IMAGE_NAME ]]; then
        mkdir $OUTPUT_DIR/$IMAGE_NAME
    fi
    cp $package_files $OUTPUT_DIR/$IMAGE_NAME

    echo "'$OUTPUT_DIR/$IMAGE_NAME/'"
    if [[ $VERBOSE ]]; then
        ls -1 $OUTPUT_DIR/$IMAGE_NAME
    fi
fi

if [[ $OVA_OUTPUT ]]; then
    echo ""
    echo "Creating OVA package"
    echo "--------------------"
    # OVA is just a tarred version of the files in a certain order - note no 'z' option
    tar cf $OUTPUT_DIR/$IMAGE_NAME.ova $package_files

    echo "'$OUTPUT_DIR/$IMAGE_NAME.ova'"
    if [[ $VERBOSE ]]; then
        # Show the user what is in the OVA package
        tar tf $OUTPUT_DIR/$IMAGE_NAME.ova
    fi
fi

if [[ $ZIP_OUTPUT ]]; then
    echo ""
    echo "Creating ZIP package"
    echo "--------------------"
    # Create ZIP archive. Do not bother trying to compress the .iso file.
    quiet_flag=''
    if [[ ! $VERBOSE ]]; then
        quiet_flag='-q'
    fi
    zip $quiet_flag -n .iso "$OUTPUT_DIR/$IMAGE_NAME.zip" $package_files

    echo "'$OUTPUT_DIR/$IMAGE_NAME.zip'"
fi


# Deploy the image if requested
if [[ -n $VSPHERE_ADDR ]]; then
    deploy_ovf "$OUTPUT_DIR/$IMAGE_NAME.ova"
fi

#######################
# Clean up temp files #
#######################

echo ""
echo "Success"

exit 0

# Local Variables:
# indent-tabs-mode: nil
# End:
