diff --git a/builtin/myplugin/push/encouragement.go b/builtin/myplugin/push/encouragement.go index d31baf1f6..2803d5daf 100644 --- a/builtin/myplugin/push/encouragement.go +++ b/builtin/myplugin/push/encouragement.go @@ -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) ) diff --git a/internal/core/project.go b/internal/core/project.go index 355364ce1..487b221e9 100644 --- a/internal/core/project.go +++ b/internal/core/project.go @@ -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 diff --git a/lib/vagrant/config/v2/dummy_config.rb b/lib/vagrant/config/v2/dummy_config.rb index 031be0275..8a5db0663 100644 --- a/lib/vagrant/config/v2/dummy_config.rb +++ b/lib/vagrant/config/v2/dummy_config.rb @@ -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 diff --git a/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_pb.rb b/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_pb.rb index 2137cc3bf..b19b82665 100644 --- a/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_pb.rb +++ b/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_pb.rb @@ -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 diff --git a/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_services_pb.rb b/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_services_pb.rb index e13a1c9b6..8ffd16181 100644 --- a/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_services_pb.rb +++ b/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_services_pb.rb @@ -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 diff --git a/plugins/commands/serve/service/internal_service.rb b/plugins/commands/serve/service/internal_service.rb index 1e711d194..f502ce142 100644 --- a/plugins/commands/serve/service/internal_service.rb +++ b/plugins/commands/serve/service/internal_service.rb @@ -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 diff --git a/plugins/kernel_v2/config/push.rb b/plugins/kernel_v2/config/push.rb index 52e4e5ef0..14597278c 100644 --- a/plugins/kernel_v2/config/push.rb +++ b/plugins/kernel_v2/config/push.rb @@ -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