147 lines
5.2 KiB
Ruby

# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
require "log4r"
require_relative "mixin_provisioners"
module Vagrant
module Action
module Builtin
# This class will run the configured provisioners against the
# machine.
#
# This action should be placed BEFORE the machine is booted so it
# can do some setup, and then run again (on the return path) against
# a running machine.
class Provision
include MixinProvisioners
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::provision")
end
def call(env)
@env = env
# Tracks whether we were configured to provision
config_enabled = true
config_enabled = env[:provision_enabled] if env.key?(:provision_enabled)
# Check if we already provisioned, and if so, disable the rest
provision_enabled = true
ignore_sentinel = true
if env.key?(:provision_ignore_sentinel)
ignore_sentinel = env[:provision_ignore_sentinel]
end
if ignore_sentinel
@logger.info("Ignoring sentinel check, forcing provision")
end
@logger.info("Checking provisioner sentinel file...")
sentinel_path = env[:machine].data_dir.join("action_provision")
update_sentinel = false
if sentinel_path.file?
# The sentinel file is in the format of "version:data" so that
# we can remain backwards compatible with previous sentinels.
# Versions so far:
#
# Vagrant < 1.5.0: A timestamp. The weakness here was that
# if it wasn't cleaned up, it would incorrectly not provision
# new machines.
#
# Vagrant >= 1.5.0: "1.5:ID", where ID is the machine ID.
# We compare both so we know whether it is a new machine.
#
contents = sentinel_path.read.chomp
parts = contents.split(":", 2)
if parts.length == 1
@logger.info("Old-style sentinel found! Not provisioning.")
provision_enabled = false if !ignore_sentinel
update_sentinel = true
elsif parts[0] == "1.5" && parts[1] == env[:machine].id.to_s
@logger.info("Sentinel found! Not provisioning.")
provision_enabled = false if !ignore_sentinel
else
@logger.info("Sentinel found with another machine ID. Removing.")
sentinel_path.unlink
end
end
# Store the value so that other actions can use it
env[:provision_enabled] = provision_enabled if !env.key?(:provision_enabled)
# Ask the provisioners to modify the configuration if needed
provisioner_instances(env).each do |p, _|
p.configure(env[:machine].config)
end
# Continue, we need the VM to be booted.
@app.call(env)
# If we're configured to not provision, notify the user and stop
if !config_enabled
env[:ui].info(I18n.t("vagrant.actions.vm.provision.disabled_by_config"))
return
end
# If we're not provisioning because of the sentinel, tell the user
# but continue trying for the "always" provisioners
if !provision_enabled
env[:ui].info(I18n.t("vagrant.actions.vm.provision.disabled_by_sentinel"))
end
# Write the sentinel if we have to
if update_sentinel || !sentinel_path.file?
@logger.info("Writing provisioning sentinel so we don't provision again")
sentinel_path.open("w") do |f|
f.write("1.5:#{env[:machine].id}")
end
end
type_map = provisioner_type_map(env)
provisioner_instances(env).each do |p, options|
type_name = type_map[p]
if options[:run] == :never
next if env[:provision_types].nil? || !env[:provision_types].include?(options[:name])
else
next if env[:provision_types] && \
!env[:provision_types].include?(type_name) && \
!env[:provision_types].include?(options[:name])
# Don't run if sentinel is around and we're not always running
next if !provision_enabled && options[:run] != :always
end
name = type_name
if options[:name]
name = "#{options[:name]} (#{type_name})"
end
env[:ui].info(I18n.t(
"vagrant.actions.vm.provision.beginning",
provisioner: name))
env[:hook].call(:provisioner_run, env.merge(
callable: method(:run_provisioner),
provisioner: p,
provisioner_name: type_name,
))
end
end
# This is pulled out into a separate method so that users can
# subclass and implement custom behavior if they'd like to work around
# this step.
def run_provisioner(env)
env[:provisioner].provision
end
end
end
end
end