8000 Update cloud-init behavior and iso generation by chrisroberts · Pull Request #13666 · hashicorp/vagrant · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Update cloud-init behavior and iso generation #13666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 56 additions & 15 deletions lib/vagrant/action/builtin/cloud_init_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,38 @@ def initialize(app, env)
end

def call(env)
machine = env[:machine]
catch(:complete) do
machine = env[:machine]

# 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

user_data_configs = machine.config.vm.cloud_init_configs
.select { |c| c.type == :user_data }
user_data_configs = machine.config.vm.cloud_init_configs.select { |c|
c.type == :user_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}" }
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)
write_cfg_iso(machine, env, user_data, meta_data)
end
end

# Continue On
Expand Down Expand Up @@ -88,24 +110,43 @@ def generate_cfg_msg(machine, text_cfgs)
# @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
Expand Down
38 changes: 28 additions & 10 deletions lib/vagrant/action/builtin/cloud_init_wait.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,37 @@ def initialize(app, env)
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
Expand Down
5 changes: 4 additions & 1 deletion lib/vagrant/action/builtin/disk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def call(env)
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)
Expand Down
109 changes: 59 additions & 50 deletions lib/vagrant/util/file_checksum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,74 @@ def update(string); end
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
# 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
@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 = ''
# 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
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
end
end

digest.hexdigest
end
digest.hexdigest
end

private
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(', ')
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
end
end

# NOTE: This class was not originally namespaced
# with the Util module so this is left for backwards
# compatibility.
FileChecksum = Vagrant::Util::FileChecksum
2 changes: 1 addition & 1 deletion lib/vagrant/util/mime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class Entity
# @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
Expand Down
3 changes: 3 additions & 0 deletions plugins/kernel_v2/config/vm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class VMConfig < Vagrant.plugin("2", :config)
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
Expand Down Expand Up @@ -87,6 +88,7 @@ def initialize
@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
Expand Down Expand Up @@ -536,6 +538,7 @@ def finalize!
@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
Expand Down
13 changes: 2 additions & 11 deletions plugins/providers/virtualbox/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,14 @@ def self.action_boot
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
Expand Down Expand Up @@ -424,7 +416,6 @@ def self.action_up
end
end

b.use EnvSet, cloud_init: true
b.use action_start
end
end
Expand Down
Loading
0