The Vagrant Plugin is an extension that allows you to add functionality to Vagrant.
Recently, mutagen is used for file synchronization when starting a VM, and the mutagen project is linked with starting / stopping the VM [vagrant-mutagen plugin](https://github.com/dasginganinja/ I was thinking of using vagrant-mutagen).
However, it didn't work properly on Windows, and I was using the modified version with PR, but it still starts the VM. I was in trouble because I had to elevate my administrator privileges twice each time.
I decided to fork and create a Vagrant Plugin at will, and I decided to summarize what I investigated at that time.
The source code of the forked plugin can be found at here, so please refer to it as an example.
vagrant-XXX
by convention.For the outline of how to make it, I referred to the one described below.
https://www.vagrantup.com/docs/plugins/development-basics
my-vagrant-plugin
is the name of the plugin you create.
source "https://rubygems.org"
group :development do
gem "vagrant", git: "https://github.com/hashicorp/vagrant.git"
end
group :plugins do
gem "my-vagrant-plugin", path: "."
end
It requires Vagrant for development, and in development mode the vagrant plugin
command doesn't work and instead the gem specified in the plugins group is loaded.
I didn't usually have Ruby development in the Windows environment, so when developing the Vagrant plugin, I used the one installed on Windows, and each time I gem build
the Plugin and uninstall / install it with vagrant. ..
gem build <SOME_PLUGIN_NAME> .gemspec
to save <SOME_PLUGIN_NAME> -X.Y.Z.gem
vagrant plugin install <PATH_TO_SOME_PLUGIN> / <SOME_PLUGIN_NAME>-X.Y.Z.gem
will install the Plugin(I thought I wouldn't make a big fix, but looking back, I think it was easier to get Vagrant running in development mode.)
Define the class that is the main body of Plugin, and specify the component class name such as Config, Provisioner in the class.
You need to require the Plugin class from the file that corresponds to the name listed in gemspec.
lib/<SOME_PLUGIN_NAME>/plugin.rb
class MyPlugin < Vagrant.plugin("2")
name "My Plugin"
command "run-my-plugin" do
require_relative "command"
Command
end
provisioner "my-provisioner" do
require_relative "provisioner"
Provisioner
end
end
In the vagrant-mutagen-utilizer I created, it looks like this:
lib/vagrant_mutagen_utilizer/plugin.rb
# frozen_string_literal: true
require_relative 'action/update_config'
require_relative 'action/remove_config'
require_relative 'action/start_orchestration'
require_relative 'action/terminate_orchestration'
require_relative 'action/save_machine_identifier'
module VagrantPlugins
module MutagenUtilizer
# Plugin to utilize mutagen
class MutagenUtilizerPlugin < Vagrant.plugin('2')
name 'Mutagen Utilizer'
description <<-DESC
This plugin manages the ~/.ssh/config file for the host machine. An entry is
created for the hostname attribute in the vm.config.
DESC
config(:mutagen_utilizer) do
require_relative 'config'
Config
end
action_hook(:mutagen_utilizer, :machine_action_up) do |hook|
hook.append(Action::UpdateConfig)
hook.append(Action::StartOrchestration)
end
: <snip>
end
end
end
vagrant-mutagen-utilizer.gemspec
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'vagrant_mutagen_utilizer/version'
Gem::Specification.new do |spec|
spec.name = 'vagrant-mutagen-utilizer'
: <snip>
end
lib/vagrant-mutagen-utilizer.rb
# frozen_string_literal: true
require 'vagrant_mutagen_utilizer/version'
require 'vagrant_mutagen_utilizer/plugin'
module VagrantPlugins
# MutagenUtilizer
module MutagenUtilizer
def self.source_root
@source_root ||= Pathname.new(File.expand_path('..', __dir__))
end
end
end
Forked vagrant-mutagen was originally like that, but instead of describing various processes in the Plugin body, class is described separately for each process, read using require_relative, and registered as a component. ..
Specify the class name on the Plugin body side.
The "foo" specified here corresponds to config.foo
. ~ In the Vagrantfile.
config "foo" do
require_relative "config"
Config
end
The Config component is defined as a subclass of Vagrant.plugin (2,: config)
.
class Config < Vagrant.plugin(2, :config)
attr_accessor :widgets
def initialize
@widgets = UNSET_VALUE
end
def finalize!
@widgets = 0 if @widgets == UNSET_VALUE
end
end
UNSET_VALUE is a value that points to undefined in Vagrant. Some plugins provide undefined values so that you can define nil as an initial value or a correct value.
initialize is the constructor when the Config class is initialized, and finalize! Is called when all Configs have been read.
In the example, 0
is assigned if @widgets
is not defined in the Config file.
The value set in Config can be accessed as machine.config.mutagen_utilizer.orchestrate
.
Action Hooks
Set this when you want to perform specific processing when vagrant up
, vagrant halt
, etc. are executed.
Hook types can be found at https://www.vagrantup.com/docs/plugins/action-hooks#public-action-hooks.
In the following, the UpdateConfig and StartOrchestration classes are set to be called when vagrant up
is executed.
lib/vagrant_mutagen_utilizer/plugin.rb
# frozen_string_literal: true
require_relative 'action/update_config'
require_relative 'action/start_orchestration'
: <snip>
module VagrantPlugins
module MutagenUtilizer
# Plugin to utilize mutagen
class MutagenUtilizerPlugin < Vagrant.plugin('2')
: <snip>
action_hook(:mutagen_utilizer, :machine_action_up) do |hook|
hook.append(Action::UpdateConfig)
hook.append(Action::StartOrchestration)
end
: <snip>
end
end
end
The UpdateConfig and StartOrchestration classes are defined as follows:
lib/vagrant_mutagen_utilizer/action/update_config.rb
# frozen_string_literal: true
require_relative '../orchestrator'
module VagrantPlugins
module MutagenUtilizer
module Action
# Update ssh config entry
# If ssh config entry already exists, just entry appended
class UpdateConfig
def initialize(app, env)
@app = app
@machine = env[:machine]
@config = env[:machine].config
@console = env[:ui]
end
def call(env)
return unless @config.orchestrate?
o = Orchestrator.new(@machine, @console)
o.update_ssh_config_entry
@app.call(env)
end
end
end
end
end
lib/vagrant_mutagen_utilizer/action/start_orchestration.rb
# frozen_string_literal: true
require_relative '../orchestrator'
module VagrantPlugins
module MutagenUtilizer
module Action
# Start mutagen project
class StartOrchestration
def initialize(app, env)
@app = app
@machine = env[:machine]
@config = env[:machine].config
@console = env[:ui]
end
def call(env)
return unless @config.orchestrate?
o = Orchestrator.new(@machine, @console)
o.start_orchestration
@app.call(env)
end
end
end
end
end
In both UpdateConfig and StartOrchestration, the constructor only registers the value received from the argument in the instance variable.
--@app
: Used when the call method is executed. Details are unknown.
--@machine
: You can refer to the ID and Config related to the VM.
--@config
: You can refer to the settings
--@ console
: Console input / output is possible
The main functionality is written in the call method. Here we check if the Plugin is enabled in Config, and if so, we call the start_orchestration method of the Orchestrattor class.
The Orchestrattor class is:
# frozen_string_literal: true
module VagrantPlugins
module MutagenUtilizer
# Class for orchestrate with mutagen
class Orchestrator
def initialize(machine, console)
@machine = machine
@console = console
end
# Update ssh config entry
# If ssh config entry already exists, just entry appended
def update_ssh_config_entry
hostname = @machine.config.vm.hostname
logging(:info, 'Checking for SSH config entries')
if ssh_config_entry_exist?
logging(:info, " updating SSH Config entry for: #{hostname}")
remove_from_ssh_config
else
logging(:info, " adding entry to SSH config for: #{hostname}")
end
append_to_ssh_config(ssh_config_entry)
end
def start_orchestration
return if mutagen_project_started?
logging(:info, 'Starting mutagen project orchestration (config: /mutagen.yml)')
start_mutagen_project || logging(:error, 'Failed to start mutagen project (see error above)')
# show project status to indicate if there are conflicts
list_mutagen_project
end
: <snip>
private
: <snip>
end
end
end
Although details are omitted, update_ssh_config_entry called from Action Hooks describes the process of adding an entry to the SSH configuration file, and start_orchestration describes the process of starting a mutagen project.
In this way, you can write a plugin that does a specific thing when vagrant up
is executed.
If you want to know the filters other than vagrant up
, you can refer to https://www.vagrantup.com/docs/plugins/action-hooks#public-action-hooks.
It has already appeared several times, but it inputs and outputs text via Vagrant :: UI
for Vagrant I / O.
I can't use the standard Ruby put / get.
The Environment for each middleware, ʻenv, allows you to get the Vagrant :: UI with ʻenv [: ui]
.
There are log output levels of info, warn, error, success, and you can output logs at each level.
The procedure is the same as exposing the gem to rubygems.