Allow go push plugins to use config from Vagrantfile
* Populate push configs when parsing the vagrantfile
* Allow untyped configs to be shipped over GRPC
* In our demo plugin, walk the vagrantfile and snag the config
Example Vagrantfile that works with the demo plugin:
```ruby
Vagrant.configure("2") do |config|
config.push.define "myplugin" do |push|
push.coolkey = "coolvalue"
push.alist = ["so", "many", "items"]
push.ahash = { "hashkey" => "hashvalue" }
end
end
```
This commit is contained in:
parent
a841da4fc6
commit
92c345b42d
@ -1,23 +1,84 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/core"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
// This is a push strategy that provides encouragement for the code you push
|
||||
// Encouragement is a push strategy that provides encouragement for the code
|
||||
// you push. Everybody could use some encouragement sometimes!
|
||||
type Encouragement struct{}
|
||||
|
||||
func (e *Encouragement) PushFunc() interface{} {
|
||||
return e.Push
|
||||
}
|
||||
|
||||
// Push runs this is the first ever Golang push plugin!
|
||||
func (e *Encouragement) Push(ui terminal.UI, proj core.Project) error {
|
||||
ui.Output("You've invoked a push plugin written in Go! Great work!")
|
||||
|
||||
pushConfig, err := findPushConfig(proj, "myplugin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pushConfig != nil {
|
||||
ui.Output("Look a this nice config you sent along too!")
|
||||
ui.Output("We'll print it as JSON for fun:")
|
||||
|
||||
config, err := unpackConfig(pushConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonConfig, err := json.MarshalIndent(config, " ", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ui.Output(" %s", jsonConfig)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findPushConfig finds the relevant PushConfig for the name given.
|
||||
//
|
||||
// For now, there are no config related helpers, so each push plugin needs to
|
||||
// walk its way down to its relevant config in the Vagrantfile.
|
||||
func findPushConfig(proj core.Project, name string) (*vagrant_plugin_sdk.Vagrantfile_PushConfig, error) {
|
||||
v, err := proj.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range v.GetPushConfigs() {
|
||||
if p.GetName() == name {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// unpackConfig takes a PushConfig and unpack the underlying map of config
|
||||
//
|
||||
// For now, there are no config related helpers, so each push plugin needs to
|
||||
// unpack from a generic struct into whatever types it might need. For this
|
||||
// demo plugin we're just leaving it untyped.
|
||||
func unpackConfig(pc *vagrant_plugin_sdk.Vagrantfile_PushConfig) (map[string]interface{}, error) {
|
||||
gc := pc.GetConfig()
|
||||
s := &structpb.Struct{}
|
||||
err := ptypes.UnmarshalAny(gc.GetConfig(), s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.AsMap(), nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ component.Push = (*Encouragement)(nil)
|
||||
)
|
||||
|
||||
@ -53,8 +53,8 @@ type Project struct {
|
||||
ui terminal.UI
|
||||
}
|
||||
|
||||
func (b *Project) Config() *vagrant_plugin_sdk.Vagrantfile_Vagrantfile {
|
||||
return b.project.Configuration
|
||||
func (b *Project) Config() (*vagrant_plugin_sdk.Vagrantfile_Vagrantfile, error) {
|
||||
return b.project.Configuration, nil
|
||||
}
|
||||
|
||||
// UI implements core.Project
|
||||
|
||||
@ -7,11 +7,17 @@ module Vagrant
|
||||
LOG = Log4r::Logger.new("vagrant::config::v2::dummy_config")
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
# If a DummyConfig ends up as a last arg in a situation where it's
|
||||
# being passed through a method with *kwargs, ruby 2.x will try to
|
||||
# implicitly convert it into a hash. If it responds to to_hash but
|
||||
# does not return an actual hash, Ruby gets mad.
|
||||
if name == :to_hash
|
||||
# There are a few scenarios where ruby will attempt to implicity
|
||||
# coerce a given object into a certain type. DummyConfigs 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 DummyConfig back, Ruby will raise a
|
||||
# TypeError. Doing the normal thing of raising NoMethodError allows
|
||||
# DummyConfig 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
|
||||
|
||||
@ -45,6 +51,20 @@ module Vagrant
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
# Converts this untyped config into a form suitable for passing over a
|
||||
# GRPC connection. This is used for portions of config that might not
|
||||
# have config classes implemented in Ruby.
|
||||
#
|
||||
# @param type [String] a name to put into the type field, e.g. plugin name
|
||||
# @return [Hashicorp::Vagrant::Sdk::Vagrantfile::GeneralConfig]
|
||||
def to_proto(type)
|
||||
protoize = self.instance_variables_hash
|
||||
protoize.delete_if{|k,v| k.start_with?("_") }
|
||||
config_struct = Google::Protobuf::Struct.from_hash(protoize)
|
||||
config_any = Google::Protobuf::Any.pack(config_struct)
|
||||
Hashicorp::Vagrant::Sdk::Vagrantfile::GeneralConfig.new(type: type, config: config_any)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -634,6 +634,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
||||
add_message "hashicorp.vagrant.sdk.Project.CwdResponse" do
|
||||
optional :path, :string, 1
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.Project.ConfigResponse" do
|
||||
optional :vagrantfile, :message, 1, "hashicorp.vagrant.sdk.Vagrantfile.Vagrantfile"
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.Project.VagrantfileNameResponse" do
|
||||
optional :name, :string, 1
|
||||
end
|
||||
@ -770,11 +773,16 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
||||
optional :owner, :string, 9
|
||||
optional :type, :string, 10
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.Vagrantfile.PushConfig" do
|
||||
optional :name, :string, 1
|
||||
optional :config, :message, 2, "hashicorp.vagrant.sdk.Vagrantfile.GeneralConfig"
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.Vagrantfile.Vagrantfile" do
|
||||
optional :path, :string, 1
|
||||
optional :raw, :string, 2
|
||||
optional :current_version, :string, 3
|
||||
repeated :machine_configs, :message, 4, "hashicorp.vagrant.sdk.Vagrantfile.MachineConfig"
|
||||
repeated :push_configs, :message, 5, "hashicorp.vagrant.sdk.Vagrantfile.PushConfig"
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.TargetIndex" do
|
||||
end
|
||||
@ -837,11 +845,6 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
||||
optional :version, :string, 2
|
||||
repeated :providers, :string, 3
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.Push" do
|
||||
end
|
||||
add_message "hashicorp.vagrant.sdk.Push.PushResponse" do
|
||||
optional :exit_code, :int32, 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1003,6 +1006,7 @@ module Hashicorp
|
||||
Project::MachineNamesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.MachineNamesResponse").msgclass
|
||||
Project::ActiveMachinesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.ActiveMachinesResponse").msgclass
|
||||
Project::CwdResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.CwdResponse").msgclass
|
||||
Project::ConfigResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.ConfigResponse").msgclass
|
||||
Project::VagrantfileNameResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.VagrantfileNameResponse").msgclass
|
||||
Project::VagrantfilePathResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.VagrantfilePathResponse").msgclass
|
||||
Project::HomeResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.HomeResponse").msgclass
|
||||
@ -1027,6 +1031,7 @@ module Hashicorp
|
||||
Vagrantfile::Provider = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Vagrantfile.Provider").msgclass
|
||||
Vagrantfile::Network = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Vagrantfile.Network").msgclass
|
||||
Vagrantfile::SyncedFolder = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Vagrantfile.SyncedFolder").msgclass
|
||||
Vagrantfile::PushConfig = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Vagrantfile.PushConfig").msgclass
|
||||
Vagrantfile::Vagrantfile = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Vagrantfile.Vagrantfile").msgclass
|
||||
TargetIndex = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.TargetIndex").msgclass
|
||||
TargetIndex::TargetIdentifier = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.TargetIndex.TargetIdentifier").msgclass
|
||||
@ -1047,8 +1052,6 @@ module Hashicorp
|
||||
BoxCollection::AllResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.BoxCollection.AllResponse").msgclass
|
||||
BoxCollection::CleanRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.BoxCollection.CleanRequest").msgclass
|
||||
BoxCollection::FindRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.BoxCollection.FindRequest").msgclass
|
||||
Push = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Push").msgclass
|
||||
Push::PushResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Push.PushResponse").msgclass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -448,6 +448,7 @@ module Hashicorp
|
||||
rpc :TargetIndex, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::TargetIndex
|
||||
# rpc ActiveMachines(google.protobuf.Empty) returns (Project.ActiveMachinesResponse);
|
||||
rpc :CWD, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Project::CwdResponse
|
||||
rpc :Config, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Project::ConfigResponse
|
||||
rpc :DataDir, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::DataDir::Project
|
||||
rpc :VagrantfileName, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Project::VagrantfileNameResponse
|
||||
rpc :VagrantfilePath, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Project::VagrantfilePathResponse
|
||||
@ -566,7 +567,7 @@ module Hashicorp
|
||||
self.service_name = 'hashicorp.vagrant.sdk.PushService'
|
||||
|
||||
rpc :PushSpec, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::FuncSpec
|
||||
rpc :Push, ::Hashicorp::Vagrant::Sdk::FuncSpec::Args, ::Hashicorp::Vagrant::Sdk::Push::PushResponse
|
||||
rpc :Push, ::Hashicorp::Vagrant::Sdk::FuncSpec::Args, ::Google::Protobuf::Empty
|
||||
rpc :Seed, ::Hashicorp::Vagrant::Sdk::Args::Seeds, ::Google::Protobuf::Empty
|
||||
rpc :Seeds, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Seeds
|
||||
end
|
||||
|
||||
@ -70,11 +70,14 @@ module VagrantPlugins
|
||||
)
|
||||
end
|
||||
|
||||
push_configs = v.config.push.to_proto
|
||||
|
||||
vagrantfile = Hashicorp::Vagrant::Sdk::Vagrantfile::Vagrantfile.new(
|
||||
path: path,
|
||||
raw: raw,
|
||||
current_version: Vagrant::Config::CURRENT_VERSION,
|
||||
machine_configs: machine_configs,
|
||||
push_configs: push_configs,
|
||||
)
|
||||
Hashicorp::Vagrant::ParseVagrantfileResponse.new(
|
||||
vagrantfile: vagrantfile
|
||||
|
||||
@ -147,6 +147,19 @@ module VagrantPlugins
|
||||
raise "Must finalize first!" if !@__finalized
|
||||
@__compiled_pushes.dup
|
||||
end
|
||||
|
||||
|
||||
def to_proto
|
||||
raise "Must finalize first!" if !@__finalized
|
||||
__compiled_pushes.map do |name, (strategy, config)|
|
||||
@logger.info("making a proto! name: #{name.inspect}, strategy: #{strategy.inspect}, config: #{config.inspect}")
|
||||
@logger.info("protofied config #{config.to_proto(strategy).inspect}")
|
||||
Hashicorp::Vagrant::Sdk::Vagrantfile::PushConfig.new(
|
||||
name: name,
|
||||
config: config.to_proto(strategy)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user