Loading...
Loading...
Marco Franssen

In the previous part of this series, we built a basic Debian VM with the minimum packages needed to run a web server. In this part, we'll improve our Packer script with user variables and learn how to use the file and shell provisioners.
We can easily add variables to the Packer script using the following JSON.
{
"variables": {
"username": "root",
"password": "r00tme",
"memory": "1024",
"cpus": "1",
"database_name": "{{env `DB_NAME`}}"
},
"builders": [{
// Left for brevity
}A good practice is to put your variables first in the JSON, before your builders. This keeps all the script's configurable values easy to find. As you can see, we define a default value for each variable, which is used when the user doesn't supply one. For the database_name variable, I used a special default that is retrieved from an environment variable. You can set this just as you would any other environment variable from your command line or shell.
set DB_NAME=mydatabaseexport DB_NAME=mydatabaseWhen the environment variable is not set, the value of database_name is an empty string. In this template format, environment variables can only be used in user variables, not elsewhere in the template. This prevents confusion about the template's possible inputs. To override the other values when building your Packer VM, set them when executing the build. You can do this using the following command.
packer build \
-var 'cpus=1' \
-var 'memory=512' \
packer-debian-x64-webserver.jsonAny user variables not overridden here use their default values. You can inspect your template by running the following command from your command line or shell.
packer inspect packer-debian-x64-webserver.jsonThe command shows the contents of your template: in our case, the variables and our VirtualBox ISO builder.
Now that we know how to define user variables, I just need to show you how to use their values in the template. Replace the corresponding parts of the Packer template with the following JSON.
"ssh_username": "{{user `username`}}",
"ssh_password": "{{user `password`}}",
//Left for brevity...
"vboxmanage": [
["modifyvm", "{{.Name}}", "--memory", "{{user `memory`}}"],
["modifyvm", "{{.Name}}", "--cpus", "{{user `cpus`}}"],
["modifyvm", "{{.Name}}", "--vram", "10"]
],Now the values for ssh_username, ssh_password, --memory, and --cpus will come from our user variables. Feel free to make disk_size configurable through a user variable too. Take a minute to apply what you've just learned and turn disk_size into a user variable.
To execute shell scripts on our VM, we first need to make them available there. The easiest way is to use the file provisioner to upload the scripts. Once they're uploaded, we can execute them with the shell provisioner. We probably also want our website's source files on the VM. After the builders property in our template, we'll add the following provisioners property.
"builders": [{
//Left for brevity...
}],
"provisioners": [{
"type": "file",
"source": "scripts",
"destination": "/tmp"
}, {
"type": "shell",
"script": "prepare_data_folder"
}, {
"type": "file",
"source": "webpage",
"destination": "/data"
}, {
"type": "shell",
"script": "setup_database",
"environment_vars": [
"DB_NAME={{user `database_name`}}"
]
}, {
"type": "shell",
"script": "configure_apache"
}]The provisioners run in the order we list them here. First, all scripts from our scripts folder are uploaded to /tmp. Then we execute prepare_data_folder so we can upload our website files to /data. Next, we execute the setup_database script, passing an environment variable that the script can use. Notice that we're using one of the user variables defined earlier. Last but not least, we execute the configure_apache script.
First, let me show you a simple script to create the data folder.
#!/bin/sh
mkdir -p /data
chown www-data:www-data /data
chmod 755 /dataThis script creates the /data folder and makes Apache's user the owner. It also makes the directory writable by that owner. Feel free to modify the script to suit your needs. The file provisioner then uploads the files from our webpage folder to /data. Next, we can execute a script to set up our database.
#!/bin/sh
# if database name not is empty
if [[ ! -z "$DB_NAME" -a "$DB_NAME" != " " ]]; then
MYSQLPASS=r00tmysql #as defined in the debian preseed file
mysqladmin create $DB_NAME -p$MYSQLPASS
mysql -u root -p$MYSQLPASS -e "GRANT ALL ON $DB_NAME.* TO $DB_NAME@localhost IDENTIFIED BY '$DB_NAME'"
mysql -u $DB_NAME -p$DB_NAME -e "CREATE TABLE user (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), email VARCHAR(50), password BINARY(64)) TYPE=innodb"
# mysql -u $DB_NAME -p$DB_NAME $DB_NAME < /data/sql/mydatabase_creationscript.sql
fiThis example script only creates a database if you provide a name. The database user and password use that same name. In the example, we create a table called user in the new database. You could also use a file containing SQL statements to create your database (see the last commented-out line). If you use a *.sql file, don't forget to upload it with the file provisioner. For example, put it in your webpage folder, which is uploaded to /data on your VM.
Last but not least, we'll configure Apache to serve the website in our data folder through a virtual host.
#!/bin/sh
echo "" >> /apache2/sites-available/mywebpage
echo " ServerName mywebpage.vm" >> /apache2/sites-available/mywebpage
echo " DocumentRoot /data" >> /apache2/sites-available/mywebpage
echo "" >> /apache2/sites-available/mywebpage
# cp /tmp/sites-available/mywebapge /etc/apache2/sites-available
/etc/init.d/apache2 restartIn the example above, I create a minimal virtual host and then restart Apache to load its configuration. You can reach the webpage at mywebpage.vm, so make sure you have this entry in your hosts file: 10.0.2.15 mywebpage.vm. Don't forget to configure port forwarding for your network adapter in VirtualBox. It is a NAT adapter; without port forwarding, you won't be able to access the VM from your host. You could copy a configuration file uploaded by the file provisioner or, even better, have the file provisioner upload it directly to the correct directory.
Assuming we're on Windows, we can execute the following from our command line to build the VM.
SET DB_NAME=MyAwesomeDB
packer build \
-var 'cpus=1' \
-var 'memory=512' \
packer-debian-x64-webserver.jsonThe result is a *.ova file that we can import into VirtualBox. Once you boot the VM, you'll see the results of all the provisioning steps. Now it's up to you to adapt the provisioning to your own project. You could set up a Git repository, configure the Git user, upload your SSH key, clone a repository, and so on. You can do all this with what you've learned in this post and the previous one. The coolest thing is that every developer can build a complete VM with their own username, password, and SSH keys supplied through variables and the file provisioner. You only need a few kilobytes of text files containing your template and provisioning scripts, instead of distributing gigabytes of VMs that developers still have to personalize. You can even store your Packer script in Git for version control.
I hope you enjoyed this two-part series and found enough inspiration to create your own Packer scripts. Don't forget to share them on GitHub so we can all benefit from your awesome work. Feel free to share your own creations in the comments. You can fork my full example on GitHub.
Marco Franssen
Configure SSH keys, Pageant, agent forwarding, and Git Bash on Windows to work with Linux VMs and Git without repeatedly entering key passphrases.
Marco Franssen
Use Gulp.js to automate PHP linting, PHPUnit tests, coding standards, documentation, and static analysis, with watch tasks for immediate feedback.
Marco Franssen
Build a reproducible Debian Wheezy VM with Packer and VirtualBox in this 2014 tutorial, using a JSON template and preseed file for unattended installation.
Marco Franssen
Write synchronous and asynchronous Node.js tests with Mocha, Chai assertions and Sinon spies, stubs and fake timers, then run them with Grunt.