Creating a Customizable Linux OVF Template

Share this:

Recently a team mate asked me to create a small linux image with a certain tool set, so he can use it for network testing. You may say, why to create it, if you can download pre-created images already. Well, thats true, but it was a nice challenge and an opportunity to dust off my Linux skills.

I have to state that what I did is far from perfect, considering I’ve spent only several hours on it. but it can be a good start for anyone trying to do the same.

Now, creation of OVF/OVA templates in VMware vSphere is nothing new. There are bunch of articles out there on the web, some are old some are new, but I wasn’t able to find even one which would cover the process end to end.

So here is what I’ve done.

1. Choose a distro.

First of all I had to decide what Linux distro to use. Not much to think for me, I know RedHat, I am even certified a RHCSA, so well, I will use CentOS. But the process is universal, should work on any distro.

2. Download and install.

My goal was to make the template as small as possible, so I decided to use the Netinstall image of CentOS. To my surprise it was not listed on the official CentOS website, but a fast Google search resolved that for me.

3. Install a VM and install needed packages.

Nothing much to cover here. Usual VM build with minimal install. Run yum update to update the system.

Then make sure you have open-vm-tools installed. Just run yum install open-vm-tools. If it’s installed it will say it’s installed, if not it will install it for you.

To check if service is running use  systemctl status vmtoolsd. VMware tools are critical for customization, you will know why later.

4. Enable and configure vApp Options.

Now, template customization is happening using parameters provided by vApp settings. So we need to Configure those first.

a.Enable vApp Options.

Edit setting of you VM. Click the vApp Options tab. select the Enable vApp options checkbox.

b.Fill in authoring details(if you want)

Enter Name of the image, Version, and any other details you would want to have here.

c.Change OVF settings.

I selected OVF environment transport to be VMware tools.

d. Create OVF properties.

This is where you define which settings you want to gather during OVF.OVA deployment, so that you can configure those on the OS. VM need to be powered off, so you can configure this.

I chose to have IP, Netmask, Default Gateway, hostname and 2 DNS servers.

To create each of these parameters click New under Properties:

Here is an example of IP setting.

  • Category: Helps you group properties.
  • Label: The label with which this property will be shown in Deployment wizard.
  • Key ID: ID of this property in vApp properties XML file. Can be whatever.
  • Type: type of the property, in this case it’s a String.

Here is a list of Properties I’ve created for this image.

5. Fetch data

Time to configure OS to fetch those settings on boot, and sett each property.

But to do this we need to have a  way to see these properties inside the OS. And here is where VMware tools come into play.

Here is the command to fetch vApp settings from inside the VM.

vmtoolsd --cmd "info-get guestinfo.ovfenv"

Output of this file is in XML format, so all we are left to do is to parse it and do the config.

6. Configure OS

There are many ways to do this in various programming languages. I am not a programmer, so I decided to go with a shell script.

And BTW, I have to say, I am not an expert in shell scripting either, I am sure there are much more much better ways to do what I’ve done. But considering I’ve spend only couple of hours with this, I am quite satisfied.

First things first. How to parse the XML output and get the values we need? Here is what I came up with.

#!/bin/bash

vmtoolsd --cmd "info-get guestinfo.ovfenv" > /tmp/ovf_env.xml
TMPXML='/tmp/ovf_env.xml'

# gathering values
date +"%m.%d.%Y %T "; echo "Sorting..."
IP=`cat $TMPXML| grep -e ip0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
NETMASK=`cat $TMPXML| grep -e netmask0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
GW=`cat $TMPXML| grep -e gateway |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
HOSTNAME=`cat $TMPXML| grep -e hostname |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
DNS0=`cat $TMPXML| grep -e dns0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
DNS1=`cat $TMPXML| grep -e dns1 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`

Basically whats happening here is , write the output of the vmtoolsd command to a temp xml file, then I cat this file and parse it using grep and sed to find info I need. I don’t claim this to be the most elegant solution though 🙂 All parameters are assigned to variables for later use.

Now, we have all the settings we need, lets configure OS.

Network settings

CentOS uses Network manager by default, I could disable it to avoid the haste, but I decided to use it instead. Bellow is the code to configure Connection using Network manager CLI.

# NMCLI fetch existing netwrok interface device name
IFACE=`nmcli dev|grep ethernet|awk '{print $1}'`

# NMCLI fetch existing connection name. This will have to be recreated.
CON=`nmcli con show|grep -v NAME| awk '{print $1}'`
# NMCLI remove connection
nmcli con delete $CON

# Create new connection
# Check if IP and NETMASk variables exist and are not empty
if [ -z ${IP+x} ] && [ -z ${NETMASK+x} ]; then

		# IF empty configure connection to use DHCP
		nmcli con add con-name "$IFACE" ifname "$IFACE" type ethernet
	else

		# If variables exist, configure interface with IP and netmask and GW. Also set DNS settings in same step.
		nmcli con add con-name "$IFACE" ifname $IFACE type ethernet ip4 $IP/$NETMASK gw4 $GW && echo "IP set to $IP/$NETMASK. GW set to $GW"
		nmcli con mod "$IFACE" ipv4.dns "$DNS0,$DNS1" && echo "DNS set to $DNS0,$DNS1"
fi

Configure Hostname

hostnamectl set-hostname $HOSTNAME --static

7. Additional considerations..

There are several things to consider.

1.We need to make sure this script will run only one on first boot. To resolve this I added code to create a state file. Every time script runs it will check if state file exists, and if yes it will exit.

STATE='/opt/ovfset/state'

if [ -e $STATE ]
	then
		date +"%m.%d.%Y %T "
		echo "$STATE file exists. Doing nothing."
		exit 1
	else
# WHOLE SCRIPT CONTENT IS EXECUTED AS PART OF else.

date +"%m.%d.%Y %T "
echo "This script will not be executed on next boot if $STATE file exists"
echo "If you want to execute this configuration on Next boot remove $STATE file"

date +"%m.%d.%Y %T " ; echo "Creating State file"
date > /opt/ovfset/state

fi

2. We need to log. So in the overall script I will be adding echo before each action and will place the log near the state file.

3. After all is done its not a bad idea to reboot, so I will add a reboot action to the end of the script.

4. Place the script in some permanent OS accessible directory. I chose to place it to /opt/ovfset.

8. Run script on boot

Again, as with  other settings in Linux, there are many ways you can use to run things on boot. I chose the old fashioned way using rc.d.

But in CentOS 7 rc.d is not executed by default. to enable it you need to grant Execute rights to the rc.local script.

chmod u+x /etc/rc.d/rc.local

Then, just add a command you want to run on boot to the end of the rc.local file

bash /opt/ovfset/ovf_set.sh >> /opt/ovfset/run.log 2>&1

Description: Use bash, to run /opt/ovfset/ovf_set.sh script, redirect all output to /opt/ovfset/run.log. 

9. Final touches

Before powering off your VM, make sure state file is deleted. You can also consider removing any signs of your activity on the VM, like command history, and SSH keys.

Connect VM to a generic virtual switch, like for example VM network.

Once ready, showdown the VM, and export it as and OVF or OVA.

10. Deploy OVF/OVA

This is how wizard will look during deployment.

11. Whole script

I know my script is not ideal, but I will attach it here in case anyone will want to take and improve it.

#!/bin/bash

STATE='/opt/ovfset/state'

if [ -e $STATE ]
	then
		date +"%m.%d.%Y %T "
		echo "$STATE file exists. Doing nothing."
		exit 1
	else
		echo "+++++++++++++++++++++++++++++++++++++++++++"
		echo "++++++++++++ OVF Config script ++++++++++++"
		echo "+ System will be rebooted after execution +"
		echo "+++++++++++++++++++++++++++++++++++++++++++"


		# create XML file with settings
		date +"%m.%d.%Y %T " ; echo "Fetcing values"
		vmtoolsd --cmd "info-get guestinfo.ovfenv" > /tmp/ovf_env.xml
		TMPXML='/tmp/ovf_env.xml'

		# gathering values
		date +"%m.%d.%Y %T "; echo "Sorting..."
		IP=`cat $TMPXML| grep -e ip0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
		NETMASK=`cat $TMPXML| grep -e netmask0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
		GW=`cat $TMPXML| grep -e gateway |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
		HOSTNAME=`cat $TMPXML| grep -e hostname |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
		DNS0=`cat $TMPXML| grep -e dns0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
		DNS1=`cat $TMPXML| grep -e dns1 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`

		# NMCLI fetch existing netwrok interface device name
		date +"%m.%d.%Y %T "; echo "Gathering info about network interfaces..."
		IFACE=`nmcli dev|grep ethernet|awk '{print $1}'`

		# NMCLI fetch existing connection name. This will have to be recreated.
		CON=`nmcli con show|grep -v NAME| awk '{print $1}'`
		# NMCLI remove connection
		nmcli con delete $CON

		# Create new connection
		date +"%m.%d.%Y %T " ; echo "Setting Network settings...."
		# Check if IP and NETMASk variables exist and are not empty
		if [ -z ${IP+x} ] && [ -z ${NETMASK+x} ]; then
				date +"%m.%d.%Y %T " ; echo "No IP information found. Trying DHCP"
				# IF empty configure connection to use DHCP
				nmcli con add con-name "$IFACE" ifname "$IFACE" type ethernet
			else
				date +"%m.%d.%Y %T " ; echo "Setting..."
				# If variables exist, configure interface with IP and netmask and GW. Also set DNS settings in same step.
				nmcli con add con-name "$IFACE" ifname $IFACE type ethernet ip4 $IP/$NETMASK gw4 $GW && echo "IP set to $IP/$NETMASK. GW set to $GW"
				nmcli con mod "$IFACE" ipv4.dns "$DNS0,$DNS1" && echo "DNS set to $DNS0,$DNS1"
		fi 

		# Set Hostname
		date +"%m.%d.%Y %T " ; echo "Setting Hostname..."
		hostnamectl set-hostname $HOSTNAME --static

		# Notification for future
		date +"%m.%d.%Y %T "
		echo "This script will not be executed on next boot if $STATE file exists"
		echo "If you want to execute this configuration on Next boot remove $STATE file"

		date +"%m.%d.%Y %T " ; echo "Creating State file"
		date > /opt/ovfset/state

		# Wait a bit and reboot
		sleep 5
		reboot
fi

12. Closing word

Not perfect I know, but works. Let me know if you have questions. always open for discussion.

The following two tabs change content below.
Aram Avetisyan is an IT specialist with more than 18 years experience. He has rich background in various IT related fields like Cloud, Virtualization and SDN. He holds several industry level certifications including but not limited to VCIX-DCV, VCIX-NV. He is also a vEXPERT in years 2014-2021.

About Aram Avetisyan

Aram Avetisyan is an IT specialist with more than 18 years experience. He has rich background in various IT related fields like Cloud, Virtualization and SDN. He holds several industry level certifications including but not limited to VCIX-DCV, VCIX-NV. He is also a vEXPERT in years 2014-2021.
Bookmark the permalink.

10 Comments

  1. Your explanation was well written and just the thing I was looking for. I also appreciate your example script to use as a starting point. Thanks for making this guide, it worked for me using vCenter 6.5.

  2. I ended up needing to add “mv /opt/ovfset/run.log /opt/ovfset/lastrun.log” to the end of the script before the sleep line to get useful logging out of the script, else the state file notification would overwrite the log.

    • Assuming you are using rc.local as well to execute the script on boot(which is not an ideal method of course) redirecting output to log using “>>” instead of “>” should make sure file is not overwritten on reboot. So command would look like “bash /opt/ovfset/ovf_set.sh >> /opt/ovfset/run.log 2>&1”. Using your method is also an option though. Thanks for pointing out though, i will update the article.

  3. Thank you for this effort on writing this post. However, I can’t seem to make it work for me as my template reboots permanently after using whole script in rc.local. Am I missing something here ?

    • Hey Falko,

      Not sure what you mean by “using whole script in rc.local.”
      There are several things to consider:
      1. you should not include the whole script in rc.local, you should include pointer to the script so it is executed (e.g. bash /opt/ovfset/ovf_set.sh, covered in paragraph 8 if the article.)
      2. The script includes a reboot command, if it reboots every time it means the script is not able to check is state file exists. make sure the path /opt/ovfset/ exists in your environment, and that the script is able to write to it (covered in paragraph 7 of the article)
      3. rc.local is just one of the options to run scripts on boot in Linux, the method to use very much depends on the linux distribution and the version of distribution. So if rc.local does not work for you try some other method, like created a service from the script.

      If you are still running into issues, we can try to troubleshoot, but for that we will need more info. For example you can try to login into single user mode and see whats happening during boot and why is it rebooting.

  4. This seems to be a working script except the networking. It is not pushing in the static information from the script to the network manager.

    After the script, it still pulls dhcp. Any help would be appreciated.

    • Hi Matt, the script was written on CentOS 6 if i remember correctly. It is possible that the nmcli behaviour changes in recent versions so i would look in that direction. If you followed the article end to end you should have a run.log generated, take a look in that file see if there is any strange messaging like, cannot find interface or something..

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.