vaguerent/internal/core/machine.go
Chris Roberts b10ff0d51d
Sort guest plugins before running detection
When detecting the machine guest, first sort the guest plugins
    by the number of ancestors. This allows for returning on the
    first match instead of requiring running the detection process
    on every registered guest plugin.
2022-04-25 12:26:36 -05:00

305 lines
7.1 KiB
Go

package core
import (
"fmt"
"reflect"
"sort"
"github.com/golang/protobuf/ptypes"
"github.com/mitchellh/mapstructure"
"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/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
guest core.Guest
}
// 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) {
if value == "" {
return m.Destroy()
} else {
m.machine.Id = value
return m.SaveMachine()
}
}
func (m *Machine) Box() (b core.Box, err error) {
if m.box == nil {
// TODO: get provider info here too/generate full machine config?
// We know that these are machines so, save the Machine record
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 := m.Config().ConfigVm.Providers[0].Type
b, err := boxes.Find(boxName, "", provider)
if err != nil {
return nil, err
}
if b == nil {
// Add the box
b, err = addBox(boxName, provider, m.project.basis)
if err != nil {
return nil, err
}
}
m.machine.Box = b.(*Box).ToProto()
m.Save()
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 {
return
}
s, ok := g.(core.Seeder)
if !ok {
m.logger.Error("synced folder plugin does not implement seeder interface")
err = fmt.Errorf("cannot seed synced folder plugin")
return
}
err = m.seedWithMachine(s)
return
}()
// Try to see if a guest has already been found
if m.guest != nil {
return m.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, _ := m.project.basis.component(
m.ctx, component.GuestType, m.target.Configuration.ConfigVm.Guest)
if guest != nil {
m.guest = guest.Value.(core.Guest)
m.seedPlugin(m.guest)
g = m.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, err := guest.Detect(m.toTarget())
if err != 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,
)
m.seedPlugin(guest)
m.guest = guest
return guest, nil
}
}
return nil, fmt.Errorf("failed to detect guest plugin for current platform")
}
func (m *Machine) seedPlugin(plg interface{}) (err error) {
if s, ok := plg.(core.Seeder); ok {
seeds, err := s.Seeds()
if err != nil {
return err
}
seeds.Typed = append(seeds.Typed, m.Target)
if err = s.Seed(seeds); err != nil {
return err
}
}
return
}
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
}
// Reload implements core.Machine
func (m *Machine) Reload() (err error) {
// TODO
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) {
var result core.MachineState
return &result, mapstructure.Decode(m.machine.State, &result)
}
// 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
}
// 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 {
// TODO: get default synced folder type
folder.Type = "virtualbox"
plg, err := m.project.basis.component(m.ctx, component.SyncedFolderType, folder.Type)
if err != nil {
return nil, err
}
if s, ok := plg.Value.(core.Seeder); ok {
if err = m.seedWithMachine(s); err != nil {
return nil, err
}
} else {
m.logger.Error("synced folder plugin does not implement seeder interface")
return nil, fmt.Errorf("cannot seed synced folder plugin")
}
var f *core.Folder
mapstructure.Decode(folder, &f)
folders = append(folders, &core.MachineSyncedFolder{
Plugin: plg.Value.(core.SyncedFolder),
Folder: f,
})
}
return
}
func (m *Machine) SaveMachine() (err error) {
m.logger.Debug("saving machine to db", "machine", m.machine.Id)
m.target.Record, err = ptypes.MarshalAny(m.machine)
m.target.Uuid = m.machine.Id
if err != nil {
return nil
}
return m.Save()
}
func (m *Machine) toTarget() core.Target {
return m
}
func (m *Machine) seedWithMachine(s core.Seeder) error {
m.logger.Debug("seeding machine into plugin",
"plugin", hclog.Fmt("%T", s),
)
seeds, err := s.Seeds()
if err != nil {
m.logger.Error("failed to fetch seeds from plugin",
"plugin", hclog.Fmt("%T", s),
"error", err,
)
return err
}
for _, t := range seeds.Typed {
sm, ok := t.(*Machine)
if !ok {
continue
}
if m.target.ResourceId == sm.target.ResourceId {
return nil
}
}
seeds.Typed = append(seeds.Typed, m)
if err = s.Seed(seeds); err != nil {
m.logger.Error("failed to seed plugin",
"plugin", hclog.Fmt("%T", s),
"error", err,
)
}
return nil
}
var _ core.Machine = (*Machine)(nil)
var _ core.Target = (*Machine)(nil)