It turns out that synced folder plugins aren't returned in a consistent order, which was causing all kinds of mayhem. We can tone down that mayhem by implementing a shim of priority sorting the plugins. This shim can be removed once we have proper priority registration in the SDK.
383 lines
9.0 KiB
Go
383 lines
9.0 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"os/user"
|
|
"reflect"
|
|
"sort"
|
|
|
|
"github.com/mitchellh/mapstructure"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/core"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/helper/path"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cacher"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
|
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
|
)
|
|
|
|
type Machine struct {
|
|
*Target
|
|
box *Box
|
|
machine *vagrant_server.Target_Machine
|
|
logger hclog.Logger
|
|
cache cacher.Cache
|
|
}
|
|
|
|
// Close implements core.Machine
|
|
func (m *Machine) Close() (err error) {
|
|
return
|
|
}
|
|
|
|
// ID implements core.Machine
|
|
func (m *Machine) ID() (id string, err error) {
|
|
return m.machine.Id, nil
|
|
}
|
|
|
|
// SetID implements core.Machine
|
|
func (m *Machine) SetID(value string) (err error) {
|
|
m.machine.Id = value
|
|
|
|
// Also set uid
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.machine.Uid = user.Uid
|
|
|
|
// Persist changes
|
|
if value == "" {
|
|
m.target.Record = nil
|
|
err = m.Destroy()
|
|
} else {
|
|
err = m.SaveMachine()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (m *Machine) Box() (b core.Box, err error) {
|
|
if m.box == nil {
|
|
boxes, _ := m.project.Boxes()
|
|
boxName := m.Config().ConfigVm.Box
|
|
// Get the first provider available - that's the one that
|
|
// will be used to launch the machine
|
|
provider, err := m.ProviderName()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := boxes.Find(boxName, "", provider)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if b == nil {
|
|
return &Box{
|
|
basis: m.project.basis,
|
|
box: &vagrant_server.Box{
|
|
Name: boxName,
|
|
Provider: provider,
|
|
},
|
|
}, nil
|
|
}
|
|
m.machine.Box = b.(*Box).ToProto()
|
|
m.SaveMachine()
|
|
m.box = b.(*Box)
|
|
}
|
|
|
|
return m.box, nil
|
|
}
|
|
|
|
// Guest implements core.Machine
|
|
func (m *Machine) Guest() (g core.Guest, err error) {
|
|
defer func() {
|
|
if g != nil {
|
|
err = seedPlugin(g, m)
|
|
if err == nil {
|
|
m.cache.Register("guest", g)
|
|
}
|
|
}
|
|
}()
|
|
|
|
i := m.cache.Get("guest")
|
|
if i != nil {
|
|
return i.(core.Guest), nil
|
|
}
|
|
|
|
// Check if a guest is provided by the Vagrantfile. If it is, then try
|
|
// to use that guest
|
|
// TODO: This check maybe needs to get reworked when the Vagrantfile bits
|
|
// actually start getting used
|
|
if m.target.Configuration.ConfigVm.Guest != "" {
|
|
// Ignore error about guest not being found - will also try detecting the guest
|
|
guest, cerr := m.project.basis.component(
|
|
m.ctx, component.GuestType, m.target.Configuration.ConfigVm.Guest)
|
|
if cerr != nil {
|
|
return nil, cerr
|
|
}
|
|
if guest != nil {
|
|
g = guest.Value.(core.Guest)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Try to detect a guest
|
|
guests, err := m.project.basis.typeComponents(m.ctx, component.GuestType)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
names := make([]string, 0, len(guests))
|
|
pcount := map[string]int{}
|
|
|
|
for name, g := range guests {
|
|
names = append(names, name)
|
|
pcount[name] = g.plugin.ParentCount()
|
|
}
|
|
|
|
sort.Slice(names, func(i, j int) bool {
|
|
in := names[i]
|
|
jn := names[j]
|
|
// TODO check values exist before use
|
|
return pcount[in] > pcount[jn]
|
|
})
|
|
|
|
for _, name := range names {
|
|
guest := guests[name].Value.(core.Guest)
|
|
detected, gerr := guest.Detect(m.toTarget())
|
|
if gerr != nil {
|
|
m.logger.Error("guest error on detection check",
|
|
"plugin", name,
|
|
"type", "Guest",
|
|
"error", err)
|
|
|
|
continue
|
|
}
|
|
if detected {
|
|
m.logger.Info("guest detection complete",
|
|
"name", name,
|
|
)
|
|
g = guest
|
|
return
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to detect guest plugin for current platform")
|
|
}
|
|
|
|
func (m *Machine) Inspect() (printable string, err error) {
|
|
name, err := m.Name()
|
|
provider, err := m.Provider()
|
|
printable = "#<" + reflect.TypeOf(m).String() + ": " + name + " (" + reflect.TypeOf(provider).String() + ")>"
|
|
return
|
|
}
|
|
|
|
// ConnectionInfo implements core.Machine
|
|
func (m *Machine) ConnectionInfo() (info *core.ConnectionInfo, err error) {
|
|
// TODO: need Vagrantfile
|
|
return
|
|
}
|
|
|
|
// MachineState implements core.Machine
|
|
func (m *Machine) MachineState() (state *core.MachineState, err error) {
|
|
p, err := m.Provider()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.State()
|
|
}
|
|
|
|
// SetMachineState implements core.Machine
|
|
func (m *Machine) SetMachineState(state *core.MachineState) (err error) {
|
|
var st *vagrant_plugin_sdk.Args_Target_Machine_State
|
|
mapstructure.Decode(state, &st)
|
|
m.machine.State = st
|
|
|
|
switch st.Id {
|
|
case "not_created":
|
|
m.target.State = vagrant_server.Operation_UNKNOWN
|
|
case "running":
|
|
m.target.State = vagrant_server.Operation_CREATED
|
|
case "poweroff":
|
|
m.target.State = vagrant_server.Operation_DESTROYED
|
|
case "pending":
|
|
m.target.State = vagrant_server.Operation_PENDING
|
|
default:
|
|
m.target.State = vagrant_server.Operation_UNKNOWN
|
|
}
|
|
|
|
return m.SaveMachine()
|
|
}
|
|
|
|
func (m *Machine) UID() (userId string, err error) {
|
|
return m.machine.Uid, nil
|
|
}
|
|
|
|
func StringToPathFunc() mapstructure.DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
if !t.Implements(reflect.TypeOf((*path.Path)(nil)).Elem()) {
|
|
return data, nil
|
|
}
|
|
|
|
// Convert it
|
|
return path.NewPath(data.(string)), nil
|
|
}
|
|
}
|
|
|
|
// 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
|
|
machineConfig := config.ConfigVm
|
|
syncedFolders := machineConfig.SyncedFolders
|
|
|
|
folders = []*core.MachineSyncedFolder{}
|
|
for _, folder := range syncedFolders {
|
|
folder.Type, err = m.defaultSyncedFolderType()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lookup := "syncedfolder_" + *(folder.Type)
|
|
v := m.cache.Get(lookup)
|
|
if v == nil {
|
|
plg, err := m.project.basis.component(m.ctx, component.SyncedFolderType, *folder.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v = plg.Value.(core.SyncedFolder)
|
|
|
|
m.cache.Register(lookup, v)
|
|
}
|
|
|
|
if err = seedPlugin(v, m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var f *core.Folder
|
|
c := &mapstructure.DecoderConfig{
|
|
DecodeHook: StringToPathFunc(),
|
|
Result: &f,
|
|
}
|
|
decoder, err := mapstructure.NewDecoder(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = decoder.Decode(folder)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
folders = append(folders, &core.MachineSyncedFolder{
|
|
Plugin: v.(core.SyncedFolder),
|
|
Folder: f,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *Machine) SaveMachine() (err error) {
|
|
m.logger.Debug("saving machine to db", "machine", m.machine.Id)
|
|
// Update the target record and uuid to match the machine's new state
|
|
m.target.Record, err = anypb.New(m.machine)
|
|
m.target.Uuid = m.machine.Id
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return m.Save()
|
|
}
|
|
|
|
func (m *Machine) toTarget() core.Target {
|
|
return m
|
|
}
|
|
|
|
var _ core.Machine = (*Machine)(nil)
|
|
var _ core.Target = (*Machine)(nil)
|