Fix commands that run without a project

Some commands like `vagrant init` and `vagrant box` should be able to
run successfully without a full Project available in VAGRANT_CWD (in
other words, they don't require that a valid Vagrantfile be available.)

Thus far we've been assuming that a Project is available when
dispatching commands, which mean that commands of this nature weren't
working.

Here we make the Basis available to serve as an alternative client to
Vagrant::Environment::Remote such that it can be instantiated and passed
through to commands. This required some changes to Environment::Remote
to make its interactions with the client more defensive, but we manage
to avoid needing to make any changes to the normal legacy codepaths.
This commit is contained in:
Paul Hinze 2022-03-15 17:24:31 -05:00
parent 61fbd0f622
commit 8f9952089a
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0
6 changed files with 94 additions and 21 deletions

View File

@ -16,6 +16,7 @@ import (
"github.com/hashicorp/vagrant-plugin-sdk/core"
"github.com/hashicorp/vagrant-plugin-sdk/datadir"
"github.com/hashicorp/vagrant-plugin-sdk/helper/path"
"github.com/hashicorp/vagrant-plugin-sdk/helper/paths"
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cacher"
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/dynamic"
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/protomappers"
@ -201,6 +202,14 @@ func (b *Basis) Config() *vagrant_plugin_sdk.Vagrantfile_Vagrantfile {
return b.basis.Configuration
}
func (p *Basis) CWD() (path string, err error) {
cwd, err := paths.VagrantCwd()
if err != nil {
return "", err
}
return cwd.String(), nil
}
// Basis UI is the "default" UI with no prefix modifications
func (b *Basis) UI() (terminal.UI, error) {
return b.ui, nil
@ -211,6 +220,12 @@ func (b *Basis) DataDir() (*datadir.Basis, error) {
return b.dir, nil
}
// DefaultPrivateKey implements core.Basis
func (b *Basis) DefaultPrivateKey() (path string, err error) {
defaultPrivateKeyPath := b.dir.DataDir().Join("insecure_private_key")
return defaultPrivateKeyPath.String(), nil
}
// Implements core.Basis
// Returns all the registered plugins of the types specified
func (b *Basis) Plugins(types ...string) (plugins []*core.NamedPlugin, err error) {
@ -275,7 +290,7 @@ func (b *Basis) State() *StateBag {
return b.statebag.(*StateBag)
}
func (b *Basis) Boxes() (bc *BoxCollection, err error) {
func (b *Basis) Boxes() (bc core.BoxCollection, err error) {
if b.boxCollection == nil {
b.boxCollection = &BoxCollection{
basis: b,

View File

@ -110,8 +110,7 @@ func (p *Project) Tmp() (path string, err error) {
// DefaultPrivateKey implements core.Project
func (p *Project) DefaultPrivateKey() (path string, err error) {
defaultPrivateKeyPath := p.dir.DataDir().Join("insecure_private_key")
return defaultPrivateKeyPath.String(), nil
return p.basis.DefaultPrivateKey()
}
// Host implements core.Project

View File

@ -10,6 +10,7 @@ module Vagrant
end
end
# Client can be either a Project or a Basis
def initialize(opts={})
@client = opts[:client]
if @client.nil?
@ -23,11 +24,11 @@ module Vagrant
opts[:ui_class] ||= UI::Remote
@cwd = Pathname.new(@client.cwd)
@home_path = Pathname.new(@client.home)
@vagrantfile_name = [@client.vagrantfile_name]
@home_path = @client.respond_to?(:home) && Pathname.new(@client.home)
@vagrantfile_name = @client.respond_to?(:vagrantfile_name) && [@client.vagrantfile_name]
@ui = opts.fetch(:ui, opts[:ui_class].new(@client.ui))
@local_data_path = Pathname.new(@client.local_data)
@boxes_path = @home_path.join("boxes")
@boxes_path = @home_path && @home_path.join("boxes")
@data_dir = Pathname.new(@client.data_dir)
@gems_path = Vagrant::Bundler.instance.plugin_gem_path
@tmp_path = Pathname.new(@client.temp_dir)
@ -43,7 +44,7 @@ module Vagrant
# TODO: aliases
@aliases_path = Pathname.new(ENV["VAGRANT_ALIAS_FILE"]).expand_path if ENV.key?("VAGRANT_ALIAS_FILE")
@aliases_path ||= @home_path.join("aliases")
@aliases_path ||= @home_path && @home_path.join("aliases")
@default_private_key_path = Pathname.new(@client.default_private_key)
copy_insecure_private_key
@ -112,20 +113,26 @@ module Vagrant
@machine_index ||= Vagrant::MachineIndex.new(client: client.target_index)
end
def vagrantfile
if @vagrantfile.nil?
def config_loader
return @config_loader if @config_loader
root_vagrantfile = nil
if client.respond_to?(:vagrantfile_path) && client.respond_to?(:vagrantfile_name)
path = client.vagrantfile_path
name = client.vagrantfile_name
full_path = path.join(name).to_s
config_loader = Vagrant::Config::Loader.new(
Vagrant::Config::VERSIONS, Vagrant::Config::VERSIONS_ORDER)
config_loader.set(:root, full_path)
@vagrantfile = Vagrant::Vagrantfile.new(config_loader, [:root])
root_vagrantfile = path.join(name).to_s
end
@vagrantfile
@config_loader = Config::Loader.new(
Config::VERSIONS, Config::VERSIONS_ORDER)
@config_loader.set(:root, root_vagrantfile) if root_vagrantfile
@config_loader
end
def vagrantfile
# When in remote mode we don't load the "home" vagrantfile
@vagrantfile ||= Vagrant::Vagrantfile.new(config_loader, [:root])
end
def to_proto
client.proto
end

View File

@ -350,9 +350,15 @@ module Hashicorp
self.unmarshal_class_method = :decode
self.service_name = 'hashicorp.vagrant.sdk.BasisService'
rpc :CWD, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Path
rpc :DataDir, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::DataDir::Basis
rpc :DefaultPrivateKey, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Path
rpc :UI, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::TerminalUI
rpc :Host, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Host
rpc :Boxes, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::BoxCollection
rpc :TargetIndex, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::TargetIndex
rpc :Seed, ::Hashicorp::Vagrant::Sdk::Args::Seeds, ::Google::Protobuf::Empty
rpc :Seeds, ::Google::Protobuf::Empty, ::Hashicorp::Vagrant::Sdk::Args::Seeds
end
Stub = Service.rpc_stub_class

View File

@ -2,6 +2,18 @@ module VagrantPlugins
module CommandServe
class Client
class Basis < Client
def boxes
BoxCollection.load(
client.boxes(Empty.new),
broker: broker
)
end
def cwd
resp = client.cwd(Empty.new)
resp.path
end
# return [Sdk::Args::DataDir::Basis]
def data_dirs
resp = client.data_dir(Empty.new)
@ -13,6 +25,18 @@ module VagrantPlugins
data_dirs.data_dir
end
def default_private_key
resp = client.default_private_key(Empty.new)
resp.path
end
def target_index
TargetIndex.load(
client.target_index(Empty.new),
broker: broker
)
end
# @return [Terminal]
def ui
begin
@ -21,7 +45,7 @@ module VagrantPlugins
broker: @broker,
)
rescue => err
raise "Failed to load terminal via project: #{err}"
raise "Failed to load terminal via basis: #{err}"
end
end
@ -45,6 +69,14 @@ module VagrantPlugins
end
plugins
end
def local_data
data_dirs.data_dir
end
def temp_dir
data_dirs.temp_dir
end
end
end
end

View File

@ -21,7 +21,7 @@ module VagrantPlugins
funcspec(
args: [
SDK::Args::TerminalUI,
SDK::Args::Project,
SDK::Args::Basis,
SDK::Command::Arguments,
],
result: SDK::Command::ExecuteResp,
@ -32,15 +32,29 @@ module VagrantPlugins
with_info(ctx, broker: broker) do |info|
plugin_name = info.plugin_name
_, env, arguments = mapper.funcspec_map(
ui, basis, arguments = mapper.funcspec_map(
req.spec,
expect: [
Vagrant::UI::Remote,
Vagrant::Environment,
SDK::Args::Basis,
Type::CommandArguments
]
)
# We need a Vagrant::Environment to pass to the command. If we got a
# Project from seeds we can use that to get an environment.
# Otherwise we can initialize a barebones environment from the
# Basis we received directly from the funcspec args above.
if @seeds && @seeds.named["project"]
logger.debug("loading a full environment from project found in seeds")
project = mapper.unany(@seeds.named["project"])
env = mapper.generate(project, type: Vagrant::Environment)
else
logger.debug("loading a minimal environment from basis provided in args")
client = Client::Basis.load(basis, broker: broker)
env = Vagrant::Environment.new(ui: ui, client: client)
end
plugin = Vagrant.plugin("2").local_manager.commands[plugin_name.to_sym].to_a.first
if !plugin
raise "Failed to locate command plugin for: #{plugin_name}"