package core import ( "fmt" "reflect" "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 } var result core.Guest var result_name string var numParents int for name, g := range guests { guest := g.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 result == nil { if detected { result = guest result_name = name numParents = g.plugin.ParentCount() } continue } if detected { gp := g.plugin.ParentCount() if gp > numParents { result = guest result_name = name numParents = gp } } } if result == nil { return nil, fmt.Errorf("failed to detect guest plugin for current platform") } m.logger.Info("guest detection complete", "name", result_name) // NOTE: For guest seeding we need to prevent guest plugin instance // from being cached and reused. Currently, in a multi-machine setup // which are the same guest, the target values will get appended // TODO(spox): Fix this in the plugin manager m.seedPlugin(result) m.guest = result if named, ok := result.(core.Named); ok { named.SetPluginName(result_name) if err != nil { return nil, err } } g = result return result, nil } 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 // TODO(spox): Synced folders are getting duped in config. The `seen` is just a temporary // hack to prevent duplicate entries seen := map[string]bool{} folders = []*core.MachineSyncedFolder{} for _, folder := range syncedFolders { if _, ok := seen[folder.Destination]; ok { continue } seen[folder.Destination] = true // 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)