mirror of
https://github.com/hashicorp/vagrant.git
synced 2026-06-08 16:26:25 -04:00
Update cloud-init behavior and iso generation
This includes a couple modifications to the cloud-init behavior. First, the cloud-init wait action will now write a sentinel file after successfully waiting for cloud-init. This results in subsequent boots of the machine to skip executing the cloud-init wait command as cloud-init is only executed on the initial boot. Second, the cloud-init setup action will check for the sentinel file written by the cloud-init wait action, and if detected it will skip the cloud-init setup. When creating the ISO for cloud-init, a second sentinel file will be used to log the path of the generated ISO file. If the file exists, the ISO generation process will be skipped.
This commit is contained in:
parent
9d25d2155e
commit
f4fc2c742a
11 changed files with 397 additions and 104 deletions
|
|
@ -16,16 +16,38 @@ module Vagrant
|
|||
end
|
||||
|
||||
def call(env)
|
||||
machine = env[:machine]
|
||||
catch(:complete) do
|
||||
machine = env[:machine]
|
||||
|
||||
user_data_configs = machine.config.vm.cloud_init_configs
|
||||
.select { |c| c.type == :user_data }
|
||||
# The sentinel file in this check is written by the cloud init
|
||||
# wait action and is only written after cloud init has completed.
|
||||
@logger.info("Checking cloud-init sentinel file...")
|
||||
sentinel_path = machine.data_dir.join("action_cloud_init")
|
||||
if sentinel_path.file?
|
||||
contents = sentinel_path.read.chomp
|
||||
if machine.id.to_s == contents
|
||||
if machine.config.vm.cloud_init_first_boot_only
|
||||
@logger.info("Sentinel found for cloud-init, skipping")
|
||||
throw :complete
|
||||
else
|
||||
@logger.info("Sentinel found for cloud-init but is configuration enabled")
|
||||
end
|
||||
else
|
||||
@logger.debug("Found stale sentinel file, removing... (#{machine.id} != #{contents})")
|
||||
end
|
||||
sentinel_path.unlink
|
||||
end
|
||||
|
||||
if !user_data_configs.empty?
|
||||
user_data = setup_user_data(machine, env, user_data_configs)
|
||||
meta_data = { "instance-id" => "i-#{machine.id.split('-').join}" }
|
||||
user_data_configs = machine.config.vm.cloud_init_configs.select { |c|
|
||||
c.type == :user_data
|
||||
}
|
||||
|
||||
write_cfg_iso(machine, env, user_data, meta_data)
|
||||
if !user_data_configs.empty?
|
||||
user_data = setup_user_data(machine, env, user_data_configs)
|
||||
meta_data = { "instance-id" => "i-#{machine.id.split('-').join}" }
|
||||
|
||||
write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
end
|
||||
|
||||
# Continue On
|
||||
|
|
@ -88,24 +110,43 @@ module Vagrant
|
|||
# @param [Vagrant::Util::Mime::Multipart] user_data
|
||||
# @param [Hash] meta_data
|
||||
def write_cfg_iso(machine, env, user_data, meta_data)
|
||||
iso_path = nil
|
||||
raise Errors::CreateIsoHostCapNotFound if !env[:env].host.capability?(:create_iso)
|
||||
|
||||
iso_path = catch(:iso_path) do
|
||||
# This iso sentinel file is used to store the path of the
|
||||
# generated iso file and its checksum. If the file does
|
||||
# not exist, or the actual checksum of the file does not
|
||||
# match that stored in the sentinel file, it is ignored
|
||||
# and the iso is generated. This is used to prevent multiple
|
||||
# iso file from being created over time.
|
||||
iso_sentinel = env[:machine].data_dir.join("action_cloud_init_iso")
|
||||
if iso_sentinel.file?
|
||||
checksum, path = iso_sentinel.read.chomp.split(":", 2)
|
||||
if File.exist?(path) && Vagrant::Util::FileChecksum.new(path, :sha256).checksum == checksum
|
||||
throw :iso_path, Pathname.new(path)
|
||||
end
|
||||
iso_sentinel.unlink
|
||||
end
|
||||
|
||||
if env[:env].host.capability?(:create_iso)
|
||||
begin
|
||||
source_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX))
|
||||
File.open("#{source_dir}/user-data", 'w') { |file| file.write(user_data.to_s) }
|
||||
|
||||
File.open("#{source_dir}/meta-data", 'w') { |file| file.write(meta_data.to_yaml) }
|
||||
|
||||
iso_path = env[:env].host.capability(:create_iso,
|
||||
source_dir, volume_id: "cidata")
|
||||
attach_disk_config(machine, env, iso_path.to_path)
|
||||
env[:env].host.capability(
|
||||
:create_iso,
|
||||
source_dir,
|
||||
volume_id: "cidata"
|
||||
).tap { |path|
|
||||
checksum = Vagrant::Util::FileChecksum.new(path.to_path, :sha256).checksum
|
||||
iso_sentinel.write("#{checksum}:#{path.to_path}")
|
||||
}
|
||||
ensure
|
||||
FileUtils.remove_entry(source_dir)
|
||||
end
|
||||
else
|
||||
raise Errors::CreateIsoHostCapNotFound
|
||||
end
|
||||
|
||||
attach_disk_config(machine, env, iso_path.to_path)
|
||||
end
|
||||
|
||||
# Adds a new :dvd disk config with the given iso_path to be attached
|
||||
|
|
|
|||
|
|
@ -14,19 +14,37 @@ module Vagrant
|
|||
end
|
||||
|
||||
def call(env)
|
||||
machine = env[:machine]
|
||||
cloud_init_wait_cmd = "cloud-init status --wait"
|
||||
if !machine.config.vm.cloud_init_configs.empty?
|
||||
if machine.communicate.test("command -v cloud-init")
|
||||
env[:ui].output(I18n.t("vagrant.cloud_init_waiting"))
|
||||
result = machine.communicate.sudo(cloud_init_wait_cmd, error_check: false)
|
||||
if result != 0
|
||||
raise Vagrant::Errors::CloudInitCommandFailed, cmd: cloud_init_wait_cmd, guest_name: machine.name
|
||||
catch(:complete) do
|
||||
machine = env[:machine]
|
||||
sentinel_path = machine.data_dir.join("action_cloud_init")
|
||||
|
||||
@logger.info("Checking cloud-init sentinel file...")
|
||||
if sentinel_path.file?
|
||||
contents = sentinel_path.read.chomp
|
||||
if machine.id.to_s == contents
|
||||
@logger.info("Sentinel found for cloud-init, skipping")
|
||||
throw :complete
|
||||
end
|
||||
else
|
||||
raise Vagrant::Errors::CloudInitNotFound, guest_name: machine.name
|
||||
@logger.debug("Found stale sentinel file, removing... (#{machine.id} != #{contents})")
|
||||
sentinel_path.unlink
|
||||
end
|
||||
|
||||
cloud_init_wait_cmd = "cloud-init status --wait"
|
||||
if !machine.config.vm.cloud_init_configs.empty?
|
||||
if machine.communicate.test("command -v cloud-init")
|
||||
env[:ui].output(I18n.t("vagrant.cloud_init_waiting"))
|
||||
result = machine.communicate.sudo(cloud_init_wait_cmd, error_check: false)
|
||||
if result != 0
|
||||
raise Vagrant::Errors::CloudInitCommandFailed, cmd: cloud_init_wait_cmd, guest_name: machine.name
|
||||
end
|
||||
else
|
||||
raise Vagrant::Errors::CloudInitNotFound, guest_name: machine.name
|
||||
end
|
||||
end
|
||||
# Write sentinel path
|
||||
sentinel_path.write(machine.id.to_s)
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ module Vagrant
|
|||
end
|
||||
end
|
||||
|
||||
write_disk_metadata(machine, configured_disks) unless configured_disks.empty?
|
||||
# Always write the disk metadata even if the configured
|
||||
# disks is empty. This ensure that old entries are not
|
||||
# orphaned in the metadata file.
|
||||
write_disk_metadata(machine, configured_disks)
|
||||
|
||||
# Continue On
|
||||
@app.call(env)
|
||||
|
|
|
|||
|
|
@ -13,65 +13,74 @@ class DigestClass
|
|||
def hexdigest; end
|
||||
end
|
||||
|
||||
class FileChecksum
|
||||
BUFFER_SIZE = 1024 * 8
|
||||
module Vagrant
|
||||
module Util
|
||||
class FileChecksum
|
||||
BUFFER_SIZE = 1024 * 8
|
||||
|
||||
# Supported file checksum
|
||||
CHECKSUM_MAP = {
|
||||
:md5 => Digest::MD5,
|
||||
:sha1 => Digest::SHA1,
|
||||
:sha256 => Digest::SHA256,
|
||||
:sha384 => Digest::SHA384,
|
||||
:sha512 => Digest::SHA512
|
||||
}.freeze
|
||||
# Supported file checksum
|
||||
CHECKSUM_MAP = {
|
||||
:md5 => Digest::MD5,
|
||||
:sha1 => Digest::SHA1,
|
||||
:sha256 => Digest::SHA256,
|
||||
:sha384 => Digest::SHA384,
|
||||
:sha512 => Digest::SHA512
|
||||
}.freeze
|
||||
|
||||
# Initializes an object to calculate the checksum of a file. The given
|
||||
# ``digest_klass`` should implement the ``DigestClass`` interface. Note
|
||||
# that the built-in Ruby digest classes duck type this properly:
|
||||
# Digest::MD5, Digest::SHA1, etc.
|
||||
def initialize(path, digest_klass)
|
||||
if digest_klass.is_a?(Class)
|
||||
@digest_klass = digest_klass
|
||||
else
|
||||
@digest_klass = load_digest(digest_klass)
|
||||
end
|
||||
|
||||
@path = path
|
||||
end
|
||||
|
||||
# This calculates the checksum of the file and returns it as a
|
||||
# string.
|
||||
#
|
||||
# @return [String]
|
||||
def checksum
|
||||
digest = @digest_klass.new
|
||||
buf = ''
|
||||
|
||||
File.open(@path, "rb") do |f|
|
||||
while !f.eof
|
||||
begin
|
||||
f.readpartial(BUFFER_SIZE, buf)
|
||||
digest.update(buf)
|
||||
rescue EOFError
|
||||
# Although we check for EOF earlier, this seems to happen
|
||||
# sometimes anyways [GH-2716].
|
||||
break
|
||||
# Initializes an object to calculate the checksum of a file. The given
|
||||
# ``digest_klass`` should implement the ``DigestClass`` interface. Note
|
||||
# that the built-in Ruby digest classes duck type this properly:
|
||||
# Digest::MD5, Digest::SHA1, etc.
|
||||
def initialize(path, digest_klass)
|
||||
if digest_klass.is_a?(Class)
|
||||
@digest_klass = digest_klass
|
||||
else
|
||||
@digest_klass = load_digest(digest_klass)
|
||||
end
|
||||
|
||||
@path = path
|
||||
end
|
||||
|
||||
# This calculates the checksum of the file and returns it as a
|
||||
# string.
|
||||
#
|
||||
# @return [String]
|
||||
def checksum
|
||||
digest = @digest_klass.new
|
||||
buf = ''
|
||||
|
||||
File.open(@path, "rb") do |f|
|
||||
while !f.eof
|
||||
begin
|
||||
f.readpartial(BUFFER_SIZE, buf)
|
||||
digest.update(buf)
|
||||
rescue EOFError
|
||||
# Although we check for EOF earlier, this seems to happen
|
||||
# sometimes anyways [GH-2716].
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
digest.hexdigest
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_digest(type)
|
||||
digest = CHECKSUM_MAP[type.to_s.downcase.to_sym]
|
||||
if digest.nil?
|
||||
raise Vagrant::Errors::BoxChecksumInvalidType,
|
||||
type: type.to_s,
|
||||
types: CHECKSUM_MAP.keys.join(', ')
|
||||
end
|
||||
digest
|
||||
end
|
||||
end
|
||||
|
||||
digest.hexdigest
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_digest(type)
|
||||
digest = CHECKSUM_MAP[type.to_s.downcase.to_sym]
|
||||
if digest.nil?
|
||||
raise Vagrant::Errors::BoxChecksumInvalidType,
|
||||
type: type.to_s,
|
||||
types: CHECKSUM_MAP.keys.join(', ')
|
||||
end
|
||||
digest
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: This class was not originally namespaced
|
||||
# with the Util module so this is left for backwards
|
||||
# compatibility.
|
||||
FileChecksum = Vagrant::Util::FileChecksum
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ module Vagrant
|
|||
# @param [String] type of the entity content
|
||||
def initialize(content, content_type)
|
||||
if !MIME::Types.include?(content_type)
|
||||
MIME::Types.add(MIME::Type.new(content_type))
|
||||
MIME::Types.add(MIME::Type.new("content-type" => content_type))
|
||||
end
|
||||
@content = content
|
||||
@content_type = MIME::Types[content_type].first
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ module VagrantPlugins
|
|||
attr_accessor :box_download_insecure
|
||||
attr_accessor :box_download_location_trusted
|
||||
attr_accessor :box_download_options
|
||||
attr_accessor :cloud_init_first_boot_only
|
||||
attr_accessor :communicator
|
||||
attr_accessor :graceful_halt_timeout
|
||||
attr_accessor :guest
|
||||
|
|
@ -87,6 +88,7 @@ module VagrantPlugins
|
|||
@box_version = UNSET_VALUE
|
||||
@allow_hosts_modification = UNSET_VALUE
|
||||
@clone = UNSET_VALUE
|
||||
@cloud_init_first_boot_only = UNSET_VALUE
|
||||
@communicator = UNSET_VALUE
|
||||
@graceful_halt_timeout = UNSET_VALUE
|
||||
@guest = UNSET_VALUE
|
||||
|
|
@ -536,6 +538,7 @@ module VagrantPlugins
|
|||
@box_extra_download_options = Vagrant::Util::MapCommandOptions.map_to_command_options(@box_download_options)
|
||||
@allow_hosts_modification = true if @allow_hosts_modification == UNSET_VALUE
|
||||
@clone = nil if @clone == UNSET_VALUE
|
||||
@cloud_init_first_boot_only = @cloud_init_first_boot_only == UNSET_VALUE ? true : !!@cloud_init_first_boot_only
|
||||
@communicator = nil if @communicator == UNSET_VALUE
|
||||
@graceful_halt_timeout = 60 if @graceful_halt_timeout == UNSET_VALUE
|
||||
@guest = nil if @guest == UNSET_VALUE
|
||||
|
|
|
|||
|
|
@ -82,22 +82,14 @@ module VagrantPlugins
|
|||
b.use ForwardPorts
|
||||
b.use SetHostname
|
||||
b.use SaneDefaults
|
||||
b.use Call, IsEnvSet, :cloud_init do |env, b2|
|
||||
if env[:result]
|
||||
b2.use CloudInitSetup
|
||||
end
|
||||
end
|
||||
b.use CloudInitSetup
|
||||
b.use CleanupDisks
|
||||
b.use Disk
|
||||
b.use Customize, "pre-boot"
|
||||
b.use Boot
|
||||
b.use Customize, "post-boot"
|
||||
b.use WaitForCommunicator, [:starting, :running, :paused]
|
||||
b.use Call, IsEnvSet, :cloud_init do |env, b2|
|
||||
if env[:result]
|
||||
b2.use CloudInitWait
|
||||
end
|
||||
end
|
||||
b.use CloudInitWait
|
||||
b.use Customize, "post-comm"
|
||||
b.use CheckGuestAdditions
|
||||
end
|
||||
|
|
@ -424,7 +416,6 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
b.use EnvSet, cloud_init: true
|
||||
b.use action_start
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ require "vagrant/util/mime"
|
|||
|
||||
describe Vagrant::Action::Builtin::CloudInitSetup do
|
||||
let(:app) { lambda { |env| } }
|
||||
let(:vm) { double("vm", disk: disk, disks: disks) }
|
||||
let(:vm) { double("vm", disk: disk, disks: disks, cloud_init_first_boot_only: first_boot_only) }
|
||||
let(:first_boot_only) { true }
|
||||
let(:disk) { double("disk") }
|
||||
let(:disks) { double("disk") }
|
||||
let(:config) { double("config", vm: vm) }
|
||||
|
|
@ -59,10 +60,69 @@ describe Vagrant::Action::Builtin::CloudInitSetup do
|
|||
|
||||
expect(subject).not_to receive(:setup_user_data)
|
||||
expect(subject).not_to receive(:write_cfg_iso)
|
||||
expect(subject).not_to receive(:attack_disk_config)
|
||||
expect(subject).not_to receive(:attach_disk_config)
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
context "sentinel file" do
|
||||
let(:sentinel) { double("sentinel") }
|
||||
let(:sentinel_exists) { false }
|
||||
let(:sentinel_contents) { "" }
|
||||
|
||||
before do
|
||||
allow(machine).to receive_message_chain(:data_dir, :join).with("action_cloud_init").and_return(sentinel)
|
||||
allow(sentinel).to receive(:file?).and_return(sentinel_exists)
|
||||
allow(sentinel).to receive(:read).and_return(sentinel_contents)
|
||||
allow(sentinel).to receive(:unlink)
|
||||
|
||||
allow(vm).to receive(:cloud_init_configs).and_return(cloud_init_configs)
|
||||
allow(subject).to receive(:setup_user_data)
|
||||
allow(subject).to receive(:write_cfg_iso)
|
||||
end
|
||||
|
||||
context "when file exists" do
|
||||
let(:sentinel_exists) { true }
|
||||
|
||||
context "when file contains machine id" do
|
||||
let(:sentinel_contents) { machine.id.to_s }
|
||||
|
||||
it "should not write iso configuration" do
|
||||
expect(subject).not_to receive(:write_cfg_iso)
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
context "when configuration enables on all boots" do
|
||||
let(:first_boot_only) { false }
|
||||
|
||||
it "should write the iso configuration" do
|
||||
expect(subject).to receive(:write_cfg_iso)
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "should remove sentinel file" do
|
||||
expect(sentinel).to receive(:unlink)
|
||||
subject.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when file does not contain machine id" do
|
||||
let(:sentinel_contents) { "unknown-id" }
|
||||
|
||||
it "should write iso configuration" do
|
||||
expect(subject).to receive(:write_cfg_iso)
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "should remove sentinel file" do
|
||||
expect(sentinel).to receive(:unlink)
|
||||
subject.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#setup_user_data" do
|
||||
|
|
@ -117,9 +177,18 @@ describe Vagrant::Action::Builtin::CloudInitSetup do
|
|||
let(:iso_path) { Pathname.new("fake/iso/path") }
|
||||
let(:source_dir) { Pathname.new("fake/source/path") }
|
||||
let(:meta_data_file) { double("meta_data_file") }
|
||||
let(:sentinel) { double("sentinel") }
|
||||
let(:sentinel_exists) { false }
|
||||
let(:file_checksum) { double("file_checksum", checksum: checksum) }
|
||||
let(:checksum) { "DUMMY-CHECKSUM-VALUE" }
|
||||
|
||||
before do
|
||||
allow(meta_data_file).to receive(:write).and_return(true)
|
||||
allow(machine).to receive_message_chain(:data_dir, :join).with("action_cloud_init_iso").and_return(sentinel)
|
||||
allow(sentinel).to receive(:file?).and_return(sentinel_exists)
|
||||
allow(sentinel).to receive(:write)
|
||||
allow(Vagrant::Util::FileChecksum).to receive(:new).with(iso_path, :sha256).and_return(file_checksum)
|
||||
allow(Vagrant::Util::FileChecksum).to receive(:new).with(iso_path.to_s, :sha256).and_return(file_checksum)
|
||||
end
|
||||
|
||||
it "raises an error if the host capability is not supported" do
|
||||
|
|
@ -140,8 +209,111 @@ describe Vagrant::Action::Builtin::CloudInitSetup do
|
|||
expect(vm.disks).to receive(:each)
|
||||
expect(meta_data).to receive(:to_yaml)
|
||||
|
||||
|
||||
subject.write_cfg_iso(machine, env, message, meta_data)
|
||||
end
|
||||
|
||||
context "sentinel file" do
|
||||
let(:user_data) { double("user_data") }
|
||||
let(:sentinel_contents) { "" }
|
||||
|
||||
before do
|
||||
allow(sentinel).to receive(:read).and_return(sentinel_contents)
|
||||
allow(sentinel).to receive(:unlink)
|
||||
|
||||
allow(host).to receive(:capability?).with(:create_iso).and_return(true)
|
||||
allow(Dir).to receive(:mktmpdir).and_return(source_dir)
|
||||
allow(File).to receive(:open).with("#{source_dir}/user-data", 'w').and_return(true)
|
||||
allow(File).to receive(:open).with("#{source_dir}/meta-data", 'w').and_yield(meta_data_file)
|
||||
allow(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)
|
||||
allow(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)
|
||||
allow(host).to receive(:capability).with(:create_iso, source_dir, volume_id: "cidata").and_return(iso_path)
|
||||
allow(subject).to receive(:attach_disk_config)
|
||||
end
|
||||
|
||||
context "when file exists" do
|
||||
let(:sentinel_exists) { true }
|
||||
|
||||
context "when file contents is iso path" do
|
||||
let(:sentinel_contents) { "#{checksum}:#{iso_path}" }
|
||||
|
||||
context "when file contents path exists" do
|
||||
before do
|
||||
expect(File).to receive(:exist?).with(iso_path.to_s).and_return(true)
|
||||
end
|
||||
|
||||
it "should not create iso" do
|
||||
expect(host).not_to receive(:capability)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should attach with the iso path" do
|
||||
expect(subject).to receive(:attach_disk_config).with(machine, env, iso_path.to_path)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should not write the sentinel file" do
|
||||
expect(sentinel).not_to receive(:write)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
end
|
||||
|
||||
context "when file contents path does not exist" do
|
||||
before do
|
||||
expect(File).to receive(:exist?).with(iso_path.to_s).and_return(false)
|
||||
end
|
||||
|
||||
it "should create iso" do
|
||||
expect(host).to receive(:capability).with(:create_iso, source_dir, volume_id: "cidata").and_return(iso_path)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should remove the sentinel file" do
|
||||
expect(sentinel).to receive(:unlink)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should write the sentinel file" do
|
||||
expect(sentinel).to receive(:write).with("#{checksum}:#{iso_path}")
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
end
|
||||
|
||||
context "when file contents checksum does not match existing file checksum" do
|
||||
let(:sentinel_contents) { "BAD-CHECKSUM-VALUE:#{iso_path}" }
|
||||
|
||||
it "should create iso" do
|
||||
expect(host).to receive(:capability).with(:create_iso, source_dir, volume_id: "cidata").and_return(iso_path)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should remove the sentinel file" do
|
||||
expect(sentinel).to receive(:unlink)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should write the sentinel file" do
|
||||
expect(sentinel).to receive(:write).with("#{checksum}:#{iso_path}")
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when file does not exist" do
|
||||
let(:sentinel_exists) { false }
|
||||
|
||||
it "should create iso" do
|
||||
expect(host).to receive(:capability).with(:create_iso, source_dir, volume_id: "cidata").and_return(iso_path)
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
it "should write the sentinel file" do
|
||||
expect(sentinel).to receive(:write).with("#{checksum}:#{iso_path}")
|
||||
subject.write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#attach_disk_config" do
|
||||
|
|
|
|||
|
|
@ -8,22 +8,30 @@ describe Vagrant::Action::Builtin::CloudInitWait do
|
|||
let(:app) { lambda { |env| } }
|
||||
let(:config) { double("config", :vm => vm) }
|
||||
let(:comm) { double("comm") }
|
||||
let(:machine) { double("machie", :config => config, :communicate => comm, :name => "test") }
|
||||
let(:machine) { double("machine", :config => config, :communicate => comm, :name => "test", id: "m-id", data_dir: data_dir) }
|
||||
let(:data_dir) { double("data_dir") }
|
||||
let(:ui) { Vagrant::UI::Silent.new }
|
||||
let(:env) { { machine: machine, ui: ui} }
|
||||
let(:sentinel) { double("sentinel_path", unlink: nil) }
|
||||
|
||||
let(:subject) { described_class.new(app, env) }
|
||||
|
||||
describe "#call" do
|
||||
let(:sentinel_exists) { false }
|
||||
let(:sentinel_contents) { "" }
|
||||
|
||||
before do
|
||||
allow(data_dir).to receive(:join).with("action_cloud_init").and_return(sentinel)
|
||||
allow(sentinel).to receive(:file?).and_return(sentinel_exists)
|
||||
allow(sentinel).to receive(:read).and_return(sentinel_contents)
|
||||
allow(sentinel).to receive(:write).with(machine.id)
|
||||
allow(comm).to receive(:test).with("command -v cloud-init").and_return(true)
|
||||
allow(comm).to receive(:sudo).with("cloud-init status --wait", error_check: false).and_return(0)
|
||||
end
|
||||
|
||||
context "cloud init configuration exists" do
|
||||
|
||||
let(:vm) { double("vm", cloud_init_configs: ["some config"]) }
|
||||
|
||||
before do
|
||||
allow(comm).to receive(:test).with("command -v cloud-init").and_return(true)
|
||||
end
|
||||
|
||||
it "waits for cloud init to be executed" do
|
||||
expect(comm).to receive(:sudo).with("cloud-init status --wait", any_args).and_return(0)
|
||||
subject.call(env)
|
||||
|
|
@ -40,10 +48,51 @@ describe Vagrant::Action::Builtin::CloudInitWait do
|
|||
expect { subject.call(env) }.
|
||||
to raise_error(Vagrant::Errors::CloudInitCommandFailed)
|
||||
end
|
||||
|
||||
context "when sentinel file exists" do
|
||||
let(:sentinel_exists) { true }
|
||||
|
||||
context "when sentinel contents is machine id" do
|
||||
let(:sentinel_contents) { machine.id.to_s }
|
||||
|
||||
it "should not test for cloud-init" do
|
||||
expect(comm).not_to receive(:test).with(/cloud-init/)
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "should not run cloud-init" do
|
||||
expect(comm).not_to receive(:sudo).with(/cloud-init/, anything)
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "should not write sentinel file" do
|
||||
expect(sentinel).not_to receive(:write)
|
||||
subject.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
context "when sentinel content is not machine id" do
|
||||
let(:sentinel_contents) { "unknown-id" }
|
||||
|
||||
it "should test for cloud-init" do
|
||||
expect(comm).to receive(:test).with(/cloud-init/)
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "should run cloud-init" do
|
||||
expect(comm).to receive(:sudo).with(/cloud-init/, anything)
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "should write sentinel file" do
|
||||
expect(sentinel).to receive(:write).with(machine.id)
|
||||
subject.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "no cloud init configuration" do
|
||||
|
||||
let(:vm) { double("vm", cloud_init_configs: []) }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -34,14 +34,14 @@ describe Vagrant::Action::Builtin::Disk do
|
|||
subject.call(env)
|
||||
end
|
||||
|
||||
it "continues on if no disk config present" do
|
||||
it "writes a disk_meta file if no disk config is present" do
|
||||
allow(vm).to receive(:disks).and_return([])
|
||||
subject = described_class.new(app, env)
|
||||
|
||||
expect(app).to receive(:call).with(env).ordered
|
||||
expect(machine.provider).not_to receive(:capability).with(:configure_disks, disks)
|
||||
|
||||
expect(subject).not_to receive(:write_disk_metadata)
|
||||
expect(subject).to receive(:write_disk_metadata)
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
|
@ -50,6 +50,7 @@ describe Vagrant::Action::Builtin::Disk do
|
|||
allow(vm).to receive(:disks).and_return(disks)
|
||||
allow(machine.provider).to receive(:capability?).with(:configure_disks).and_return(false)
|
||||
subject = described_class.new(app, env)
|
||||
allow(subject).to receive(:write_disk_metadata)
|
||||
|
||||
expect(app).to receive(:call).with(env).ordered
|
||||
expect(machine.provider).not_to receive(:capability).with(:configure_disks, disks)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,12 @@ the name of the synced folder plugin.
|
|||
- `config.vm.cloud_init` - Stores various [cloud_init](/vagrant/docs/cloud-init) configurations
|
||||
on the machine.
|
||||
|
||||
- `config.vm.cloud_init_first_boot_only` - (boolean) - If true then the cloud-init
|
||||
configuration will only be generated and attached on the first successful boot of
|
||||
the machine. Subsequent boots of the machine will not generate the cloud-init
|
||||
configuration and the `cloud-init wait` command will not be executed. Defaults
|
||||
to `true`.
|
||||
|
||||
- `config.vm.communicator` (string) - The communicator type to use to connect to the
|
||||
guest box. By default this is `"ssh"`, but should be changed to `"winrm"` for
|
||||
Windows guests.
|
||||
|
|
|
|||
Loading…
Reference in a new issue