Merge pull request #11706 from briancain/feature/cloud-init_setup_action
Initial commit of cloud_init setup action
This commit is contained in:
commit
d99dacf44f
@ -14,6 +14,7 @@ module Vagrant
|
||||
autoload :BoxRemove, "vagrant/action/builtin/box_remove"
|
||||
autoload :Call, "vagrant/action/builtin/call"
|
||||
autoload :CleanupDisks, "vagrant/action/builtin/cleanup_disks"
|
||||
autoload :CloudInitSetup, "vagrant/action/builtin/cloud_init_setup"
|
||||
autoload :Confirm, "vagrant/action/builtin/confirm"
|
||||
autoload :ConfigValidate, "vagrant/action/builtin/config_validate"
|
||||
autoload :Delayed, "vagrant/action/builtin/delayed"
|
||||
|
||||
127
lib/vagrant/action/builtin/cloud_init_setup.rb
Normal file
127
lib/vagrant/action/builtin/cloud_init_setup.rb
Normal file
@ -0,0 +1,127 @@
|
||||
require 'mime'
|
||||
require 'tmpdir'
|
||||
|
||||
module Vagrant
|
||||
module Action
|
||||
module Builtin
|
||||
class CloudInitSetup
|
||||
TEMP_PREFIX = "vagrant-cloud-init-iso-temp-".freeze
|
||||
|
||||
def initialize(app, env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new("vagrant::action::builtin::cloudinit::setup")
|
||||
end
|
||||
|
||||
def call(env)
|
||||
machine = env[:machine]
|
||||
|
||||
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}" }
|
||||
|
||||
write_cfg_iso(machine, env, user_data, meta_data)
|
||||
end
|
||||
|
||||
# Continue On
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @param [Vagrant::Environment] env
|
||||
# @param [Array<#VagrantPlugins::Kernel_V2::VagrantConfigCloudInit>] user_data_cfgs
|
||||
# @return [MIME::Text] user_data
|
||||
def setup_user_data(machine, env, user_data_cfgs)
|
||||
machine.ui.info(I18n.t("vagrant.actions.vm.cloud_init_user_data_setup"))
|
||||
|
||||
text_cfgs = user_data_cfgs.map { |cfg| read_text_cfg(machine, cfg) }
|
||||
|
||||
user_data = generate_cfg_msg(machine, text_cfgs)
|
||||
user_data
|
||||
end
|
||||
|
||||
# Reads an individual cloud_init config and stores its contents and the
|
||||
# content_type as a MIME text
|
||||
#
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @param [VagrantPlugins::Kernel_V2::VagrantConfigCloudInit] cfg
|
||||
# @return [MIME::Text] text_msg
|
||||
def read_text_cfg(machine, cfg)
|
||||
if cfg.path
|
||||
text = File.read(Pathname.new(cfg.path).expand_path(machine.env.root_path))
|
||||
else
|
||||
text = cfg.inline
|
||||
end
|
||||
|
||||
# Note: content_type must remove the leading `text/` because
|
||||
# the MIME::Text initializer hardcodes `text/` already to the type.
|
||||
# We assume content_type is correct due to the validation step
|
||||
# in VagrantConfigCloudInit.
|
||||
content_type = cfg.content_type.split('/', 2).last
|
||||
text_msg = MIME::Text.new(text, content_type)
|
||||
|
||||
text_msg
|
||||
end
|
||||
|
||||
# Combines all known cloud_init configs into a multipart mixed MIME text
|
||||
# message
|
||||
#
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @param [Array<MIME::Text>] text_msg - One or more text configs
|
||||
# @return [MIME::Multipart::Mixed] msg
|
||||
def generate_cfg_msg(machine, text_cfgs)
|
||||
msg = MIME::Multipart::Mixed.new
|
||||
|
||||
text_cfgs.each do |c|
|
||||
msg.add(c)
|
||||
end
|
||||
|
||||
msg
|
||||
end
|
||||
|
||||
# Writes the contents of the guests cloud_init config to a tmp
|
||||
# dir and passes that source directory along to the host cap to be
|
||||
# written to an iso
|
||||
#
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @param [MIME::Multipart::Mixed] user_data
|
||||
# @param [Hash] meta_data
|
||||
def write_cfg_iso(machine, env, user_data, meta_data)
|
||||
iso_path = nil
|
||||
|
||||
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_s) }
|
||||
|
||||
iso_path = env[:env].host.capability(:create_iso, env[:env],
|
||||
source_dir, volume_id: "cidata")
|
||||
|
||||
attach_disk_config(machine, env, iso_path)
|
||||
ensure
|
||||
FileUtils.remove_entry(source_dir)
|
||||
end
|
||||
else
|
||||
raise Errors::CreateIsoHostCapNotFound
|
||||
end
|
||||
end
|
||||
|
||||
# Adds a new :dvd disk config with the given iso_path to be attached
|
||||
# to the guest later
|
||||
#
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @param [Vagrant::Environment] env
|
||||
# @param [String] iso_path
|
||||
def attach_disk_config(machine, env, iso_path)
|
||||
@logger.info("Adding cloud_init iso '#{iso_path}' to disk config")
|
||||
machine.config.vm.disk :dvd, file: iso_path, name: "vagrant-cloud_init-disk"
|
||||
machine.config.vm.disks.each { |d| d.finalize! if d.type == :dvd && d.file == iso_path }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -340,6 +340,10 @@ module Vagrant
|
||||
error_key(:corrupt_machine_index)
|
||||
end
|
||||
|
||||
class CreateIsoHostCapNotFound < VagrantError
|
||||
error_key(:create_iso_host_cap_not_found)
|
||||
end
|
||||
|
||||
class DarwinMountFailed < VagrantError
|
||||
error_key(:darwin_mount_failed)
|
||||
end
|
||||
|
||||
@ -79,6 +79,7 @@ module VagrantPlugins
|
||||
b.use ForwardPorts
|
||||
b.use SetHostname
|
||||
b.use SaneDefaults
|
||||
b.use CloudInitSetup
|
||||
b.use CleanupDisks
|
||||
b.use Disk
|
||||
b.use Customize, "pre-boot"
|
||||
|
||||
@ -832,6 +832,9 @@ en:
|
||||
they'll have to be destroyed manually.
|
||||
|
||||
Path: %{path}
|
||||
create_iso_host_cap_not_found: |-
|
||||
Vagrant cannot create an iso due to the host capability for creating isos not existing.
|
||||
Vagrant will now exit.
|
||||
destroy_requires_force: |-
|
||||
Destroy doesn't have a TTY to ask for confirmation. Please pass the
|
||||
`--force` flag to force a destroy, otherwise attach a TTY so that
|
||||
@ -2324,6 +2327,8 @@ en:
|
||||
create_master:
|
||||
failure: |-
|
||||
Failed to create lock-file for master VM creation for box %{box}.
|
||||
cloud_init_user_data_setup: |-
|
||||
Preparing cloud_init iso based on :user_data configuration...
|
||||
customize:
|
||||
failure: |-
|
||||
A customization command failed:
|
||||
|
||||
124
test/unit/vagrant/action/builtin/cloud_init_setup_test.rb
Normal file
124
test/unit/vagrant/action/builtin/cloud_init_setup_test.rb
Normal file
@ -0,0 +1,124 @@
|
||||
require File.expand_path("../../../../base", __FILE__)
|
||||
|
||||
describe Vagrant::Action::Builtin::CloudInitSetup do
|
||||
let(:app) { lambda { |env| } }
|
||||
let(:vm) { double("vm", disk: disk, disks: disks) }
|
||||
let(:disk) { double("disk") }
|
||||
let(:disks) { double("disk") }
|
||||
let(:config) { double("config", vm: vm) }
|
||||
let(:provider) { double("provider") }
|
||||
let(:machine) { double("machine", config: config, provider: provider, name: "machine",
|
||||
provider_name: "provider", data_dir: Pathname.new("/fake/dir"),
|
||||
ui: ui, env: machine_env, id: "123-456-789") }
|
||||
let(:host) { double("host") }
|
||||
let(:machine_env) { double("machine_env", root_path: "root", host: host) }
|
||||
let(:env) { { ui: ui, machine: machine, env: machine_env} }
|
||||
|
||||
let(:ui) { double("ui", info: true, warn: true) }
|
||||
|
||||
let(:cfg) { double("cfg", type: :user_data, content_type: "text/cloud-config",
|
||||
path: "my/path", inline: nil) }
|
||||
let(:cfg_inline) { double("cfg", type: :user_data, content_type: "text/cloud-config",
|
||||
inline: "data: true", path: nil) }
|
||||
let(:cloud_init_configs) { [cfg, cfg_inline] }
|
||||
|
||||
let(:text_cfgs) { [MIME::Text.new("data: true", "cloud-config"),
|
||||
MIME::Text.new("data: false", "cloud-config") ] }
|
||||
|
||||
let(:meta_data) { { "instance-id": "i-123456789" } }
|
||||
|
||||
|
||||
let(:subject) { described_class.new(app, env) }
|
||||
|
||||
describe "#call" do
|
||||
it "calls setup_user_data if cloud_init config present" do
|
||||
allow(vm).to receive(:cloud_init_configs).and_return(cloud_init_configs)
|
||||
|
||||
expect(app).to receive(:call).with(env).ordered
|
||||
|
||||
expect(subject).to receive(:setup_user_data).and_return(true)
|
||||
expect(subject).to receive(:write_cfg_iso).and_return(true)
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "continues on if no cloud_init config present" do
|
||||
allow(vm).to receive(:cloud_init_configs).and_return([])
|
||||
|
||||
expect(app).to receive(:call).with(env).ordered
|
||||
|
||||
expect(subject).not_to receive(:setup_user_data)
|
||||
expect(subject).not_to receive(:write_cfg_iso)
|
||||
expect(subject).not_to receive(:attack_disk_config)
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#setup_user_data" do
|
||||
it "builds a MIME message and prepares a disc to be attached" do
|
||||
expect(subject).to receive(:read_text_cfg).twice
|
||||
|
||||
expect(subject).to receive(:generate_cfg_msg)
|
||||
|
||||
subject.setup_user_data(machine, env, cloud_init_configs)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#read_text_cfg" do
|
||||
let(:cfg_text) { "config: true" }
|
||||
|
||||
it "takes a text cfg path and saves it as a MIME text message" do
|
||||
allow(File).to receive(:read).and_return(cfg_text)
|
||||
|
||||
expect(MIME::Text).to receive(:new).with(cfg_text, "cloud-config")
|
||||
subject.read_text_cfg(machine, cfg)
|
||||
end
|
||||
|
||||
it "takes a text cfg inline string and saves it as a MIME text message" do
|
||||
expect(MIME::Text).to receive(:new).with("data: true", "cloud-config")
|
||||
subject.read_text_cfg(machine, cfg_inline)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#generate_cfg_msg" do
|
||||
it "creates a miltipart mixed message of combined configs" do
|
||||
message = subject.generate_cfg_msg(machine, text_cfgs)
|
||||
expect(message).to be_a(MIME::Multipart::Mixed)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#write_cfg_iso" do
|
||||
let(:iso_path) { Pathname.new("fake/iso/path") }
|
||||
let(:source_dir) { Pathname.new("fake/source/path") }
|
||||
|
||||
it "raises an error if the host capability is not supported" do
|
||||
message = subject.generate_cfg_msg(machine, text_cfgs)
|
||||
allow(host).to receive(:capability?).with(:create_iso).and_return(false)
|
||||
|
||||
expect{subject.write_cfg_iso(machine, env, message, {})}.to raise_error(Vagrant::Errors::CreateIsoHostCapNotFound)
|
||||
end
|
||||
|
||||
it "creates a temp dir with the cloud_init config and generates an iso" do
|
||||
message = subject.generate_cfg_msg(machine, text_cfgs)
|
||||
allow(host).to receive(:capability?).with(:create_iso).and_return(true)
|
||||
allow(Dir).to receive(:mktmpdir).and_return(source_dir)
|
||||
expect(File).to receive(:open).with("#{source_dir}/user-data", 'w').and_return(true)
|
||||
expect(File).to receive(:open).with("#{source_dir}/meta-data", 'w').and_return(true)
|
||||
expect(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)
|
||||
allow(host).to receive(:capability).with(:create_iso, machine_env, source_dir, volume_id: "cidata").and_return(iso_path)
|
||||
expect(vm.disks).to receive(:each)
|
||||
|
||||
subject.write_cfg_iso(machine, env, message, {})
|
||||
end
|
||||
end
|
||||
|
||||
describe "#attach_disk_config" do
|
||||
let(:iso_path) { Pathname.new("fake/iso/path") }
|
||||
|
||||
it "creates a new disk config based on the iso_path" do
|
||||
expect(vm.disks).to receive(:each)
|
||||
subject.attach_disk_config(machine, env, iso_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -19,21 +19,22 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency "childprocess", "~> 3.0.0"
|
||||
s.add_dependency "ed25519", "~> 1.2.4"
|
||||
s.add_dependency "erubis", "~> 2.7.0"
|
||||
s.add_dependency "hashicorp-checkpoint", "~> 0.1.5"
|
||||
s.add_dependency "i18n", "~> 1.8"
|
||||
s.add_dependency "listen", "~> 3.1.5"
|
||||
s.add_dependency "hashicorp-checkpoint", "~> 0.1.5"
|
||||
s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11"
|
||||
s.add_dependency "net-ssh", "~> 5.2.0"
|
||||
s.add_dependency "net-sftp", "~> 2.1"
|
||||
s.add_dependency "mime", "~> 0.4.4"
|
||||
s.add_dependency "net-scp", "~> 1.2.0"
|
||||
s.add_dependency "net-sftp", "~> 2.1"
|
||||
s.add_dependency "net-ssh", "~> 5.2.0"
|
||||
s.add_dependency "rb-kqueue", "~> 0.2.0"
|
||||
s.add_dependency "rest-client", ">= 1.6.0", "< 3.0"
|
||||
s.add_dependency "rubyzip", "~> 2.0"
|
||||
s.add_dependency "vagrant_cloud", "~> 2.0.3"
|
||||
s.add_dependency "wdm", "~> 0.1.0"
|
||||
s.add_dependency "winrm", ">= 2.3.4", "< 3.0"
|
||||
s.add_dependency "winrm-fs", ">= 1.3.4", "< 2.0"
|
||||
s.add_dependency "winrm-elevated", ">= 1.2.1", "< 2.0"
|
||||
s.add_dependency "vagrant_cloud", "~> 2.0.3"
|
||||
s.add_dependency "winrm-fs", ">= 1.3.4", "< 2.0"
|
||||
|
||||
# NOTE: The ruby_dep gem is an implicit dependency from the listen gem. Later versions
|
||||
# of the ruby_dep gem impose an aggressive constraint on the required ruby version (>= 2.2.5).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user