Provisioner Plugins

This commit is contained in:
Paul Hinze 2022-02-09 19:15:01 -06:00
parent 8b6ef27a0f
commit ff86d86ac8
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0
18 changed files with 324 additions and 20 deletions

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.18.0
// protoc v3.15.8
// source: vagrant-ruby/builtin/myplugin/proto/plugin.proto
package proto

View File

@ -1,13 +1,14 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.19.4
// protoc v3.15.8
// source: proto/ruby_vagrant/ruby-server.proto
package ruby_vagrant
import (
vagrant_plugin_sdk "github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
_ "google.golang.org/genproto/googleapis/rpc/errdetails"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/known/anypb"
@ -321,6 +322,8 @@ var file_proto_ruby_vagrant_ruby_server_proto_rawDesc = []byte{
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x0c, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0x49, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73,

View File

@ -6,6 +6,7 @@ option go_package = "github.com/hashicorp/vagrant/internal/server/proto/ruby_vag
import "google/protobuf/empty.proto";
import "google/protobuf/any.proto";
import "google/rpc/error_details.proto";
import "plugin.proto";

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.19.4
// protoc v3.15.8
// source: proto/vagrant_server/server.proto
package vagrant_server
@ -9610,7 +9610,8 @@ func (*GetJobStreamResponse_Terminal_Event_Raw_) isGetJobStreamResponse_Terminal
func (*GetJobStreamResponse_Terminal_Event_Table_) isGetJobStreamResponse_Terminal_Event_Event() {}
func (*GetJobStreamResponse_Terminal_Event_StepGroup_) isGetJobStreamResponse_Terminal_Event_Event() {}
func (*GetJobStreamResponse_Terminal_Event_StepGroup_) isGetJobStreamResponse_Terminal_Event_Event() {
}
func (*GetJobStreamResponse_Terminal_Event_Step_) isGetJobStreamResponse_Terminal_Event_Event() {}

View File

@ -9,6 +9,7 @@ module Vagrant
autoload :Plugin, "vagrant/plugin/remote/plugin"
autoload :Provider, "vagrant/plugin/remote/provider"
autoload :Push, "vagrant/plugin/remote/push"
autoload :Provisioner, "vagrant/plugin/remote/provisioner"
autoload :SyncedFolder, "vagrant/plugin/remote/synced_folder"
end
end

View File

@ -190,20 +190,20 @@ module Vagrant
end
end
# def provisioners
# return real_manager.provisioners if plugin_manager.nil?
#
# Registry.new.tap do |result|
# plugin_manager.list_plugins(:provisioner).each do |plg|
# sf_class = Class.new(Remote::Provisioner, &WRAPPER_CLASS)
# sf_class.plugin_name = plg[:name]
# sf_class.type = plg[:type]
# result.register(plg[:name].to_sym) do
# sf_class
# end
# end
# end
# end
def provisioners
return real_manager.provisioners if plugin_manager.nil?
Registry.new.tap do |result|
plugin_manager.list_plugins(:provisioner).each do |plg|
sf_class = Class.new(Remote::Provisioner, &WRAPPER_CLASS)
sf_class.plugin_name = plg[:name]
sf_class.type = plg[:type]
result.register(plg[:name].to_sym) do
sf_class
end
end
end
end
def pushes
return real_manager.pushes if plugin_manager.nil?

View File

@ -0,0 +1,47 @@
module Vagrant
module Plugin
module Remote
class Provisioner < V2::Provisioner
# Add an attribute accesor for the client
attr_accessor :client
# Provisioner plugins receive machine and config on init that they
# expect to set as instance variables and use later. When we're in
# server mode, this instance is only a client, and the local plugin
# is going to be reinstantiated over on the server side. We will still
# needs these two pieces of state on the server side, so in order to
# get them over there, we tack them onto the front as args for each
# client method call. The server will then peel off those two args
# and use them to initialize the plugin in local mode before it
# dispatches the method call.
#
# @see Vagrant::Plugin::V2::Provisioner#initialize the local init
# method we're overriding
# @see Vagrant::CommandServe::Service::ProvisionerService where we
# pop off the two args and reinitialize a local plugin
def initialize(machine, config, **opts)
@_machine = machine
@_config = config
if opts[:client].nil?
raise ArgumentError,
"Remote client is required for `#{self.class.name}`"
end
@client = opts[:client]
super(machine, config)
end
def cleanup
client.provision(@_machine, @_config)
end
def configure(root_config)
client.configure(@_machine, @_config, root_config)
end
def provision
client.provision(@_machine, @_config)
end
end
end
end
end

View File

@ -76,6 +76,20 @@ module Vagrant
def method_missing(name, *args, &block)
return super if @__finalized
# There are a few scenarios where ruby will attempt to implicity
# coerce a given object into a certain type. Configs can end up
# in some of these scenarios when they're being shipped around in
# callbacks with splats. If method_missing allows these methods to be
# called but continues to return Config back, Ruby will raise a
# TypeError. Doing the normal thing of raising NoMethodError allows
# Config to behave normally as its being passed through splats.
#
# For a bit more detail and some keywords for further searching, see:
# https://ruby-doc.org/core-2.7.2/doc/implicit_conversion_rdoc.html
if [:to_hash, :to_ary].include?(name)
return super
end
name = name.to_s
name = name[0...-1] if name.end_with?("=")

View File

@ -5,6 +5,7 @@ require 'google/protobuf'
require 'google/protobuf/empty_pb'
require 'google/protobuf/any_pb'
require 'google/rpc/error_details_pb'
require 'plugin_pb'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_file("proto/ruby_vagrant/ruby-server.proto", :syntax => :proto3) do

View File

@ -13,6 +13,7 @@ module VagrantPlugins
autoload :PluginManager, Vagrant.source_root.join("plugins/commands/serve/client/plugin_manager").to_s
autoload :Project, Vagrant.source_root.join("plugins/commands/serve/client/project").to_s
autoload :Provider, Vagrant.source_root.join("plugins/commands/serve/client/provider").to_s
autoload :Provisioner, Vagrant.source_root.join("plugins/commands/serve/client/provisioner").to_s
autoload :Push, Vagrant.source_root.join("plugins/commands/serve/client/push").to_s
autoload :Target, Vagrant.source_root.join("plugins/commands/serve/client/target").to_s
autoload :Terminal, Vagrant.source_root.join("plugins/commands/serve/client/terminal").to_s

View File

@ -0,0 +1,47 @@
module VagrantPlugins
module CommandServe
class Client
class Provisioner < Client
prepend Util::ClientSetup
prepend Util::HasLogger
include Util::HasSeeds::Client
def cleanup_func
spec = client.cleanup_spec(Empty.new)
cb = proc do |args|
client.cleanup(args)
end
[spec, cb]
end
def cleanup(machine, config)
run_func(machine, config)
end
def configure_func
spec = client.configure_spec(Empty.new)
cb = proc do |args|
client.configure(args)
end
[spec, cb]
end
def configure(machine, config, root_config)
run_func(machine, config, root_config, {})
end
def provision_func
spec = client.provision_spec(Empty.new)
cb = proc do |args|
client.provision(args)
end
[spec, cb]
end
def provision(machine, config)
run_func(machine, config)
end
end
end
end
end

View File

@ -98,7 +98,7 @@ module VagrantPlugins
[Service::InternalService, Service::ProviderService, Service::GuestService,
Service::HostService, Service::CommandService, Service::SyncedFolderService,
Service::CommunicatorService, Service::PushService,
Service::CommunicatorService, Service::PushService, Service::ProvisionerService,
Broker::Streamer].each do |service_klass|
service = service_klass.new(broker: broker)
s.handle(service)

View File

@ -363,6 +363,7 @@ require Vagrant.source_root.join("plugins/commands/serve/mappers/pathname.rb").t
require Vagrant.source_root.join("plugins/commands/serve/mappers/plugin_manager.rb").to_s
require Vagrant.source_root.join("plugins/commands/serve/mappers/project.rb").to_s
require Vagrant.source_root.join("plugins/commands/serve/mappers/provider.rb").to_s
require Vagrant.source_root.join("plugins/commands/serve/mappers/provisioner.rb").to_s
require Vagrant.source_root.join("plugins/commands/serve/mappers/push.rb").to_s
require Vagrant.source_root.join("plugins/commands/serve/mappers/state_bag.rb").to_s
require Vagrant.source_root.join("plugins/commands/serve/mappers/synced_folder.rb").to_s

View File

@ -0,0 +1,49 @@
module VagrantPlugins
module CommandServe
class Mappers
# Build a guest client from a proto instance
class ProvisionerFromProto < Mapper
def initialize
inputs = [].tap do |i|
i << Input.new(type: SDK::Args::Provisioner)
i << Input.new(type: Broker)
end
super(inputs: inputs, output: Client::Provisioner, func: method(:converter))
end
def converter(proto, broker)
Client::Provisioner.load(proto, broker: broker)
end
end
class GeneralConfigFromSpec < Mapper
def initialize
inputs = [].tap do |i|
i << Input.new(type: SDK::FuncSpec::Value) { |arg|
arg.type == "hashicorp.vagrant.sdk.Vagrantfile.GeneralConfig" &&
!arg&.value&.value.nil?
}
end
super(inputs: inputs, output: SDK::Vagrantfile::GeneralConfig, func: method(:converter))
end
def converter(proto)
proto.value.unpack(SDK::Vagrantfile::GeneralConfig)
end
end
class ConfigToProto < Mapper
def initialize
inputs = [].tap do |i|
i << Input.new(type: Vagrant::Plugin::V2::Config)
end
super(inputs: inputs, output: SDK::Vagrantfile::GeneralConfig, func: method(:converter))
end
def converter(config)
config.to_proto(config.class.to_s)
end
end
end
end
end

View File

@ -10,6 +10,7 @@ module VagrantPlugins
autoload :HostService, Vagrant.source_root.join("plugins/commands/serve/service/host_service").to_s
autoload :InternalService, Vagrant.source_root.join("plugins/commands/serve/service/internal_service").to_s
autoload :ProviderService, Vagrant.source_root.join("plugins/commands/serve/service/provider_service").to_s
autoload :ProvisionerService, Vagrant.source_root.join("plugins/commands/serve/service/provisioner_service").to_s
autoload :SyncedFolderService, Vagrant.source_root.join("plugins/commands/serve/service/synced_folder_service").to_s
autoload :PushService, Vagrant.source_root.join("plugins/commands/serve/service/push_service").to_s

View File

@ -0,0 +1,130 @@
module VagrantPlugins
module CommandServe
module Service
class ProvisionerService < Hashicorp::Vagrant::Sdk::ProvisionerService::Service
prepend Util::HasMapper
prepend Util::HasBroker
prepend Util::HasLogger
include Util::ServiceInfo
include Util::HasSeeds::Service
include Util::ExceptionTransformer
def cleanup(req, ctx)
machine, plugin_config = _process_args(req)
provisioner =_lookup_or_instantiate_provisioner(req, ctx, machine, plugin_config)
provisioner.cleanup
end
def cleanup_spec(*_)
SDK::FuncSpec.new(
name: "cleanup_spec",
args: [
SDK::FuncSpec::Value.new(
type: "hashicorp.vagrant.sdk.Args.Target.Machine",
name: "",
),
SDK::FuncSpec::Value.new(
type: "hashicorp.vagrant.sdk.Vagrantfile.GeneralConfig",
name: "",
)
],
result: [],
)
end
def configure(req, ctx)
machine, plugin_config = _process_args(req)
provisioner =_lookup_or_instantiate_provisioner(req, ctx, machine, plugin_config)
provisioner.configure(machine.config)
Empty.new
end
def configure_spec(*_)
SDK::FuncSpec.new(
name: "configure_spec",
args: [
SDK::FuncSpec::Value.new(
type: "hashicorp.vagrant.sdk.Args.Target.Machine",
name: "",
),
SDK::FuncSpec::Value.new(
type: "hashicorp.vagrant.sdk.Vagrantfile.GeneralConfig",
name: "",
)
],
result: [],
)
end
def provision(req, ctx)
machine, plugin_config = _process_args(req)
provisioner =_lookup_or_instantiate_provisioner(req, ctx, machine, plugin_config)
provisioner.provision
Empty.new
end
def provision_spec(*_)
SDK::FuncSpec.new(
name: "provision_spec",
args: [
SDK::FuncSpec::Value.new(
type: "hashicorp.vagrant.sdk.Args.Target.Machine",
name: "",
),
SDK::FuncSpec::Value.new(
type: "hashicorp.vagrant.sdk.Vagrantfile.GeneralConfig",
name: "",
)
],
result: [],
)
end
# All of the methods here take the same args, so they have the same
# args processing which can be shared
#
# @param req the request
# @return [Array(Vagrant::Machine, Vagrant::Plugin::V2::Config)]
def _process_args(req)
machine, config = mapper.funcspec_map(
req, mapper, broker, expect: [
Vagrant::Machine, SDK::Vagrantfile::GeneralConfig
]
)
config_klass = config.type.split('::').inject(Kernel) { |memo, obj|
memo.const_get(obj)
}
config_data = config.config.unpack(Google::Protobuf::Struct).to_h
plugin_config = config_klass.new
plugin_config.set_options(config_data)
return machine, plugin_config
end
def _lookup_or_instantiate_provisioner(req, ctx, machine, plugin_config)
@_provisioner_cache ||= {}
key = ctx.metadata["plugin_name"]
if @_provisioner_cache[key] != nil
return @_provisioner_cache[key]
else
plugins = Vagrant.plugin("2").local_manager.provisioners
with_plugin(ctx, plugins, broker: broker) do |plugin|
provisioner = plugin.new(machine, plugin_config)
@_provisioner_cache[key] = provisioner
return provisioner
end
end
end
end
end
end
end

View File

@ -35,7 +35,7 @@ module VagrantPlugins
def with_plugin(context, plugins, broker:, &block)
with_info(context, broker: broker) do |info|
plugin_name = info.plugin_name
plugin = plugins[plugin_name.to_s.to_sym].to_a.first
plugin = Array(plugins[plugin_name.to_s.to_sym]).first
if !plugin
raise NameError, "Failed to locate plugin named #{plugin_name}"
end

View File

@ -72,5 +72,12 @@ describe Vagrant::Plugin::V2::Config do
expect { subject.i_should_not_exist }.
to raise_error(NoMethodError)
end
it "should survive being the last arg to a method that captures kwargs without a ruby conversion error" do
arg_capturer = lambda { |*args, **kwargs| }
expect {
arg_capturer.call(subject)
}.to_not raise_error
end
end
end