diff --git a/.gitignore b/.gitignore index 5f3e03c97..d2bead200 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,10 @@ cover.out # nektos/act secrets file .secrets + +# delve debug binary +__debug_bin + +# solargraph (ruby lsp) & rubocop +.solargraph.yml +.rubocop.yml diff --git a/builtin/otherplugin/command.go b/builtin/otherplugin/command.go index d8e95cb28..011d44585 100644 --- a/builtin/otherplugin/command.go +++ b/builtin/otherplugin/command.go @@ -132,7 +132,7 @@ func (c *Command) ExecuteInfo(trm terminal.UI, p plugincore.Project) int32 { ptrm.Output("YAY! This is project specific output!") } - t, err := p.Target("one") + t, err := p.Target("one", "") if err != nil { trm.Output("Failed to load `one' target -- " + err.Error()) return 1 diff --git a/go.mod b/go.mod index 255054b66..9598400a9 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl/v2 v2.7.1-0.20201023000745-3de61ecba298 github.com/hashicorp/nomad/api v0.0.0-20200814140818-42de70466a9d - github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220524143830-80e9a67614d0 + github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220525200400-f1459dc0d92b github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect github.com/imdario/mergo v0.3.11 github.com/improbable-eng/grpc-web v0.13.0 diff --git a/go.sum b/go.sum index e24d580e6..b298c4e00 100644 --- a/go.sum +++ b/go.sum @@ -355,10 +355,8 @@ github.com/hashicorp/hcl/v2 v2.7.1-0.20201023000745-3de61ecba298 h1:pLsdnvAlWuZ9 github.com/hashicorp/hcl/v2 v2.7.1-0.20201023000745-3de61ecba298/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/nomad/api v0.0.0-20200814140818-42de70466a9d h1:afuZ/KNbxwUgjEzq2NXO2bRKZgsIJQgFxgIRGETF0/A= github.com/hashicorp/nomad/api v0.0.0-20200814140818-42de70466a9d/go.mod h1:DCi2k47yuUDzf2qWAK8E1RVmWgz/lc0jZQeEnICTxmY= -github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220513172229-62750f6e1ce7 h1:4d/nNs0gKLHkm9Ra/bdQTvxWSz0ctvwaOUFrZD/4pN4= -github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220513172229-62750f6e1ce7/go.mod h1:KWfWOiotOWKiAqdroXVc7GUFnuOzlzhnRkGTV9Js7/s= -github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220524143830-80e9a67614d0 h1:oHSg3GwN9Wk4Fa94Ozx9et+hUesiPe4bdjyp0uIv5Qo= -github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220524143830-80e9a67614d0/go.mod h1:KWfWOiotOWKiAqdroXVc7GUFnuOzlzhnRkGTV9Js7/s= +github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220525200400-f1459dc0d92b h1:GidqljOXwQSIFn+nk4DOpa0kpR/1x/yS/yCNnA7sUFE= +github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20220525200400-f1459dc0d92b/go.mod h1:KWfWOiotOWKiAqdroXVc7GUFnuOzlzhnRkGTV9Js7/s= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce h1:7UnVY3T/ZnHUrfviiAgIUjg2PXxsQfs5bphsG8F7Keo= github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= diff --git a/internal/client/project.go b/internal/client/project.go index 706ce778b..092bab733 100644 --- a/internal/client/project.go +++ b/internal/client/project.go @@ -159,20 +159,6 @@ func (p *Project) LoadTarget(n string) (*Target, error) { }, nil } -// TODO: Determine default provider by implementing algorithm from -// https://www.vagrantup.com/docs/providers/basic_usage#default-provider -// -// Currently blocked on being able to parse Vagrantfile -func (p *Project) GetDefaultProvider(exclude []string, forceDefault bool, checkUsable bool) (provider string, err error) { - defaultProvider := os.Getenv("VAGRANT_DEFAULT_PROVIDER") - if defaultProvider != "" && forceDefault { - return defaultProvider, nil - } - - // HACK: This should throw an error if no default provider is found - return "virtualbox", nil -} - func (p *Project) UI() terminal.UI { return p.ui } diff --git a/internal/core/machine.go b/internal/core/machine.go index b00bc0331..8b3728655 100644 --- a/internal/core/machine.go +++ b/internal/core/machine.go @@ -231,6 +231,88 @@ func StringToPathFunc() mapstructure.DecodeHookFunc { } } +// TEMP: until we have plugin priority being sent along at registration, we are +// manually mirroring the plugin priorities from legacy vagrant +func syncedFolderPriority(name string) int { + switch name { + case "nfs": + return 5 + case "rsync": + return 5 + case "smb": + return 7 + default: // covers virtualbox, docker, vmware + return 10 + } +} + +func (m *Machine) defaultSyncedFolderType() (folderType *string, err error) { + logger := m.logger.Named("default-synced-folder-type") + + // Get all available synced folder plugins + syncedFolders, err := m.project.basis.typeComponents(m.ctx, component.SyncedFolderType) + if err != nil { + return + } + + // Get all plugin components + components := make([]*Component, 0, len(syncedFolders)) + for _, value := range syncedFolders { + components = append(components, value) + } + + // Sort by plugin priority. Higher is first + sort.SliceStable(components, func(i, j int) bool { + return syncedFolderPriority(components[i].Info.Name) > syncedFolderPriority(components[j].Info.Name) + }) + + names := make([]string, 0, len(components)) + for _, c := range components { + names = append(names, c.Info.Name) + } + logger.Debug("Sorted synced folder plugins", "names", names) + + // Remove unallowed types + config := m.target.Configuration + machineConfig := config.ConfigVm + if len(machineConfig.AllowedSyncedFolderTypes) > 0 { + allowed := make(map[string]struct{}) + for _, a := range machineConfig.AllowedSyncedFolderTypes { + allowed[a] = struct{}{} + } + k := 0 + for _, c := range components { + if _, ok := allowed[c.Info.Name]; ok { + components[k] = c + k++ + } else { + logger.Debug("removing disallowed plugin", "type", c.Info.Name) + } + } + components = components[:k] + } + + for _, component := range components { + syncedFolder := component.Value.(core.SyncedFolder) + usable, err := syncedFolder.Usable(m) + if err != nil { + logger.Error("synced folder error on usable check", + "plugin", component.Info.Name, + "type", "SyncedFolder", + "error", err) + continue + } + if usable { + logger.Info("returning default", "name", component.Info.Name) + return &component.Info.Name, nil + } else { + logger.Debug("skipping unusable plugin", "name", component.Info.Name) + } + } + + return nil, fmt.Errorf("failed to detect guest plugin for current platform") +} + // SyncedFolders implements core.Machine func (m *Machine) SyncedFolders() (folders []*core.MachineSyncedFolder, err error) { config := m.target.Configuration @@ -239,10 +321,11 @@ func (m *Machine) SyncedFolders() (folders []*core.MachineSyncedFolder, err erro folders = []*core.MachineSyncedFolder{} for _, folder := range syncedFolders { - if folder.Type == nil { - // TODO: get default synced folder type - defaultType := "virtualbox" - folder.Type = &defaultType + if folder.GetType() == "" { + folder.Type, err = m.defaultSyncedFolderType() + if err != nil { + return nil, err + } } lookup := "syncedfolder_" + *(folder.Type) v := m.cache.Get(lookup) diff --git a/internal/core/machine_test.go b/internal/core/machine_test.go index 7fcef998a..a9aa54c8c 100644 --- a/internal/core/machine_test.go +++ b/internal/core/machine_test.go @@ -101,6 +101,7 @@ func TestMachineGetNonExistentBox(t *testing.T) { WithTestTargetConfig(&vagrant_plugin_sdk.Vagrantfile_MachineConfig{ ConfigVm: &vagrant_plugin_sdk.Vagrantfile_ConfigVM{Box: "somebox"}, }), + WithTestTargetProvider("testprovider"), ) box, err := tm.Box() @@ -110,7 +111,7 @@ func TestMachineGetNonExistentBox(t *testing.T) { require.Equal(t, name, "somebox") provider, err := box.Provider() require.NoError(t, err) - require.NotEmpty(t, provider) + require.Equal(t, provider, "testprovider") metaurl, err := box.MetadataURL() require.NoError(t, err) require.Empty(t, metaurl) @@ -283,6 +284,7 @@ func syncedFolderPlugin(t *testing.T, name string) *plugin.Plugin { plugin.WithPluginTypes(component.SyncedFolderType), ) } + func TestMachineSyncedFolders(t *testing.T) { mySyncedFolder := syncedFolderPlugin(t, "mysyncedfolder") myOtherSyncedFolder := syncedFolderPlugin(t, "myothersyncedfolder") diff --git a/internal/core/project.go b/internal/core/project.go index 6cc55ff83..bdd94031f 100644 --- a/internal/core/project.go +++ b/internal/core/project.go @@ -3,6 +3,8 @@ package core import ( "context" "errors" + "fmt" + "os" "strings" "sync" @@ -66,7 +68,7 @@ func (p *Project) ActiveTargets() (activeTargets []core.Target, err error) { if err != nil { return nil, err } - if st == core.CREATED { + if st.IsActive() { activeTargets = append(activeTargets, t) } } @@ -99,10 +101,159 @@ func (p *Project) DefaultPrivateKey() (path path.Path, err error) { } // DefaultProvider implements core.Project -func (p *Project) DefaultProvider() (name string, err error) { - // TODO: This needs to implement the default provider algorithm - // from https://www.vagrantup.com/docs/providers/basic_usage.html#default-provider - return "virtualbox", nil +func (p *Project) DefaultProvider(opts *core.DefaultProviderOptions) (string, error) { + logger := p.logger.Named("default-provider") + logger.Debug("Searching for default provider", "options", fmt.Sprintf("%#v", opts)) + // Algorithm ported from Vagrant::Environment#default_provider; structure + // and comments mirrored from there. + + // Implement the algorithm from + // https://www.vagrantup.com/docs/providers/basic_usage.html#default-provider + // with additional steps 2.5 and 3.5 from + // https://bugzilla.redhat.com/show_bug.cgi?id=1444492 + // to allow system-configured provider priorities. + // + // 1. The --provider flag on a vagrant up is chosen above all else, if it is + // present. + // + // (Step 1 is done by the caller; this method is only called if --provider + // wasn't given.) + // + // 2. If the VAGRANT_DEFAULT_PROVIDER environmental variable is set, it + // takes next priority and will be the provider chosen. + defaultProvider := os.Getenv("VAGRANT_DEFAULT_PROVIDER") + if defaultProvider != "" && opts.ForceDefault { + logger.Debug("Using forced default provider", "provider", defaultProvider) + return defaultProvider, nil + } + + // Get the list of providers in our configuration, in order + configProviders := []string{} + for _, m := range p.project.GetConfiguration().GetMachineConfigs() { + // If a MachineName is provided - we're only looking at providers + // scoped to that machine name + if opts.MachineName != "" && opts.MachineName != m.GetName() { + continue + } + for _, p := range m.GetConfigVm().GetProviders() { + configProviders = append(configProviders, p.GetType()) + } + } + + usableProviders := []string{} + pluginProviders, err := p.basis.plugins.ListPlugins("provider") + if err != nil { + return "", err + } + for _, pp := range pluginProviders { + // Skip excluded providers + if opts.IsExcluded(pp.Name) { + continue + } + + // TODO: how to check for defaultable? + + // Skip the providers that aren't usable. + if opts.CheckUsable { + logger.Debug("Checking usable on provider", "provider", pp.Name) + plug, err := p.basis.plugins.GetPlugin(pp.Name, pp.Type) + if err != nil { + return "", err + } + pluginImpl := plug.Plugin.(core.Provider) + usable, err := pluginImpl.Usable() + if err != nil { + return "", err + } + if !usable { + continue + } + } + + // If we made it here we have a candidate usable provider + usableProviders = append(usableProviders, pp.Name) + } + logger.Debug("Initial usable provider list", "usableProviders", usableProviders) + + // TODO: how to get and sort by provider priority? + + // If we're not forcing the default, but it's usable and hasn't been + // otherwise excluded, return it now. + for _, u := range usableProviders { + if u == defaultProvider { + logger.Debug("Using default provider as it was found in usable list", + "provider", u) + return u, nil + } + } + + // 2.5. Vagrant will go through all of the config.vm.provider calls in the + // Vagrantfile and try each in order. It will choose the first + // provider that is usable and listed in VAGRANT_PREFERRED_PROVIDERS. + preferredProviders := strings.Split(os.Getenv("VAGRANT_PREFERRED_PROVIDERS"), ",") + k := 0 + for _, pp := range preferredProviders { + spp := strings.TrimSpace(pp) // .map { s.strip } + if spp != "" { // .select { !s.empty? } + preferredProviders[k] = spp + k++ + } + } + preferredProviders = preferredProviders[:k] + + for _, cp := range configProviders { + for _, up := range usableProviders { + if cp == up { + for _, pp := range preferredProviders { + if cp == pp { + logger.Debug("Using preferred provider detected in configuration and usable", + "provider", pp) + return pp, nil + } + } + } + } + } + + // 3. Vagrant will go through all of the config.vm.provider calls in the + // Vagrantfile and try each in order. It will choose the first provider + // that is usable. For example, if you configure Hyper-V, it will never + // be chosen on Mac this way. It must be both configured and usable. + for _, cp := range configProviders { + for _, up := range usableProviders { + if cp == up { + logger.Debug("Using provider detected in configuration and usable", + "provider", cp) + return cp, nil + } + } + } + + // 3.5. Vagrant will go through VAGRANT_PREFERRED_PROVIDERS and find the + // first plugin that reports it is usable. + for _, pp := range preferredProviders { + for _, up := range usableProviders { + if pp == up { + logger.Debug("Using preffered provider found in usable list", + "provider", pp) + return pp, nil + } + } + } + + // 4. Vagrant will go through all installed provider plugins (including the + // ones that come with Vagrant), and find the first plugin that reports + // it is usable. There is a priority system here: systems that are known + // better have a higher priority than systems that are worse. For + // example, if you have the VMware provider installed, it will always + // take priority over VirtualBox. + if len(usableProviders) > 0 { + logger.Debug("Using the first provider from the usable list", + "provider", usableProviders[0]) + return usableProviders[0], nil + } + + return "", errors.New("No default provider.") } // Home implements core.Project @@ -138,13 +289,26 @@ func (p *Project) RootPath() (path path.Path, err error) { } // Target implements core.Project -func (p *Project) Target(nameOrId string) (core.Target, error) { +func (p *Project) Target(nameOrId string, provider string) (core.Target, error) { if t, ok := p.targets[nameOrId]; ok { return t, nil } // Check for name or id for _, t := range p.targets { if t.target.Name == nameOrId { + // TODO: Because we don't have provider selection fully ported + // over, there are cases where a target is loaded without a + // provider being set on it. For now we're just handling that here + // on lookup, but once we know for sure that any Target that exists + // already knows what its provider is, this should be able to be + // removed. + st, err := t.State() + if err != nil { + return nil, err + } + if provider != "" && !st.IsActive() { + t.target.Provider = provider + } return t, nil } if t.target.ResourceId == nameOrId { @@ -160,6 +324,7 @@ func (p *Project) Target(nameOrId string) (core.Target, error) { ResourceId: nameOrId, }, ), + WithProvider(provider), ) } diff --git a/internal/core/project_test.go b/internal/core/project_test.go index 25425a552..ddc444674 100644 --- a/internal/core/project_test.go +++ b/internal/core/project_test.go @@ -45,17 +45,17 @@ func TestProjectGetTarget(t *testing.T) { } // Get by id - one, err := tp.Target("id-one") + one, err := tp.Target("id-one", "") require.NoError(t, err) require.Equal(t, targetOne, one) // Get by name - two, err := tp.Target("target-two") + two, err := tp.Target("target-two", "") require.NoError(t, err) require.Equal(t, targetTwo, two) // Get target that doesn't exist - noexist, err := tp.Target("ohnooooo") + noexist, err := tp.Target("ohnooooo", "") require.Error(t, err) require.Nil(t, noexist) } diff --git a/internal/core/target.go b/internal/core/target.go index e0db5d089..5e3a86b5d 100644 --- a/internal/core/target.go +++ b/internal/core/target.go @@ -2,6 +2,7 @@ package core import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -98,6 +99,9 @@ func (t *Target) Provider() (p core.Provider, err error) { if err != nil { return nil, err } + if providerName == "" { + return nil, errors.New("cannot fetch provider for target when provider name is blank") + } provider, err := t.project.basis.component( t.ctx, component.ProviderType, providerName) @@ -110,10 +114,8 @@ func (t *Target) Provider() (p core.Provider, err error) { } // ProviderName implements core.Target -// TODO: Use actual value once provider is set on the go side func (t *Target) ProviderName() (string, error) { - // return t.target.Provider, nil - return "virtualbox", nil + return t.target.Provider, nil } // Communicate implements core.Target @@ -410,7 +412,7 @@ type TargetOption func(*Target) error func WithTargetName(name string) TargetOption { return func(t *Target) (err error) { - if ex, _ := t.project.Target(name); ex != nil { + if ex, _ := t.project.Target(name, ""); ex != nil { if et, ok := ex.(*Target); ok { t.target = et.target } @@ -484,4 +486,13 @@ func WithTargetRef(r *vagrant_plugin_sdk.Ref_Target) TargetOption { } } +func WithProvider(provider string) TargetOption { + return func(t *Target) (err error) { + if t != nil && t.target != nil && provider != "" { + t.target.Provider = provider + } + return nil + } +} + var _ core.Target = (*Target)(nil) diff --git a/internal/core/target_index.go b/internal/core/target_index.go index 54c192373..bbbe37011 100644 --- a/internal/core/target_index.go +++ b/internal/core/target_index.go @@ -47,15 +47,7 @@ func (t *TargetIndex) Get(uuid string) (entry core.Target, err error) { }, }) if err != nil { - // Search name if not found by uuid - result, err = t.client.FindTarget(t.ctx, &vagrant_server.FindTargetRequest{ - Target: &vagrant_server.Target{ - Name: uuid, - }, - }) - if err != nil { - return - } + return } return t.loadTarget(&vagrant_plugin_sdk.Ref_Target{ ResourceId: result.Target.ResourceId, diff --git a/internal/core/target_index_test.go b/internal/core/target_index_test.go index 18545c180..5b90c78b7 100644 --- a/internal/core/target_index_test.go +++ b/internal/core/target_index_test.go @@ -44,12 +44,6 @@ func TestTargetIndexGet(t *testing.T) { // Add targets projectTargets(t, tp, 3) - // Get by target name - target, err = ti.Get("target-1") - require.NoError(t, err) - rid, _ := target.ResourceId() - require.Equal(t, rid, "id-1") - // Get by target id target, err = ti.Get("uuid-1") require.NoError(t, err) @@ -72,11 +66,6 @@ func TestTargetIndexIncludes(t *testing.T) { // Add targets projectTargets(t, tp, 3) - // Includes by target name - exists, err = ti.Includes("target-1") - require.NoError(t, err) - require.True(t, exists) - // Includes by target id exists, err = ti.Includes("uuid-1") require.NoError(t, err) diff --git a/internal/core/testing_basis.go b/internal/core/testing_basis.go index a65f657e0..49250fb9d 100644 --- a/internal/core/testing_basis.go +++ b/internal/core/testing_basis.go @@ -72,6 +72,7 @@ func BuildTestSyncedFolderPlugin(parent string) *TestSyncedFolderPlugin { p.On("Seed", mock.AnythingOfType("*core.Seeds")).Return(nil) p.On("Seeds").Return(core.NewSeeds(), nil) p.On("Parent").Return(parent, nil) + p.On("Usable", mock.AnythingOfType("*core.Machine")).Return(true, nil) return p } diff --git a/internal/core/testing_target.go b/internal/core/testing_target.go index e37319027..91b1b324e 100644 --- a/internal/core/testing_target.go +++ b/internal/core/testing_target.go @@ -101,3 +101,10 @@ func WithTestTargetConfig(config *vagrant_plugin_sdk.Vagrantfile_MachineConfig) return } } + +func WithTestTargetProvider(provider string) TestMachineOption { + return func(m *Machine) (err error) { + m.target.Provider = provider + return + } +} diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index d9609fa3d..de99cbf3b 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -304,6 +304,12 @@ module Vagrant # This returns the provider name for the default provider for this # environment. # + # @param check_usable [Boolean] (true) whether to filter for `.usable?` providers + # @param exclude [Array] ([]) list of provider names to exclude from + # consideration + # @param force_default [Boolean] (true) whether to prefer the value of + # VAGRANT_DEFAULT_PROVIDER over other strategies if it is set + # @param machine [Symbol] (nil) a machine name to scope this lookup # @return [Symbol] Name of the default provider. def default_provider(**opts) opts[:exclude] = Set.new(opts[:exclude]) if opts[:exclude] @@ -974,7 +980,7 @@ module Vagrant provider = guess_provider vagrantfile.machine_names.each do |mname| ldp = @local_data_path.join("machines/#{mname}/#{provider}") if @local_data_path - plugins << vagrantfile.machine_config(mname, guess_provider, boxes, ldp, false)[:config] + plugins << vagrantfile.machine_config(mname, provider, boxes, ldp, false)[:config] end result = plugins.reverse.inject(Vagrant::Util::HashWithIndifferentAccess.new) do |memo, val| Vagrant::Util::DeepMerge.deep_merge(memo, val.vagrant.plugins) diff --git a/lib/vagrant/environment/remote.rb b/lib/vagrant/environment/remote.rb index 25e625caf..20f4c8062 100644 --- a/lib/vagrant/environment/remote.rb +++ b/lib/vagrant/environment/remote.rb @@ -102,15 +102,16 @@ module Vagrant end def default_provider(**opts) - client.respond_to?(:default_provider) && client.default_provider.to_sym + client.respond_to?(:default_provider) && client.default_provider(opts) end # Gets a target (machine) by name # # @param [String] machine name + # @param [String] provider name # return [VagrantPlugins::CommandServe::Client::Machine] - def get_target(name) - client.target(name) + def get_target(name, provider) + client.target(name, provider) end # Returns the host object associated with this environment. @@ -126,8 +127,8 @@ module Vagrant # @param [String] machine name # return [Vagrant::Machine] - def machine(name, *_, **_) - client.machine(name) + def machine(name, provider, **_) + client.machine(name, provider) end def machine_names diff --git a/lib/vagrant/machine/remote.rb b/lib/vagrant/machine/remote.rb index 7cf9cd27b..6c9d56f5d 100644 --- a/lib/vagrant/machine/remote.rb +++ b/lib/vagrant/machine/remote.rb @@ -30,7 +30,7 @@ module Vagrant @logger = Log4r::Logger.new("vagrant::machine") if !env.nil? && client.nil? @env = env - @client = env.get_target(name) + @client = env.get_target(name, provider_name) else @client = client @env = client.environment diff --git a/lib/vagrant/plugin/remote/provider.rb b/lib/vagrant/plugin/remote/provider.rb index d889e6aa8..6eb8c7f85 100644 --- a/lib/vagrant/plugin/remote/provider.rb +++ b/lib/vagrant/plugin/remote/provider.rb @@ -31,6 +31,25 @@ module Vagrant client.action(@machine.to_proto, name) end + # Executes the capability with the given name, optionally passing more + # arguments onwards to the capability. If the capability returns a value, + # it will be returned. + # + # @param [Symbol] cap_name Name of the capability + def capability(cap_name, *args) + @logger.debug("running remote provider capability #{cap_name} with args #{args}") + client.capability(cap_name, *args) + end + + # Tests whether the given capability is possible. + # + # @param [Symbol] cap_name Capability name + # @return [Boolean] + def capability?(cap_name) + @logger.debug("checking for remote provider capability #{cap_name}") + client.has_capability?(cap_name) + end + def machine_id_changed client.machine_id_changed(@machine.to_proto) 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 3ccac7622..27f2a6bde 100644 --- a/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_pb.rb +++ b/lib/vagrant/protobufs/proto/vagrant_plugin_sdk/plugin_pb.rb @@ -668,6 +668,12 @@ 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.DefaultProviderRequest" do + optional :check_usable, :bool, 1 + repeated :exclude, :string, 2 + optional :force_default, :bool, 3 + optional :machine_name, :string, 4 + end add_message "hashicorp.vagrant.sdk.Project.DefaultProviderResponse" do optional :provider_name, :string, 1 end @@ -685,6 +691,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do end add_message "hashicorp.vagrant.sdk.Project.TargetRequest" do optional :name, :string, 1 + optional :provider, :string, 2 end add_message "hashicorp.vagrant.sdk.Project.TargetNamesResponse" do repeated :names, :string, 1 @@ -1114,6 +1121,7 @@ module Hashicorp Project::ActiveTargetsResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.ActiveTargetsResponse").msgclass Project::ConfigResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.ConfigResponse").msgclass Project::CwdResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.CwdResponse").msgclass + Project::DefaultProviderRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.DefaultProviderRequest").msgclass Project::DefaultProviderResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.DefaultProviderResponse").msgclass Project::HomeResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.HomeResponse").msgclass Project::LocalDataResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hashicorp.vagrant.sdk.Project.LocalDataResponse").msgclass 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 634f7fc5b..39ccc159b 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 @@ -472,7 +472,7 @@ module Hashicorp rpc :CWD, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Path rpc :DataDir, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::DataDir::Project rpc :DefaultPrivateKey, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Path - rpc :DefaultProvider, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Project::DefaultProviderResponse + rpc :DefaultProvider, ::Hashicorp::Vagrant::Sdk::Project::DefaultProviderRequest, ::Hashicorp::Vagrant::Sdk::Project::DefaultProviderResponse rpc :Home, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Path rpc :Host, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Host rpc :LocalData, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Path diff --git a/lib/vagrant/vagrantfile.rb b/lib/vagrant/vagrantfile.rb index 21dfa49ec..1a4d5006b 100644 --- a/lib/vagrant/vagrantfile.rb +++ b/lib/vagrant/vagrantfile.rb @@ -81,7 +81,6 @@ module Vagrant return Machine.new(name, provider, provider_cls, provider_config, provider_options, config, data_path, box, env, self) end - # Returns the configuration for a single machine. # # When loading a box Vagrantfile, it will be prepended to the diff --git a/plugins/commands/serve/client/project.rb b/plugins/commands/serve/client/project.rb index 8b4bd227e..a559c75f8 100644 --- a/plugins/commands/serve/client/project.rb +++ b/plugins/commands/serve/client/project.rb @@ -54,10 +54,15 @@ module VagrantPlugins resp.path end - # return [String] - def default_provider - resp = client.default_provider(Empty.new) - resp.provider_name + def default_provider(opts) + req = ::Hashicorp::Vagrant::Sdk::Project::DefaultProviderRequest.new( + exclude: opts.fetch(:exclude, []), + force_default: opts.fetch(:force_default, true), + check_usable: opts.fetch(:check_usable, true), + machine_name: opts[:machine], + ) + resp = client.default_provider(req) + resp.provider_name.to_sym end # return [String] @@ -79,8 +84,11 @@ module VagrantPlugins end # return [Vagrant::Machine] - def machine(name) - t = client.target(SDK::Project::TargetRequest.new(name: name)) + def machine(name, provider) + t = client.target(SDK::Project::TargetRequest.new( + name: name, + provider: provider, + )) machine = mapper.map(t, to: Vagrant::Machine) return machine end @@ -124,9 +132,14 @@ module VagrantPlugins # Returns a machine client for the given name # return [VagrantPlugins::CommandServe::Client::Target::Machine] - def target(name) + def target(name, provider) target = Target.load( - client.target(SDK::Project::TargetRequest.new(name: name)), + client.target( + SDK::Project::TargetRequest.new( + name: name, + provider: provider, + ) + ), broker: broker ) target.to_machine diff --git a/plugins/commands/serve/client/target.rb b/plugins/commands/serve/client/target.rb index e11c5723c..08ae6dd4e 100644 --- a/plugins/commands/serve/client/target.rb +++ b/plugins/commands/serve/client/target.rb @@ -13,7 +13,7 @@ module VagrantPlugins :DESTROYED, ].freeze - # @return [SDK::Ref::Target] proto reference for this target + # @return [Hashicorp::Vagrant::Sdk::Ref::Target] proto reference for this target def ref SDK::Ref::Target.new(resource_id: resource_id) end diff --git a/plugins/commands/serve/client/target_index.rb b/plugins/commands/serve/client/target_index.rb index b80d7c50f..4e12e9f04 100644 --- a/plugins/commands/serve/client/target_index.rb +++ b/plugins/commands/serve/client/target_index.rb @@ -2,7 +2,7 @@ module VagrantPlugins module CommandServe class Client class TargetIndex < Client - # @param [string] + # @param [String] # @return [Boolean] true if delete is successful def delete(ident) logger.debug("deleting machine with id #{ident} from index") @@ -14,8 +14,8 @@ module VagrantPlugins true end - # @param [string] - # @return [MachineIndex::Entry] + # @param [String] + # @return [Vagrant::MachineIndex::Entry] def get(ident) logger.debug("getting machine with id #{ident} from index") begin @@ -31,7 +31,7 @@ module VagrantPlugins end end - # @param [string] + # @param [String] # @return [Boolean] def include?(ident) logger.debug("checking for machine with id #{ident} in index") @@ -42,8 +42,8 @@ module VagrantPlugins ).exists end - # @param [MachineIndex::Entry] - # @return [MachineIndex::Entry] + # @param [Vagrant::MachineIndex::Entry] + # @return [Vagrant::MachineIndex::Entry] def set(entry) logger.debug("setting machine #{entry} in index") if entry.id.to_s.empty? @@ -62,7 +62,7 @@ module VagrantPlugins end # Get all targets - # @return [Array] + # @return [Array] def all logger.debug("getting all machines") client.all(Empty.new).targets.map do |t_ref| diff --git a/plugins/commands/serve/mappers/mapper.rb b/plugins/commands/serve/mappers/mapper.rb index 6e00c3934..85b9548bb 100644 --- a/plugins/commands/serve/mappers/mapper.rb +++ b/plugins/commands/serve/mappers/mapper.rb @@ -20,14 +20,14 @@ module VagrantPlugins attr_reader :name # @return [Class] type of the argument attr_reader :type - # @return [Callable] callable that can validate argument + # @return [#call] callable that can validate argument attr_reader :validator # Create a new input # # @param type [Class] Type of the input - # @param validator [Callable] Callable to validate argument (optional) - # @yield Callable to validate argument (optional) + # @param validator [#call] Callable to validate argument (optional) + # @yield #call to validate argument (optional) def initialize(type:, validator: nil, &block) if !type.is_a?(Class) && !type.is_a?(Module) raise ArgumentError, @@ -107,14 +107,14 @@ module VagrantPlugins attr_reader :inputs # @return [Class, nil] type of output attr_reader :output - # @return [Callable] callable to perform mapping + # @return [#call] callable to perform mapping attr_reader :func # Create a new mapper instance # # @param inputs [Array] List of inputs for mapper # @param output [Class] Type of output value - # @param func [Callable] Callable to perform mapping + # @param func [#call] Callable to perform mapping def initialize(inputs:, output:, func:) Array(inputs).each do |i| if !i.is_a?(Input) diff --git a/plugins/commands/serve/service/provider_service.rb b/plugins/commands/serve/service/provider_service.rb index d65661afc..c05d8c34f 100644 --- a/plugins/commands/serve/service/provider_service.rb +++ b/plugins/commands/serve/service/provider_service.rb @@ -9,7 +9,7 @@ module VagrantPlugins super caps = Vagrant.plugin("2").local_manager.provider_capabilities default_args = { - Client::Target::Machine => SDK::Args::Target::Machine + Vagrant::Machine => SDK::Args::Target::Machine } initialize_capability_platform!(caps, default_args) end diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index b5eb61035..2693d6ab4 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -755,7 +755,12 @@ module VagrantPlugins @allow_fstab_modification = true if @allow_fstab_modification == UNSET_VALUE end - if !box && !clone && !machine.provider_options[:box_optional] + # HACK(phinze): We cannot honor box_optional in gogo yet so we are + # temporarily hacking in a workaround which explicitly looks for docker + # instead of the option itself. Once plugin metadata is implemented + # this conditional should be reverted to the commented line below + # if !box && !clone && !machine.provider_options[:box_optional] + if !box && !clone && machine.provider_name != :docker errors << I18n.t("vagrant.config.vm.box_missing") end diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index d35cf3a9a..e12331bfc 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -100,6 +100,8 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end it "is not required if the provider says so" do + # TODO: reenable this test once provider options are implemented + pending "removal of temporary workaround" machine.provider_options[:box_optional] = true subject.box = nil subject.finalize!