vaguerent/internal/core/machine.go
Paul Hinze 8b08b3117c
Use plugin options for Synced Folder priorities
Now that plugin options are available, we can use them to interpret
synced folder priorities and remove the shim we had in place.
2022-06-06 17:39:09 -05:00

370 lines
8.8 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
}
}
func (m *Machine) defaultSyncedFolderType() (folderType *string, err error) {
logger := m.logger.Named("default-synced-folder-type")
// Get all available synced folder plugins
sfPlugins, err := m.project.basis.plugins.ListPlugins("synced_folder")
if err != nil {
return
}
// Load full plugins so we can read options
for i, sfp := range sfPlugins {
fullPlugin, err := m.project.basis.plugins.GetPlugin(sfp.Name, sfp.Type)
if err != nil {
logger.Error("error while loading synced folder plugin: name", sfp.Name, "err", err)
return nil, fmt.Errorf("error while loading synced folder plugin: name = %s, err = %s", sfp.Name, err)
}
sfPlugins[i] = fullPlugin
}
// Sort by plugin priority. Higher is first
sort.SliceStable(sfPlugins, func(i, j int) bool {
iPriority := sfPlugins[i].Options.(*component.SyncedFolderOptions).Priority
jPriority := sfPlugins[j].Options.(*component.SyncedFolderOptions).Priority
return iPriority > jPriority
})
logger.Debug("sorted synced folder plugins", "names", sfPlugins)
// 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 _, sfp := range sfPlugins {
if _, ok := allowed[sfp.Name]; ok {
sfPlugins[k] = sfp
k++
} else {
logger.Debug("removing disallowed plugin", "type", sfp.Name)
}
}
sfPlugins = sfPlugins[:k]
}
// Check for first usable plugin
for _, sfp := range sfPlugins {
syncedFolder := sfp.Plugin.(core.SyncedFolder)
usable, err := syncedFolder.Usable(m)
if err != nil {
logger.Error("error on usable check", "plugin", sfp.Name, "error", err)
continue
}
if usable {
logger.Info("returning default", "name", sfp.Name)
return &sfp.Name, nil
} else {
logger.Debug("skipping unusable plugin", "name", sfp.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 {
if folder.GetType() == "" {
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)