Skip to content

Commit

Permalink
Generic vagrant build (#6)
Browse files Browse the repository at this point in the history
* triggers working

* Split out classes. Add script provsioner

* Package installer, host file setup

* commentsd

* quit on empty package list

* Refactor to support bridging

* Bridged network for VB

* Renaname library code

* RAM check

* Edit comment

* Improving hosts file update

* Rename script

* Handle multiple IF with same IP (WMware)

* git attributes

* updated

* WMware bridge

* fix

* Restructure

* Use bento/centos9 for arm

* Chomp ip output

* Fix

* Support pre-provision
e.g. on bento centos we need EPEL
On boxomatic it is already present

* Preinstall JQ/YQ

* Fix amd64 yq

* Update comment

* Update README

* adjust

* Update provision instructions
  • Loading branch information
fireflycons authored Aug 3, 2024
1 parent dbb8c2e commit acb5c4d
Show file tree
Hide file tree
Showing 9 changed files with 822 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.rb text eol=lf
*.sh text eol=lf
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.docx
*.tmp
*/**/*.hidden.*
.vagrant/
.vagrant/
temp/
30 changes: 30 additions & 0 deletions vagrant/generic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generic Vagrant Template

This is a generic template for building cross-platform Vagrant VM builds. It is intended to be copied to a specific project, then edited there for that project's requirements.

It should be very simple affair to customize this for a given project and more or less limited to

1. Define the network type by editing [NETWORK_SETTINGS](./Vagrantfile#L9).
* `NAT` - run VMs in a private network with the given [IP prefix](./Vagrantfile#L11) (the default is known to work with all 3 platforms) and [IP of the first machine](./Vagrantfile#L12) in the VM list. Subsequent machines will have contiguous IP addresses.
* `BRIDGE` - run VMs on host machine's LAN. Useful for Kubernetes deployments as it's not necessary to port-forward nodeports.
1. Define the VMs required by editing the [VIRTUAL_MACHINES](./Vagrantfile#L16) list and setting the properties for each machine in the list.
* All VMs will have `jq` and `yq` installed by default. Additional packages can be added by populating the [packages](./Vagrantfile#L22) list for each VM. Package names are specific to the VM's package manager (CentOS - `yum`, Ubuntu - `apt`).
1. Additional customizations such as port-forwards or shell provision steps can be added inside the [Hypervisor block](./Vagrantfile#L42-L45), using Hypervisor class's methods:

* `port_forward`

Forward 8080 on host to 80 on guest

```ruby
hv.port_forward host: 8080, guest: 80
```

Note that the SSH port is automatically forwarded by Vagrant so you don't have to.
* `provision_script`
Run the given script which should be placed in the [linux](./linux/) subdirectory in the guest. The shell script can be passed optional arguments. Note that the same script will be run on *all* guests, so ensure it is [distro aware](./linux/setup_host.sh#L11-L13) and idempotent. Host name should be set already as long as the script comes after `hv.deploy` so that can also be switched on
```ruby
hv.provision_script [arg1 , arg2, ...argn,] script: "my_script.sh"
```
74 changes: 74 additions & 0 deletions vagrant/generic/Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

require_relative "./utils"
require_relative "./hypervisor"
require_relative "./host"

# Define network that the virtual machines will join.
NETWORK_SETTINGS = {
network_type: "BRIDGE", # NAT = use a private network; BRIDGE = use addresses from your broadband router's network.
private_network: "192.168.56.", # Network prefix for VM network if using NAT mode. Ignored for BRIDGE.
ip_start: 11, # First IP to use for VMs on private network if using NAT mode. Ignored for BRIDGE.
}

# Define list of virtual machines to be created here.
VIRTUAL_MACHINES = [
{
name: "controller", # Name (and hostname) for the VM
cpu: 4, # Number of vCPUs to assign it
memory: 2048, # RAM in MB to assign it
box: Hypervisor.centos, # Operating system to run (centos or ubuntu)
#packages: ["some-package"], # Packages to be installed by OS's package manager
},
{
name: "target1",
cpu: 2,
memory: 2048,
box: Hypervisor.ubuntu,
},
]

# Deploy the VMs
Vagrant.configure("2") do |config|
config.vm.box_check_update = false

# For each machine defined in VIRTUAL_MACHINES above
VIRTUAL_MACHINES.each_with_index do |vm, index|
config.vm.define vm[:name] do |node|
# Set the "box", i.e. operating system for guest
node.vm.box = vm[:box].box
# Get hypervisor support for the host OS
Hypervisor.get node: node do |hv|
# Deploy the VM
hv.deploy vm: vm, network: NETWORK_SETTINGS, index: index
end
end
end

# Tasks to perform before any VMs are created
config_shown = false
config.trigger.before :up do |trigger|
trigger.info = "Pre-start trigger"
trigger.ruby do |env, machine|
if !config_shown
host = Host.get()
show_system_info(trigger, host)
validate_configuration(trigger, host, VIRTUAL_MACHINES)
config_shown = true
end
end
end

# Tasks to perform after all VMs are created
vm_count = 0
config.trigger.after :up do |trigger|
trigger.info = "Post-provision"
trigger.ruby do |env, machine|
vm_count += 1
if vm_count == env.machine_names.length()
post_provision(env)
end
end
end
end
261 changes: 261 additions & 0 deletions vagrant/generic/host.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
##########################################################################
#
# Host is an abstraction for the host system we are building VMs on.
#
##########################################################################
class Host
CPU_NAME = 0
CPU_COUNT = 1
RAM = 2
OS_NAME = 3
HV_EXISTS = 4

@@specs = nil

def self.get()
if OS.apple_silicon?
return AppleSiliconHost.new
elsif OS.mac?
return IntelMacHost.new
elsif OS.windows?
return WindowsHost.new
elsif OS.linux_arm?
return ArmLinuxHost.new
elsif OS.linux?
return IntelLinuxHost.new
else
raise DetectionError.new "FATAL - Cannot determine your operating system"
end
end

def get_system_name()
return ""
end

def gateway_addresses()
""
end
end

class WindowsHost < Host
include Powershell

def physical_ram_gb()
if not @@specs
self.get_sysinfo()
end
return @@specs[RAM].to_i
end

def cpu_count()
if not @@specs
self.get_sysinfo()
end
return @@specs[CPU_COUNT].to_i
end

def cpu_name()
if not @@specs
self.get_sysinfo()
end
return @@specs[CPU_NAME]
end

def os_name()
if not @@specs
self.get_sysinfo()
end
return @@specs[OS_NAME]
end

def hypervisor_name()
return "VirtualBox"
end

def hypervisor_exists?
if not @@specs
self.get_sysinfo()
end
@@specs[HV_EXISTS] == "1"
end

private

def get_sysinfo()
ps = <<~PS
$p = Get-CimInstance Win32_Processor | Select-Object NumberOfLogicalProcessors, Name
$m = (Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum / 1GB
$o = (Get-CimInstance Win32_OperatingSystem | select -ExpandProperty Name).split('|') | Select-Object -First 1
$hv = 0
try {
$d = Get-ItemProperty hklm:Software/Oracle/VirtualBox -PSProperty InstallDir -ErrorAction Stop
if (Test-Path (Join-Path $d.InstallDir vboxmanage.exe)) {
$hv = 1
}
}
catch {}
Write-Host "$($p.Name)/$($p.NumberOfLogicalProcessors)/$m/$o/$hv"
PS
@@specs = self.powershell(ps).split("/")
end
end

class MacHost < Host
def physical_ram_gb()
if not @@specs
self.get_sysinfo()
end
return @@specs[RAM].to_i / 1073741824
end

def cpu_count()
if not @@specs
self.get_sysinfo()
end
return @@specs[CPU_COUNT].to_i
end

def cpu_name()
if not @@specs
self.get_sysinfo()
end
return @@specs[CPU_NAME]
end

def os_name()
if not @@specs
self.get_sysinfo()
end
return @@specs[OS_NAME]
end

def get_system_name()
return "Mac"
end

def gateway_addresses()
cmd = "netstat -rn -f inet | egrep '^default.*UGS' | awk '{print $2}' | xargs echo | tr ' ' ','"
%x{ #{cmd} }.chomp()
end

private

def get_sysinfo()
sh = <<~SHEL
pc=$(sysctl -n "hw.ncpu")
m=$(sysctl -n "hw.memsize")
pn=$(sysctl -n "machdep.cpu.brand_string")
os_name=$(awk '/SOFTWARE LICENSE AGREEMENT FOR macOS/' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | awk -F 'macOS ' '{print $NF}' | awk '{print substr($0, 0, length($0)-1)}')
os_ver=$(sw_vers | awk '/ProductVersion/ { print $2 }')
echo "${pn}/${pc}/${m}/${os_name} ${os_ver}"
SHEL
@@specs = %x{ #{sh} }.chomp().split("/")
end
end

class IntelMacHost < MacHost
def hypervisor_name()
return "VirtualBox"
end

def hypervisor_exists?
Dir.exists?("/Applications/VirtualBox.app")
end
end

class AppleSiliconHost < MacHost
def hypervisor_name()
return "VMware Fusion"
end

def hypervisor_exists?
Dir.exists?("/Applications/VMware Fusion.app")
end
end

class LinuxHost < Host
def physical_ram_gb()
if not @@specs
self.get_sysinfo()
end
return @@specs[RAM].to_i / 1048576
end

def cpu_count()
if not @@specs
self.get_sysinfo()
end
return @@specs[CPU_COUNT].to_i
end

def cpu_name()
if not @@specs
self.get_sysinfo()
end
return @@specs[CPU_NAME]
end

def os_name()
if not @@specs
self.get_sysinfo()
end
return @@specs[OS_NAME]
end

private

def get_sysinfo
sh = <<~SHEL
m=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
pc=$(nproc)
pn=$(cat /proc/cpuinfo | grep "model name" | uniq | cut -d ':' -f 2 | sed "s/^[ ]*//")
o=$(source /etc/os-release && echo -n "$NAME $VERSION")
echo "${pn}/${pc}/${m}/${o}"
SHEL
@@specs = %x{ #{sh} }.chomp().split("/")
end
end

class IntelLinuxHost < LinuxHost
def hypervisor_name()
return "VirtualBox"
end

def hypervisor_exists?
sh = <<~SHEL
if command -v VBoxManage > /dev/null
then
echo -n "1"
else
echo -n "0"
fi
SHEL
%x{ #{sh} }.chomp() == "1"
end

def get_system_name()
return "Intel"
end
end

class ArmLinuxHost < LinuxHost
def hypervisor_name()
return "VMware Workstation"
end

def hypervisor_exists?
sh = <<~SHEL
if command -v vmrun > /dev/null
then
echo -n "1"
else
echo -n "0"
fi
SHEL
%x{ #{sh} }.chomp() == "1"
end

def get_system_name()
return "ARM"
end
end
Loading

0 comments on commit acb5c4d

Please sign in to comment.