Last active
October 18, 2016 13:15
-
-
Save andkirby/dd66d8fadcd8050b381a1cfd2a5f801e to your computer and use it in GitHub Desktop.
This file can help to set forwarded ports in VirtualBox VM. It solves the problem described in the issue https://github.com/mitchellh/vagrant/issues/7905
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'yaml' | |
require 'digest/md5' | |
## | |
# Module VBoxDirectPorts | |
# It add ports forwarding in VirtualBox VM | |
# It can generate ports based upon hostname | |
# | |
# Configuration files. | |
# (.dist) means there is should be distributive file by default with default configuration | |
# | |
# - pf-config.yml(.dist) | |
# This file contains custom user configuration. E.g.: | |
# my.hostname.cc: | |
# - host_ip: 127.0.0.1 | |
# guest_ip: 0.0.0.0 | |
# host: 80 | |
# guest: 80 | |
# | |
# - pf-hosts | |
# This file contains user hostnames. | |
# System will generate guest ports and host IP addresses for them in | |
# file pf-config-autogenerated.yml. | |
# Default guest IP address is 0.0.0.0 | |
# | |
# - pf-host-ports(.dist) | |
# Ports list which will be used on the host side during autogeneration. | |
# Guest ports will be generated automatically. | |
# | |
# Guest ports generation algorithm in | |
# Vagrant::VBoxDirectPorts::host_ports | |
# Host IP addresses generation algorithm in | |
# Vagrant::VBoxDirectPorts::host_ip | |
# | |
# Using in Vagrantfile: | |
# # Set up port forwarding in non-provisioning mode | |
# # http://stackoverflow.com/questions/24855635/check-if-vagrant-provisioning-has-been-done | |
# if File.file?('.vagrant/machines/default/virtualbox/action_provision') | |
# require_relative 'vbox_direct_ports.rb' | |
# | |
# # Enable ports output | |
# # Vagrant::VBoxDirectPorts::verbosity = true | |
# # Generate always new file pf-config-autogenerated.yml (any vagrant command here) | |
# # Vagrant::VBoxDirectPorts::always_generate_config = true | |
# # Set direct_vm_update to update port forwarding list with using Vagrant native functionality | |
# # Vagrant::VBoxDirectPorts::direct_vm_update = false | |
# | |
# # Make ports forwarding | |
# Vagrant::VBoxDirectPorts::forward_ports config | |
# end | |
# | |
# GitHub Gist: https://gist.github.com/andkirby/dd66d8fadcd8050b381a1cfd2a5f801e | |
# | |
module Vagrant | |
module VBoxDirectPorts | |
module_function | |
# region Properties | |
## | |
# Enable verbosity | |
# | |
@@verbosity = false | |
## | |
# Generate config always in file pf-config-autogenerated.yml | |
# | |
@@always_generate_config = false | |
## | |
# Status of updating port forwarding list in VirtualBox VM directly | |
# | |
@@direct_update = true | |
## | |
# Set status of output | |
# | |
def verbosity=(flag) | |
@@verbosity = flag | |
end | |
## | |
# Set status of output | |
# | |
def always_generate_config=(flag) | |
@@always_generate_config = flag | |
end | |
## | |
# Status of updating port forwarding list in VirtualBox VM directly | |
# If FALSE it will use Vagrant functionality | |
# | |
def direct_vm_update=(flag) | |
@@direct_update = flag | |
end | |
# endregion | |
# region Main methods | |
## | |
# Set up port forwarding from configuration file | |
# | |
# pf-config.yml or pf-config.yml.dist | |
# | |
def forward_ports(config) | |
forward_hosts_ports config | |
forward_custom_ports config | |
end | |
## | |
# Forward ports by user config file | |
# | |
def forward_custom_ports(config) | |
update_hosts_file load_config(port_forward_custom_file) | |
config_forward_ports config, file: port_forward_custom_file | |
self | |
end | |
## | |
# Forward host ports from file pf-hosts (or pf-hosts.dist) | |
# | |
def forward_hosts_ports(config) | |
if @@always_generate_config === false && File.exist?(autogenerated_config_file) | |
return config_forward_ports config, file: autogenerated_config_file | |
end | |
return self until File.readable? current_dir + '/pf-hosts' | |
generated_config = {} | |
(File.read(current_dir + '/pf-hosts') + $/).each_line do |line| | |
hostname = line.strip! | |
# Ignore comments, empty strings | |
next if hostname == nil || hostname.chars[0] == '#' | |
generated_config[hostname] = auto_forward_ports_config(hostname: hostname, config: config) | |
end | |
# Generate ports forwarding from written file | |
config_forward_ports config, file: write_autogenerated_config(generated_config) | |
update_hosts_file generated_config | |
self | |
end | |
# endregion | |
protected | |
module_function | |
## | |
# It will return path to pf-config.yml if exist | |
# or pf-config.yml.dist instead | |
# | |
def port_forward_custom_file | |
path = current_dir | |
path + '/pf-config.yml' + | |
(File.exist?(path + '/pf-config.yml') ? '' : '.dist') | |
end | |
# region Ports Auto-generation | |
## | |
# Write config into YAML file | |
# | |
def write_autogenerated_config(ports_config) | |
file = autogenerated_config_file | |
fw = File.open(file, 'w') | |
fw.write(ports_config.to_yaml) | |
fw.close | |
file | |
end | |
def autogenerated_config_file | |
current_dir + '/pf-config-autogenerated.yml' | |
end | |
## | |
# Generate configuration automatically with guest ports and host IP address for a hostname | |
# | |
def auto_forward_ports_config(hostname:, config:) | |
configs = [] | |
ip_host = host_ip(hostname) | |
host_ports(hostname).each { |host_port, guest_port| | |
configs << { | |
host: host_port, guest: guest_port, | |
host_ip: ip_host, guest_ip: default_guest_ip | |
} | |
} | |
configs | |
end | |
## | |
# Get hostname ports map which should be forwarded from a host OS to a guest OS | |
# | |
def host_ports(hostname) | |
base_port = guest_base_port(hostname).to_i | |
ports_map = {} | |
path = current_dir | |
file = path + '/pf-host-ports' + (File.exist?(path + '/pf-host-ports') ? '' : '.dist') | |
index = 0 | |
File.open(file, 'r') do |f| | |
f.each_line do |port| | |
# Ignore comments, empty strings | |
next if port == nil || port.chars[0] == '#' | |
ports_map[port.to_i] = base_port + port.to_i | |
index += 1 | |
end | |
end | |
ports_map | |
end | |
## | |
# Generate guest base port based upon a hostname | |
# | |
# NOTE: This algorithm must reflected in centos7-nginx-php7/create-container.sh file | |
# or used the same container ports | |
# | |
def guest_base_port(hostname) | |
hash = Digest::SHA1.hexdigest hostname | |
1024 + hash.chars[0].ord + hash.chars[1].ord + hash.chars[2].ord + hash.chars[3].ord + | |
hash.chars[4].ord + hash.chars[5].ord | |
end | |
## | |
# Generate local IP address based upon a hostname | |
# | |
def host_ip(hostname) | |
hash = Digest::SHA1.hexdigest hostname | |
'127.' + (hash_symbol_int(hash.chars[0]) + hash_symbol_int(hash.chars[1])).to_s + '.' + | |
(hash_symbol_int(hash.chars[2]).ord + hash_symbol_int(hash.chars[3])).to_s + '.' + | |
(hash_symbol_int(hash.chars[4]).ord + hash_symbol_int(hash.chars[5])).to_s | |
end | |
# endregion | |
# region Apply ports forwarding by YAML file | |
## | |
# Forward ports by config file | |
# | |
def config_forward_ports(config, file:) | |
puts "Reading #{file}..." if @@verbosity | |
load_config(file).each { | group_key, group_net_conf | | |
puts "===== #{group_key.green} =====" if @@verbosity | |
puts 'Ports forwarding.' if @@verbosity | |
group_net_conf.each { |net_conf| | |
# convert keys to hash keys | |
net_conf_hashed = Hash.new | |
net_conf.each { |key, value| net_conf_hashed[key.to_sym] = value } | |
puts "#{net_conf_hashed[:host_ip]}:#{net_conf_hashed[:host]} => " + | |
"#{net_conf_hashed[:guest_ip]}:#{net_conf_hashed[:guest]}" if @@verbosity | |
if @@direct_update | |
## | |
# Due to reason there is no ability to use the same ports Vagrant command "config.vm.network" cannot be used | |
# https://github.com/mitchellh/vagrant/issues/7905 | |
# | |
add_vbox_nat_port_forwarding(config, net_conf_hashed) | |
else | |
# Add port forwarding via Vagrant | |
config.vm.network 'forwarded_port', net_conf_hashed | |
end | |
} | |
puts '===============' if @@verbosity | |
} | |
end | |
## | |
# Make direct changes in VM | |
# It works with NAT only | |
# | |
def add_vbox_nat_port_forwarding(config, host:, guest:, host_ip: '', guest_ip: '') | |
config.vm.provider :virtualbox do |vb| | |
value = "tcp,#{host_ip},#{host},#{guest_ip},#{guest}" | |
value = Digest::SHA1.hexdigest(value) + ',' + value # add unique ID | |
vb.customize ['modifyvm', :id, '--natpf1', value] | |
end | |
end | |
## | |
# Load YAML configuration | |
# | |
def load_config(file) | |
YAML.load_file(file) || {} | |
end | |
# endregion | |
# region Common | |
## | |
# Get current directory path | |
# | |
def current_dir | |
File.dirname(File.expand_path(__FILE__)) | |
end | |
## | |
# Default guest IP address | |
# | |
def default_guest_ip | |
'0.0.0.0' | |
end | |
## | |
# It will make numbers lower | |
# because there is the range 0..9 and a..f | |
# The smallest number is 48 ("0".ord) | |
# | |
def hash_symbol_int(char) | |
char.ord - 47 # 47 means "0".ord - 1 | |
end | |
## | |
# Added records in hosts file | |
# | |
def update_hosts_file(config) | |
until File.writable? hosts_file | |
puts "Cannot update etc/hosts file. It's not writable".red if @@verbosity | |
return | |
end | |
hosts_content = File.read(hosts_file).strip | |
updated = false | |
config.each { |hostname, item| | |
next if item[0][:host_ip].nil? && item[0]['host_ip'].nil? | |
host_ip = item[0][:host_ip] || item[0]['host_ip'] | |
# 20 symbols space for IP address | |
entry = host_ip + ' ' * (20 - host_ip.length) + hostname | |
# ignore exist entries | |
if hosts_content.scan(/^#{host_ip}[ \t]+#{hostname}$/).empty? | |
if hosts_content.scan(/^(\d{1,3}\.?){4}[ \t]+#{hostname}$/).empty? | |
hosts_content += $/ + entry | |
else | |
hosts_content = hosts_content.gsub!(/^(\d{1,3}\.?){4}[ \t]+#{hostname}$/, entry) | |
end | |
updated = true | |
end | |
} | |
# Apply changed hosts file content | |
if updated | |
puts 'etc/hosts file has been updated.'.yellow | |
fw = File.open(hosts_file, 'w') | |
fw.write hosts_content + $/ | |
fw.close | |
end | |
end | |
## | |
# Get path to hosts file | |
# | |
def hosts_file | |
ENV['OS'] == 'Windows_NT' ? 'c:/Windows/System32/drivers/etc/hosts' : '/etc/hosts' | |
end | |
#endregion | |
end | |
end | |
# String colorization | |
class String | |
# colorization | |
def colorize(color_code) | |
"\e[#{color_code}m#{self}\e[0m" | |
end | |
def red | |
colorize(31) | |
end | |
def green | |
colorize(32) | |
end | |
def yellow | |
colorize(33) | |
end | |
def blue | |
colorize(34) | |
end | |
def pink | |
colorize(35) | |
end | |
def light_blue | |
colorize(36) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment