Adds initial basic support for HCP based configuration in vagrant-go. The initalization process has been updated to remove Vagrantfile parsing from the client, moving it to the runner using init jobs for the basis and the project (if there is one). Detection is done on the file based on extension for Ruby based parsing or HCP based parsing. Current HCP parsing is extremely simple and currently just a base to build off. Config components will be able to implement an `Init` function to handle receiving configuration data from a non-native source file. This will be extended to include a default approach for injecting defined data in the future. Some cleanup was done in the state around validations. Some logging adjustments were applied on the Ruby side for better behavior consistency. VirtualBox provider now caches locale detection to prevent multiple checks every time the driver is initialized.
687 lines
16 KiB
Ruby
687 lines
16 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
# Patch things to produce proto messages
|
|
require "pathname"
|
|
require "securerandom"
|
|
require "google/protobuf/wrappers_pb"
|
|
require "google/protobuf/well_known_types"
|
|
|
|
PROTO_LOGGER = Log4r::Logger.new("vagrant::protologger")
|
|
|
|
# Default proto mapping
|
|
class Object
|
|
def self.to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Class.new(name: name)
|
|
end
|
|
|
|
# simple stub so non-proto objects won't
|
|
# error if conversion is attempted
|
|
def to_ruby
|
|
self
|
|
end
|
|
|
|
def to_any
|
|
source_proto = if !self.class.ancestors.include?(Google::Protobuf::MessageExts)
|
|
to_proto
|
|
else
|
|
self
|
|
end
|
|
begin
|
|
Google::Protobuf::Any.pack(source_proto)
|
|
rescue
|
|
PROTO_LOGGER.warn("failed to any this type: #{self.class} value: #{self}")
|
|
raise
|
|
end
|
|
end
|
|
|
|
def to_proto
|
|
begin
|
|
klass = self.class.to_proto
|
|
data = Hash.new.tap do |h|
|
|
instance_variables.each do |v|
|
|
h[v.to_s.sub('@', '')] = instance_variable_get(v)
|
|
end
|
|
end
|
|
|
|
entries = data.map do |k, v|
|
|
Hashicorp::Vagrant::Sdk::Args::HashEntry.new(
|
|
key: k.to_any,
|
|
value: v.to_any,
|
|
)
|
|
end.compact
|
|
Hashicorp::Vagrant::Sdk::Config::RawRubyValue.new(
|
|
source: klass,
|
|
data: Hashicorp::Vagrant::Sdk::Args::Hash.new(entries: entries)
|
|
)
|
|
end
|
|
rescue => err
|
|
PROTO_LOGGER.warn("failed to proto #{self.class} | reason: #{err}")
|
|
raise
|
|
end
|
|
end
|
|
|
|
# Base types
|
|
class Array
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Array.new(
|
|
list: map(&:to_any)
|
|
)
|
|
end
|
|
end
|
|
|
|
class FalseClass
|
|
def to_proto
|
|
Google::Protobuf::BoolValue.new(value: false)
|
|
end
|
|
end
|
|
|
|
class Float
|
|
def to_proto
|
|
Google::Protobuf::FloatValue.new(value: self)
|
|
end
|
|
end
|
|
|
|
class Hash
|
|
def to_proto
|
|
entries = map do |k, v|
|
|
Hashicorp::Vagrant::Sdk::Args::HashEntry.new(
|
|
key: k.to_any,
|
|
value: v.to_any,
|
|
)
|
|
end
|
|
Hashicorp::Vagrant::Sdk::Args::Hash.new(entries: entries)
|
|
end
|
|
end
|
|
|
|
class Integer
|
|
def to_proto
|
|
Google::Protobuf::Int64Value.new(value: self)
|
|
end
|
|
end
|
|
|
|
class NilClass
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Null.new
|
|
end
|
|
end
|
|
|
|
class Pathname
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Path.new(
|
|
path: to_s
|
|
)
|
|
end
|
|
end
|
|
|
|
class Proc
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::ProcRef.new(
|
|
id: VagrantPlugins::CommandServe::Mappers::ProcRegistry.instance.register(self)
|
|
)
|
|
end
|
|
end
|
|
|
|
class Range
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Range.new(start: first, end: last)
|
|
end
|
|
end
|
|
|
|
class Set
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Set.new(list: to_a.to_proto)
|
|
end
|
|
end
|
|
|
|
class String
|
|
def to_proto
|
|
Google::Protobuf::StringValue.new(value: to_s)
|
|
end
|
|
end
|
|
|
|
class Symbol
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Symbol.new(str: to_s)
|
|
end
|
|
end
|
|
|
|
class TrueClass
|
|
def to_proto
|
|
Google::Protobuf::BoolValue.new(value: true)
|
|
end
|
|
end
|
|
|
|
# Complex types
|
|
class Vagrant::Plugin::V2::Config
|
|
def to_proto
|
|
data = Hash.new.tap do |h|
|
|
instance_variables.each do |v|
|
|
h[v.to_s.sub('@', '')] = instance_variable_get(v)
|
|
end
|
|
end
|
|
|
|
# Include a unique identifier for this configuration instance. This
|
|
# will allow us to identifier it later when it is decoded.
|
|
if !data.key?("_vagrant_config_identifier")
|
|
data["_vagrant_config_identifier"] = SecureRandom.uuid
|
|
end
|
|
|
|
entries = data.map do |k, v|
|
|
Hashicorp::Vagrant::Sdk::Args::HashEntry.new(
|
|
key: k.to_any,
|
|
value: v.to_any,
|
|
)
|
|
end
|
|
Hashicorp::Vagrant::Sdk::Args::ConfigData.new(
|
|
data: Hashicorp::Vagrant::Sdk::Args::Hash.new(entries: entries),
|
|
source: self.class.to_proto,
|
|
)
|
|
end
|
|
end
|
|
|
|
class Vagrant::Config::V2::Root
|
|
def to_proto
|
|
__internal_state["keys"].to_proto
|
|
end
|
|
end
|
|
|
|
class VagrantPlugins::CommandServe::Type::CommunicatorCommandArguments
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Communicator::Command.new(command: value)
|
|
end
|
|
end
|
|
|
|
class VagrantPlugins::CommandServe::Type::CommandInfo
|
|
def to_proto
|
|
flags = info.flags.map do |f|
|
|
Vagrant::Hashicorp::Sdk::Command::Flag.new(
|
|
long_name: f.long_name,
|
|
short_name: f.short_name,
|
|
description: f.description,
|
|
default_value: f.default_value,
|
|
type: f.type == :BOOL ? Vagrant::Hashicorp::Sdk::Command::Flag::Type::BOOL :
|
|
Hashicorp::Vagrant::Sdk::Command::Flag::Type::STRING
|
|
)
|
|
end
|
|
subcommands = info.subcommands.map do |s_info|
|
|
converter(s_info)
|
|
end
|
|
Hashicorp::Vagrant::Sdk::Command::CommandInfo.new(
|
|
name: info.name,
|
|
help: info.help,
|
|
synopsis: info.synopsis,
|
|
flags: flags,
|
|
subcommands: subcommands,
|
|
primary: info.primary,
|
|
)
|
|
end
|
|
end
|
|
|
|
class VagrantPlugins::CommandServe::Type::Direct
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Direct.new(
|
|
arguments: value.to_proto
|
|
)
|
|
end
|
|
end
|
|
|
|
class VagrantPlugins::CommandServe::Type::Duration
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Duration.new(
|
|
duration: value
|
|
)
|
|
end
|
|
end
|
|
|
|
class VagrantPlugins::CommandServe::Type::Folders
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Folders.new(
|
|
folders: value.to_proto
|
|
)
|
|
end
|
|
end
|
|
|
|
class VagrantPlugins::CommandServe::Type::Options
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::Options.new(
|
|
options: value.to_proto
|
|
)
|
|
end
|
|
end
|
|
|
|
class Log4r::Logger
|
|
def to_proto
|
|
Hashicorp::Vagrant::Sdk::Args::RubyLogger.new(name: fullname)
|
|
end
|
|
end
|
|
|
|
# Proto conversions
|
|
class Google::Protobuf::Any
|
|
def to_ruby
|
|
_vagrant_unany(self).to_ruby
|
|
end
|
|
end
|
|
|
|
module Google::Protobuf::MessageExts
|
|
def to_ruby
|
|
return value if self.respond_to?(:value)
|
|
|
|
raise NotImplementedError,
|
|
"#{self.class}#to_ruby has not been implemented"
|
|
end
|
|
|
|
# Convert Any proto message to actual message type
|
|
#
|
|
# @param any [Google::Protobuf::Any]
|
|
# @return [Google::Protobuf::MessageExts]
|
|
def _vagrant_unany(any)
|
|
type = _vagrant_find_type(any.type_name.split("/").last.to_s)
|
|
any.unpack(type)
|
|
end
|
|
|
|
# Get const from name
|
|
#
|
|
# @param name [String]
|
|
# @return [Class]
|
|
def _vagrant_find_type(name)
|
|
parent_module_options = []
|
|
name.to_s.split(".").inject(Object) { |memo, n|
|
|
c = memo.constants.detect { |mc| mc.to_s.downcase == n.to_s.downcase }
|
|
if c.nil?
|
|
parent_module_options.delete(memo)
|
|
parent_module_options.each do |pm|
|
|
c = pm.constants.detect { |mc| mc.to_s.downcase == n.to_s.downcase }
|
|
if !c.nil?
|
|
memo = pm
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
raise NameError,
|
|
"Failed to find constant for `#{name}'" if c.nil?
|
|
|
|
parent_module_options = memo.constants.select {
|
|
|mc| mc.to_s.downcase == n.to_s.downcase
|
|
}.map {
|
|
|mc| memo.const_get(mc)
|
|
}
|
|
memo.const_get(c)
|
|
}
|
|
end
|
|
|
|
def _vagrant_load_client(klass)
|
|
raise TypeError,
|
|
"Proto is not a valid client message type (#{self.class})" if
|
|
!respond_to?(:addr)
|
|
|
|
cid = "client+" + addr.to_s
|
|
return VagrantPlugins::CommandServe.cache.get(cid) if
|
|
VagrantPlugins::CommandServe.cache.registered?(cid)
|
|
|
|
v = klass.load(self, broker: VagrantPlugins::CommandServe.broker)
|
|
VagrantPlugins::CommandServe.cache.register(cid, v)
|
|
v
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Array
|
|
def to_ruby
|
|
list.map { |a|
|
|
val = _vagrant_unany(a).to_ruby
|
|
val.is_a?(VagrantPlugins::CommandServe::Type) ? val.value : val
|
|
}
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::BoxMetadata
|
|
# TODO(spox): should this be returning a box metadata instance instead of client?
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::BoxMetadata)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Class
|
|
def to_ruby
|
|
if name.to_s.empty?
|
|
raise NameError,
|
|
"No name defined for for class"
|
|
end
|
|
name.to_s.split("::").inject(Object) { |memo, n|
|
|
c = memo.constants.detect { |mc| mc.to_s.downcase == n.to_s.downcase }
|
|
raise NameError,
|
|
"Failed to find constant for `#{name}'" if c.nil?
|
|
memo.const_get(c)
|
|
}
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::CorePluginManager
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::CorePluginManager)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::ConfigData
|
|
def to_ruby
|
|
base_klass = source.to_ruby
|
|
if [0, -1].include?(base_klass.instance_method(:initialize).arity)
|
|
klass = base_klass
|
|
else
|
|
klass = Class.new(base_klass)
|
|
klass.class_eval("
|
|
def self.to_proto
|
|
#{base_klass.name}.to_proto
|
|
end
|
|
def self.class
|
|
#{base_klass.name}
|
|
end
|
|
def initialize
|
|
end
|
|
")
|
|
end
|
|
instance = klass.new
|
|
d = data.to_ruby
|
|
|
|
# Since we are restoring the config, if the config this
|
|
# represents was already finalized we finalize it first
|
|
# before we inject the instance variables to get as close
|
|
# to the correct original state as possible
|
|
if d.key?("__finalized")
|
|
instance.finalize!
|
|
instance._finalize!
|
|
end
|
|
|
|
# Now set our data into the instance
|
|
d.each_pair do |k, v|
|
|
instance.instance_variable_set("@#{k}", v)
|
|
end
|
|
|
|
instance
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Direct
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Type::Direct.new(
|
|
arguments: arguments.map { |arg|
|
|
val = arg.to_ruby
|
|
val.is_a?(VagrantPlugins::CommandServe::Type) ? val.value : val
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Folders
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Type::Folders.new(value: folders.to_ruby)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Guest
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::Guest)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Hash
|
|
def to_ruby
|
|
data = Hash.new
|
|
entries.each do |e|
|
|
key = _vagrant_unany(e.key).to_ruby
|
|
value = _vagrant_unany(e.value).to_ruby
|
|
data[key] = value.is_a?(VagrantPlugins::CommandServe::Type) ? value.value : value
|
|
end
|
|
|
|
data
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Host
|
|
def to_ruby
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::Host)
|
|
Vagrant::Host.new(client, nil, Vagrant.plugin("2").local_manager.host_capabilities)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::MetadataSet
|
|
def to_ruby
|
|
{}
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::NamedCapability
|
|
def to_ruby
|
|
capability.to_s.to_sym
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Null
|
|
def to_ruby
|
|
nil
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Options
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Type::Options.new(value: options.to_ruby)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Path
|
|
def to_ruby
|
|
Pathname.new(path.to_s)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::ProcRef
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Mappers::ProcRegistry.instance.fetch(id)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Project
|
|
def to_ruby
|
|
cid = "environment"+addr.to_s
|
|
return VagrantPlugins::CommandServe.cache.get(cid) if
|
|
VagrantPlugins::CommandServe.cache.registered?(cid)
|
|
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::Project)
|
|
ui = client.ui.to_ui
|
|
|
|
env = Vagrant::Environment.new(client: client, ui: ui)
|
|
VagrantPlugins::CommandServe.cache.register(cid, env)
|
|
env
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Provisioner
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::Provisioner)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Provider
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::Provider)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Range
|
|
def to_ruby
|
|
Range.new(self.start, self.end)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Config::RawRubyValue
|
|
def to_ruby
|
|
base_klass = source.to_ruby
|
|
if [0, -1].include?(base_klass.instance_method(:initialize).arity)
|
|
klass = base_klass
|
|
else
|
|
klass = Class.new(base_klass)
|
|
klass.class_eval("
|
|
def self.class
|
|
#{base_klass.name}
|
|
end
|
|
def initialize
|
|
end
|
|
")
|
|
end
|
|
|
|
instance = klass.new
|
|
d = data.to_ruby
|
|
|
|
d.each_pair do |k, v|
|
|
instance.instance_variable_set("@#{k}", v)
|
|
end
|
|
|
|
instance
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::RubyLogger
|
|
def to_ruby
|
|
Log4r::Logger.new(name)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Set
|
|
def to_ruby
|
|
::Set.new(list.to_ruby)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::StateBag
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::StateBag)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::SyncedFolder
|
|
def to_ruby
|
|
cid = "syncedfolder+" + addr.to_s
|
|
return VagrantPlugins::CommandServe.cache.get(cid) if
|
|
VagrantPlugins::CommandServe.cache.registered?(cid)
|
|
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::SyncedFolder)
|
|
fld = Vagrant::Plugin::Remote::SyncedFolder.new(client: client)
|
|
VagrantPlugins::CommandServe.cache.register(cid, fld)
|
|
fld
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Symbol
|
|
def to_ruby
|
|
str.to_sym
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Target
|
|
def to_ruby
|
|
cid = "machine+" + addr.to_s
|
|
return VagrantPlugins::CommandServe.cache.get(cid) if
|
|
VagrantPlugins::CommandServe.cache.registered?(cid)
|
|
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::Target)
|
|
env = client.environment.to_ruby
|
|
machine = env.machine(client.name.to_sym, client.provider_name.to_sym)
|
|
VagrantPlugins::CommandServe.cache.register(cid, machine)
|
|
machine
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Target::Machine
|
|
def to_ruby
|
|
cid = "machine+" + addr.to_s
|
|
return VagrantPlugins::CommandServe.cache.get(cid) if
|
|
VagrantPlugins::CommandServe.cache.registered?(cid)
|
|
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::Target::Machine)
|
|
m = Vagrant::Machine.new(client: client)
|
|
VagrantPlugins::CommandServe.cache.register(cid, m)
|
|
m
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Target::Machine::State
|
|
def to_ruby
|
|
Vagrant::MachineState.new(
|
|
id.to_sym, short_description, long_description
|
|
)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::TargetIndex
|
|
def to_ruby
|
|
_vagrant_load_client(VagrantPlugins::CommandServe::Client::TargetIndex)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::TimeDuration
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Type::Duration.new(value: duration)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::TerminalUI
|
|
def to_ruby
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::Terminal)
|
|
Vagrant::UI::Remote.new(client)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Args::Vagrantfile
|
|
def to_ruby
|
|
client = _vagrant_load_client(VagrantPlugins::CommandServe::Client::Vagrantfile)
|
|
Vagrant::Vagrantfile.new(client: client)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Command::Arguments
|
|
def to_ruby
|
|
_args = args.to_a
|
|
_flags = Hash.new.tap do |flgs|
|
|
flags.each do |f|
|
|
if f.type == :BOOL
|
|
flgs[f.name] = f.bool
|
|
else
|
|
flgs[f.name] = f.string
|
|
end
|
|
end
|
|
end
|
|
VagrantPlugins::CommandServe::Type::CommandArguments.new(args: _args, flags: _flags)
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Command::CommandInfo
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Type::CommandInfo.new(
|
|
name: name,
|
|
help: help,
|
|
synopsis: synopsis,
|
|
primary: primary,
|
|
).tap do |c|
|
|
flags.each do |f|
|
|
c.add_flag(
|
|
long_name: f.long_name,
|
|
short_name: f.short_name,
|
|
description: f.description,
|
|
default_value: f.default_value,
|
|
type: f.type,
|
|
)
|
|
end
|
|
subcommands.each do |s_proto|
|
|
c.add_subcommand(s_proto.to_ruby)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class Hashicorp::Vagrant::Sdk::Communicator::Command
|
|
def to_ruby
|
|
VagrantPlugins::CommandServe::Type::CommunicatorCommandArguments.new(value: command)
|
|
end
|
|
end
|