Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to load a Vagrantfile config from within another Vagrantfile?

Vagrant Multi-Machine functionality seems pretty cool, however one thing that bothers me (or is isn't immediately apparent) is that there doesn't seem to be a good way to share configuration options between a "parent" Vagrantfile and "children" Vagrantfiles. Is there a way to effectively and maintainably share configuration options between the parent and it's children? An example might make this more clear.

Let's assume I've got a platform which is comprised of 3 apps/services: API, Web, and Worker.

Let's presume a directory structure of the following:

/some_platform
  /api
    # app code...
    Vagrantfile
  /web
    # app code...
    Vagrantfile
  /worker
    # app code...
    Vagrantfile
  Vagrantfile

Let's say /some_platform/api/Vagrantfile looks like:

Vagrant.configure("2") do |config|
  config.vm.box = "debian/jessie64"
end

Presumably the web and worker Vagrantfiles look similar.

Now, using the wonders of Multi-Machine I rely on Vagrant coordinate these VMs, and /some_platform/Vagrantfile looks like:

Vagrant.configure("2") do |config|
  config.vm.define "web" do |api|
    api.vm.box = "debian/jessie64"
  end

  config.vm.define "web" do |web|
    web.vm.box = "debian/jessie64"
  end

  config.vm.define "web" do |worker|
    worker.vm.box = "debian/jessie64"
  end
end

I realize this example is contrived, but it's easy to see how once you get more and more complex config declarations, it's annoying and hazardous to have that config duplicated in two places.

You might be wondering "Why does each project have it's own Vagrantfile?" Doing so provides a single source of truth for how the server that app runs on should be setup. I realize there are provisioners you can use (and I will use them), but you still have to declare a few other things outside of that and I want to keep that DRY so that I can either bring up a cluster of apps via Multi-Machine, or I can work on a single app and change it's VM/server setup.

What I'd really love is a way to merge other Vagrantfiles into a "parent" file.

Is that possible? Or am I crazy for trying? Any clever ideas on how to achieve this? I've mucked about with some yaml files and POROs to skate around this issue, but none of the hacks feel very satisfying.

like image 561
jamesconant Avatar asked Sep 28 '17 03:09

jamesconant


2 Answers

Good news!
You can in fact apply DRY principles in a Vagrantfile.

First: Create a file /some_platform/DRY_vagrant/Vagrantfile.sensible to hold some sensible defaults :

Vagrant.configure("2") do |config|
  # With the setting below, any vagrantfile VM without a 'config.vm.box' will 
  # automatically inherit "debian/jessie64"
  config.vm.box = "debian/jessie64"
end

Second: Create a file /some_platform/DRY_vagrant/Vagrantfile.worker for the 'worker' virtual machine :

Vagrant.configure("2") do |config|
  config.vm.define "worker" do |worker|
    # This 'worker' VM will not inherit "debian/jessie64". 
    # Instead, this VM will explicitly use "debian/stretch64"
    worker.vm.box = "debian/stretch64"
  end
end

Finally: Create a file /some_platform/Vagrantfile to tie it all together :

# Load Sensible Defaults
sensible_defaults_vagrantfile = '/some_platform/DRY_vagrant/Vagrantfile.sensible'
load sensible_defaults_vagrantfile if File.exists?(sensible_defaults_vagrantfile)

# Define the 'api' VM within the main Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.define "api" do |api|
    # This 'api' VM will automatically inherit the "debian/jessie64" which we
    # configured in Vagrantfile.sensible

    # Make customizations to the 'api' VM
    api.vm.hostname = "vm-debian-jessie64-api"
  end
end

# Load the 'worker' VM
worker_vm_vagrantfile = '/some_platform/DRY_vagrant/Vagrantfile.worker'
load worker_vm_vagrantfile if File.exists?(worker_vm_vagrantfile)

This approach can be used for almost any other vagrantfile config options. It is not limited to just the "config.vm.box" setting.

Hope this helped!

like image 198
Miron Veryanskiy Avatar answered Sep 22 '22 17:09

Miron Veryanskiy


There are 2 things you can look at (probably more, but those two comes to my mind)

  1. look at How to template Vagrantfile using Ruby? its an example how you can read the content of another file, Vagrantfile is just a ruby script so you can use all the power of ruby.

  2. vagrant has a concept of loading and merging, see from doc so if you wanted to do something anytime you run a vagrant command, you could create a Vagrantfile under your ~/.vagrant.d/ folder and it will always run

one drawback (or at least to pay attention) : the Vagrantfile is a ruby script that is evaluated each (and every time) a vagrant command is executed (up, status, halt ....)

like image 38
Frederic Henri Avatar answered Sep 20 '22 17:09

Frederic Henri