Bolt + Vagrant

Back to Listing

Hanover, MD, 26 April 2021


Vagrant is a great tool from Hashicorp (makers of tools such as Terraform and Vault) for prototyping infrastructure for applications. It will let you start up one or more VMs of any type with any arbitrary configuration.

Bolt is a tool from Puppet (makers of, well, Puppet) that makes it easy to orchestrate actions across systems using any combination of commands, scripts, or even Puppet code. One of the best features of Bolt is its inventory plugins. In this case, we’re going to examine the bolt_vagrant inventory plugin which lets us use Bolt against Vagrant VMs without hard-coding IP addresses or, in some cases, even names. Best of all, you can test Bolt tasks and plans in a Vagrant environment, then move on to real systems by simply changing your inventory.

Getting Started

First, you’ll need Bolt and Vagrant installed. In addition, you’ll need a Vagrant provider like VirtualBox.

Building VMs

Let’s start out by building a couple of VMs. Create an empty directory to work from and cd into it. You can run vagrant init centos/8 to create a Vagrantfile with a CentOS 8 VM configured. From there, you can edit the Vagrantfile to add additional VMs. Here’s an example that you can use:

Vagrant.configure("2") do |config|
  config.vm.define "centos" do |centos|
    centos.vm.box = "centos/8"
    centos.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end

  config.vm.define "windows" do |windows|
    windows.vm.box = "gusztavvargadr/windows-server"
    windows.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end
end

This configuration gives us a CentOS 8 VM and a Windows Server 2019 VM, both configured for 1GB RAM.

To start the VMs, run vagrant up.

Setting up Bolt

Start out by running bolt project init. If the name of the directory you created isn’t acceptable as a Bolt project name (which has the same rules as Puppet class names), you can also supply a Bolt-friendly project name:

$ bolt project init demo

This creates two files, bolt-project.yaml and inventory.yaml. More on those later.

Adding the bolt_vagrant module

Next we need to add the bolt_vagrant module to our project with bolt module add dylanratcliffe-bolt_vagrant. This updates bolt-project.yaml, creates a Bolt-managed Puppetfile, and installs the module (plus any dependencies) in the .modules directory.

The bolt_vagrant module adds a task that can be used to generate inventory for Bolt from the running Vagrant configuration.

To enable the bolt_vagrant inventory, we need to add the following to inventory.yaml:

targets:
  - _plugin: task
    task: bolt_vagrant::targets

To demonstrate that this is working, run bolt inventory show -t all. You should see centos and windows in the output.

Running ad-hoc commands

Let’s run a command across all of our VMs to start.

$ bolt command run hostname -t all

(Note that we’re cheating a little here by running the hostname command, which works on both Linux and Windows.)

Running scripts

We can also run arbitrary scripts against our VMs.

$ echo hostname > myscript.ps1
$ bolt script run myscript.ps1 -t all

(Now we’re really cheating here. On Windows, the ps1 extension will tell Bolt to use PowerShell to run the script. On Linux, the extension is ignored and the script is run by the default script interpreter, /bin/sh.)

Running tasks

Bolt tasks are scripts with some associated metadata that is used to specify script arguments, etc.

Several tasks are shipped with Bolt. You can see the current list with bolt task show.

To add additional tasks, either install additional Puppet modules that contain tasks or create a directory named tasks and drop in the necessary files.

For example:

tasks/hostname.json:

{
  "puppet_task_version": 1,
  "supports_noop": false,
  "description": "Run hostname",
  "implementations": [
    { "name": "hostname.sh", "requirements": ["shell"] },
    { "name": "hostname.ps1", "requirements": ["powershell"] }
  ],
  "parameters": {
  }
}

tasks/hostname.sh:

#!/bin/sh

hostname

tasks/hostname.ps1:

hostname

Once those files are in place, you can run the task on all nodes with the following command:

$ bolt task run demo::hostname -t all

Plans

Bolt supports two different kinds of plans: plans written in YAML, and plans written in the Puppet language.

Plans can run a set of commands, scripts, tasks, or other plans in a set sequence on a list of targets.

You can drop plan files in a directory named plans in the Bolt project, but there is also a bolt command to generate a new plan template.

$ bolt plan new demo::hostname1

That command will create a YAML plan. To create a Puppet-language plan, add --pp to the command.

$ bolt plan new demo::hostname2 --pp

For demo::hostname1, we can modify plans/hostname1.yaml to look like this:

description: Run hostname via a YAML plan
parameters:
  targets:
    type: TargetSpec
    description: A list of targets to run actions on
    default: all
steps:
  - message: Hello from demo::hostname1
  - name: command_step
    command: hostname
    targets: $targets
return: $command_step

To get the same functionality out of demo::hostname2, modify plans/hostname2.pp to look like this:

plan demo::hostname2 (
  TargetSpec $targets = 'all',
) {
  out::message('Hello from demo::hostname2')
  $command_result = run_command('hostname', $targets)
  return $command_result
}

You can run these plans with

$ bolt plan run demo::hostname1

or

$ bolt plan run demo::hostname2

Note that we do not need to supply a list of targets for either of these plans because they default to a target list of all.

Using a Bolt Plan for Provisioning

With a couple of minor tweaks to our Vagrantfile, we can use a Bolt plan for provisioning.

First, we need to enable an experimental Vagrant feature - typed_triggers. We can do this by adding the following line to the beginning of our Vagrantfile:

ENV['VAGRANT_EXPERIMENTAL'] = 'typed_triggers'

Next, we want to make sure that any Puppet modules that Bolt needs (like bolt_vagrant) are installed before we try to use them. Add the following to the Vagrant.configure(2) block:

  config.trigger.before [:up, :provision, :reload], type: :command do |trigger|
    trigger.info = 'Initializing bolt'
    trigger.run = { inline: 'bolt module install' }
  end

Finally, we want to run our plan after all of the VMs have started:

  config.trigger.after [:up, :provision, :reload], type: :command do |trigger|
    trigger.info = 'Running bolt plan'
    trigger.run = { inline: 'bolt plan run demo' }
  end

Examples

You can find examples of this pattern on GitHub.

Click here to learn more about how Onyx Point, LLC's professional services and development teams can help you.

Steven is a consultant and trainer for Onyx Point, focusing on Puppet, compliance automation, and all things DevOps.

At Onyx Point, our engineers focus on Security, System Administration, Automation, Dataflow, and DevOps consulting for government and commercial clients. We offer professional services for Puppet, RedHat, SIMP, NiFi, GitLab, and the other solutions in place that keep your systems running securely and efficiently. We offer Open Source Software support and Engineering and Consulting services through GSA IT Schedule 70. As Open Source contributors and advocates, we encourage the use of FOSS products in Government as part of an overarching IT Efficiencies plan to reduce ongoing IT expenditures attributed to software licensing. Our support and contributions to Open Source, are just one of our many guiding principles

  • Customer First.
  • Security in All We Do.
  • Pursue Innovation with Integrity.
  • Communicate Openly and Respectfully.
  • Offer Your Talents, and Appreciate the Talents of Others

bolt, vagrant, technical

Share this story

We work with these Technologies + Partners

puppet
gitlab
simp
beaker
redhat
AFCEA
GitHub
FOSSFeb