DEV Community

Edoardo Sanna
Edoardo Sanna

Posted on

Setting up Windows virtual test environments with Vagrant

Table of contents

Introduction: why and what

Recently we've been trying to implement a Continuous Delivery pipeline for our in-house .NET applications suite - you can't imagine how long and painful it is to install and configure a multi-tier application by hand πŸ˜“ ! Being a demanding task (a looot of scripting required!), we needed to be able to quickly provision and destroy several test environments (where 'environment', containers not being supported yet, is a whole Windows Virtual Machine).

The main idea was to create an easy, repeatable and trackable procedure. The best practices mentioned by Google's DevOps capabilities persuaded us to use Infrastructure as Code (IaC). IaC is basically code - therefore version-controlled with git - made of creation and configuration scripts to be fed to a proper virtualization engine.

Virtualbox, VMWare and Hyper-V all offer their own CLIs, but we'd like to use a provider-neutral tool. Hence we decided for Hashicorp's Vagrant, an open-source CLI-based VM manager already equipped with providers integrated with the main hypervisors.

This article is a quick start guide to create your first test environments using Vagrant.

Install and setup Vagrant

You can download Vagrant from the download page.

Once the installation is complete, you can verify it by opening your favorite shell (recommending Microsoft's open-source Windows Terminal), and running:

vagrant --version
Enter fullscreen mode Exit fullscreen mode

Creating a new project

Vagrant depends on a specific location on your workstation to store all the configuration files of your project and the virtualized disks: therefore, you need to create your own MyVagrant\ folder and cd into it.

Once you're in it, run

vagrant init --minimal
Enter fullscreen mode Exit fullscreen mode

to initialize your Vagrant project in the local folder. It will create a minimal configuration file called Vagrantfile.

The Vagrantfile is a Ruby-based script containing the instructions Vagrant will use to interact with the various virtualization providers in order to create the described infrastructure.

The minimal Vagrantfile created with the init command is pretty simple:

Vagrant.configure("2") do |config|
  config.vm.box = "base"
end
Enter fullscreen mode Exit fullscreen mode

The Vagrant.configure block sets a specific "configurator" version: all the next instructions will need to be included in this block.

The central command (config.vm.box) specifies the "box" used to create your Virtual Machine. A "box") is a VM image - pretty much similar to the concept of Docker images - exportable from already created VMs or downloadable from the community's Vagrant Cloud repository.

Your first Vagrant up

If you try to spin up your VM with the initial Vagrantfile, Vagrant will return an error because it doesn't know any "base" box. You may replace that value with any box name available: in our example, we will try to boot a Windows Server machine, therefore we'll use the publicly available Stefan Scherer's Windows Server 2019 box to have a 180-days trial version of Windows Server 2019 Eval with Desktop.

In case you don't have a local copy of the box, Vagrant will automatically download it from the Vagrant Cloud before creating the VM.

To boot your VM, just run:

vagrant up
Enter fullscreen mode Exit fullscreen mode

Since we didn't specify any virtualization provider, Vagrant will use Virtualbox as default: if you have a different hypervisor, make sure you edit the Vagrantfile with the appropriate settings. Remember then to add the --provider=PROVIDER_NAME (where PROVIDER_NAME is vmware_fusion, hyperv, vmware_desktop, docker, etc) to your vagrant up command.

Vagrant will take some minutes to download the image and boot up the VM, depending on the chosen OS. If your output includes:

Timed out while waiting for the machine to boot. This means that
Vagrant was unable to communicate with the guest machine within
the configured ("config.vm.boot_timeout" value) time period.
Enter fullscreen mode Exit fullscreen mode

The VM has correctly started on Virtualbox nonetheless, but Vagrant won't be able to communicate with it and proceed with the following instructions, such as provisioning software. Therefore, we need to add in the first part of the Vagrantfile the following properties about the communicator (i.e. WinRM) that Vagrant will use to connect to our Windows operating system:

  # Additional parameters to communicate with Windows
  config.vm.boot_timeout = 60
  config.vm.communicator = "winrm"
  config.winrm.port = 55985
Enter fullscreen mode Exit fullscreen mode

By opening the Virtualbox GUI you may find your new VM in the list: notice that, with a Vagrantfile as minimal as the one we provided, the Virtual Machine will have a combination of the provider's and Vagrant's default properties, such as

  • The default VM name chosen by Vagrant follows the format PROJECT-FOLDER_default_TIMESTAMP; you may want to customize it with a more descriptive hostname, such as web or db.
==> default: Setting the name of the VM: MyVagrant_default_1629122422277_92221
Enter fullscreen mode Exit fullscreen mode
  • The VM will have a main network adapter ("Adapter 1") connected via NAT to the host:
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
Enter fullscreen mode Exit fullscreen mode
  • The network TCP port 22 on the host is mapped to TCP port 2222 on the guest:
==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
Enter fullscreen mode Exit fullscreen mode

We'll see how to change the properties above in the following section.

Customizing your VM

Customizing our newborn Virtual Machine requires a little bit of additional configuration in the Vagrantfile: some settings needs to be added in the config.vm namespace of the Vagrantfile.

All the code described below must be added inside the Vagrant.configure("2") do |config| section.

  • Some properties can be changed by configuring the provider, such as the VM's name or the hypervisor's GUI. Notice that some settings don't have a specific shortcut and they will need the customize property:
# Customization
config.vm.provider "virtualbox" do |v|
  v.name = "my_vm"    # Sets the new VM's name
  v.gui = true        # Enables the hypervisor's GUI
  v.memory = 2048     # Sets the VM's RAM
  v.customize ["modifyvm", :id, "--draganddrop", "hosttoguest"] # Enables drag-and-drop between host and guest
  v.customize ["modifyvm", :id, "--clipboard", "bidirectional"] # Enables a bidirectional clipboard between host and guest
end
Enter fullscreen mode Exit fullscreen mode
  • Additional ports can be mapped between host and guest with the config.vm.network settings (additional details on networking can be found here)
  # Customization
  config.vm.network :forwarded_port, guest: 80, host: 8080, id: "http"  # Map host's port 8080 to guest's port 80
Enter fullscreen mode Exit fullscreen mode
  • By default, Vagrant will share (or "sync") your project directory - i.e. the directory with the Vagrantfile - to /vagrant on the guest. You can set additional synced folders with:
  # Customization
  config.vm.synced_folder "app/wwwroot", "src/"  # Map the host's "src/" folder to the guest's "/app/wwwroot"
Enter fullscreen mode Exit fullscreen mode

And many more!

After each change, remember to run vagrant validate to check the Vagrantfile for any syntax error.

If the validation is successful, run vagrant reload to reboot your VM and apply all the changes.

Provisioning

So now we have a ready VM, but what if we need to also install software or change the configuration during the boot process? This practice is called provisioning and it can be attached to the vagrant up command.

There are several provisioners integrated with Vagrant, such as Ansible, Puppet, Docker and even simply uploading files to the VM.

In our case, let's try to set up a development environment by installing a package manager (in our case Chocolatey) and then an IDE (what's better than Visual Studio Code?), and let's try to do it with the shell provisioner, i.e. uploading and running a shell script.

Create a scripts/ folder in your project and add the InstallChocolatey.ps1 PowerShell script (you may find it on the official Chocolatey install page, please be sure to read Chocolatey's disclaimer about downloading scripts from the Internet ❗) in it:

In the config block of your Vagrantfile, add

  # Provisioning
  config.vm.provision "shell", path: "scripts/InstallChocolatey.ps1"  # Run the external script to install Chocolatey
  config.vm.provision "shell", inline: "choco install vscode --yes"   # Run the inline script to install VSCode via Chocolatey
Enter fullscreen mode Exit fullscreen mode

Now, if you vagrant reload your VM, the provisioning will be performed at the end of the boot. The first shell script (called from an external file whose path is provided) will download and install Chocolatey, the second inline script will force the download and installation of the Visual Studio Code's choco package from the Chocolatey community repository.

Conclusion

In just a few minutes, we were able to spin up a Virtual Machine with our desired operating system and with some software installed (a package manager and an IDE) and now we're ready to write code and start some tests! πŸŽ‰ 🎊

Our new virtual test environment!

Plus, our IaC files (i.e. the Vagrantfile and the corresponding PowerShell scripts) now can be stored in a git repository, thus taking advantage of all the source control features: basically we have a versioned infrastructure, repeatable and exportable in any settings.

Here you can find the code mentioned in this post.

See you next time! πŸ‘‹

Top comments (1)

Collapse
 
vaggeliskls profile image
vaggeliskls

You can check this out: github.com/vaggeliskls/windows-in-...