diff --git a/internal/core/helpers_test.go b/internal/core/helpers_test.go new file mode 100644 index 000000000..7a70cc3ad --- /dev/null +++ b/internal/core/helpers_test.go @@ -0,0 +1,72 @@ +package core + +import ( + "testing" + + "github.com/hashicorp/vagrant-plugin-sdk/component" + "github.com/hashicorp/vagrant/internal/plugin" +) + +// Synced folder entry +type testSyncedFolder struct { + source string + destination string + kind string +} + +// Add vm box name to configuration +func testBoxConfig(name string) *component.ConfigData { + return &component.ConfigData{ + Data: map[string]interface{}{ + "vm": &component.ConfigData{ + Data: map[string]interface{}{ + "box": name, + }, + }, + }, + } +} + +// Add synced folders to vm configuration +func testSyncedFolderConfig(folders []*testSyncedFolder) *component.ConfigData { + f := map[interface{}]interface{}{} + for _, tf := range folders { + f[tf.destination] = map[interface{}]interface{}{ + "hostpath": tf.source, + "guestpath": tf.destination, + "type": tf.kind, + } + } + + return &component.ConfigData{ + Data: map[string]interface{}{ + "vm": &component.ConfigData{ + Data: map[string]interface{}{ + "__synced_folders": f, + }, + }, + }, + } +} + +// Set guest name in vm configuration +func testGuestConfig(name string) *component.ConfigData { + return &component.ConfigData{ + Data: map[string]interface{}{ + "vm": &component.ConfigData{ + Data: map[string]interface{}{ + "guest": name, + }, + }, + }, + } +} + +// Generate a synced folder plugin +func syncedFolderPlugin(t *testing.T, name string) *plugin.Plugin { + return plugin.TestPlugin(t, + BuildTestSyncedFolderPlugin(""), + plugin.WithPluginName(name), + plugin.WithPluginTypes(component.SyncedFolderType), + ) +} diff --git a/internal/core/machine.go b/internal/core/machine.go index 9139e6fc9..9bac5d04a 100644 --- a/internal/core/machine.go +++ b/internal/core/machine.go @@ -107,6 +107,10 @@ func (m *Machine) Guest() (g core.Guest, err error) { } }() + // Note that if we get the guest value from the + // local cache, we return it directly to prevent + // the seeding and cache registration from happening + // again. i := m.cache.Get("guest") if i != nil { return i.(core.Guest), nil @@ -122,13 +126,13 @@ func (m *Machine) Guest() (g core.Guest, err error) { } else { guestName, ok := vg.(string) if ok { - guest, err := m.project.basis.component(m.ctx, component.GuestType, guestName) + var guest *Component + guest, err = m.project.basis.component(m.ctx, component.GuestType, guestName) if err != nil { return nil, err } - if guest != nil { - return guest.Value.(core.Guest), nil - } + g = guest.Value.(core.Guest) + return } else { m.logger.Debug("guest name was not a valid string value", "guest", vg, @@ -388,7 +392,15 @@ func (m *Machine) SyncedFolders() (folders []*core.MachineSyncedFolder, err erro } } if ftype == "" { - ftype = "virtualbox" // TODO(spox): use default type function after rebase + ftype, err = m.project.DefaultProvider( + &core.DefaultProviderOptions{ + CheckUsable: false, + MachineName: m.target.Name, + }, + ) + if err != nil { + return nil, err + } } lookup := "syncedfolder_" + ftype diff --git a/internal/core/machine_test.go b/internal/core/machine_test.go index d3e469646..165a75c64 100644 --- a/internal/core/machine_test.go +++ b/internal/core/machine_test.go @@ -10,15 +10,10 @@ import ( "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/anypb" - "google.golang.org/protobuf/types/known/wrapperspb" ) func TestMachineSetValidId(t *testing.T) { - tm, err := TestMinimalMachine(t) - if err != nil { - t.Fatal(err) - } + tm := TestMinimalMachine(t) // Set valid id tm.SetID("something") @@ -41,7 +36,7 @@ func TestMachineSetValidId(t *testing.T) { } func TestMachineSetEmptyId(t *testing.T) { - tm, _ := TestMinimalMachine(t) + tm := TestMinimalMachine(t) oldId := tm.target.ResourceId // Set empty id @@ -85,7 +80,7 @@ func TestMachineSetEmptyId(t *testing.T) { } func TestMachineSetIdBlankThenSomethingPreservesDataDir(t *testing.T) { - tm, _ := TestMinimalMachine(t) + tm := TestMinimalMachine(t) // Set empty id, followed by a temp id. This is the same thing that happens // in the Docker provider's InitState action @@ -102,8 +97,8 @@ func TestMachineSetIdBlankThenSomethingPreservesDataDir(t *testing.T) { func TestMachineGetNonExistentBox(t *testing.T) { tp := TestMinimalProject(t) - tm, _ := TestMachine(t, tp, - WithTestTargetConfig(testBoxConfig("somename")), + tm := TestMachine(t, tp, + WithTestTargetConfig(testBoxConfig("somebox")), WithTestTargetProvider("testprovider"), ) @@ -120,133 +115,11 @@ func TestMachineGetNonExistentBox(t *testing.T) { require.Empty(t, metaurl) } -func testBoxConfig(name string) *vagrant_plugin_sdk.Args_ConfigData { - b_key, _ := anypb.New(&wrapperspb.StringValue{Value: "box"}) - b_name, _ := anypb.New(&wrapperspb.StringValue{Value: name}) - vm_key, _ := anypb.New(&wrapperspb.StringValue{Value: "vm"}) - vm, _ := anypb.New(&vagrant_plugin_sdk.Args_ConfigData{ - Data: &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: b_key, - Value: b_name, - }, - }, - }, - }) - - return &vagrant_plugin_sdk.Args_ConfigData{ - Data: &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: vm_key, - Value: vm, - }, - }, - }, - } -} - -type testSyncedFolder struct { - source string - destination string - kind string -} - -func testSyncedFolderConfig(folders []*testSyncedFolder) *vagrant_plugin_sdk.Args_ConfigData { - f := &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{}, - } - src_key, _ := anypb.New(&wrapperspb.StringValue{Value: "hostpath"}) - dst_key, _ := anypb.New(&wrapperspb.StringValue{Value: "guestpath"}) - type_key, _ := anypb.New(&wrapperspb.StringValue{Value: "type"}) - for i := 0; i < len(folders); i++ { - fld := folders[i] - f_src, _ := anypb.New(&wrapperspb.StringValue{Value: fld.source}) - f_dst, _ := anypb.New(&wrapperspb.StringValue{Value: fld.destination}) - f_type, _ := anypb.New(&wrapperspb.StringValue{Value: fld.kind}) - - hsh := &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: src_key, - Value: f_src, - }, - { - Key: dst_key, - Value: f_dst, - }, - { - Key: type_key, - Value: f_type, - }, - }, - } - entry, _ := anypb.New(hsh) - f.Entries = append(f.Entries, - &vagrant_plugin_sdk.Args_HashEntry{ - Key: f_dst, - Value: entry, - }, - ) - } - f_key, _ := anypb.New(&wrapperspb.StringValue{Value: "__synced_folders"}) - f_value, _ := anypb.New(f) - vm_key, _ := anypb.New(&wrapperspb.StringValue{Value: "vm"}) - vm, _ := anypb.New(&vagrant_plugin_sdk.Args_ConfigData{ - Data: &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: f_key, - Value: f_value, - }, - }, - }, - }) - - return &vagrant_plugin_sdk.Args_ConfigData{ - Data: &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: vm_key, - Value: vm, - }, - }, - }, - } -} - -func testGuestConfig(name string) *vagrant_plugin_sdk.Args_ConfigData { - g_key, _ := anypb.New(&wrapperspb.StringValue{Value: "guest"}) - g_name, _ := anypb.New(&wrapperspb.StringValue{Value: name}) - vm_key, _ := anypb.New(&wrapperspb.StringValue{Value: "vm"}) - vm, _ := anypb.New(&vagrant_plugin_sdk.Args_ConfigData{ - Data: &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: g_key, - Value: g_name, - }, - }, - }, - }) - - return &vagrant_plugin_sdk.Args_ConfigData{ - Data: &vagrant_plugin_sdk.Args_Hash{ - Entries: []*vagrant_plugin_sdk.Args_HashEntry{ - { - Key: vm_key, - Value: vm, - }, - }, - }, - } -} - func TestMachineGetExistentBox(t *testing.T) { tp := TestMinimalProject(t) - tm, _ := TestMachine(t, tp, + tm := TestMachine(t, tp, WithTestTargetConfig(testBoxConfig("test/box")), + WithTestTargetProvider("virtualbox"), ) testBox := newFullBox(t, testboxBoxData(), tp.basis) testBox.Save() @@ -266,7 +139,7 @@ func TestMachineGetExistentBox(t *testing.T) { func TestMachineConfigedGuest(t *testing.T) { type test struct { - config *vagrant_plugin_sdk.Args_ConfigData + config *component.ConfigData errors bool } @@ -275,7 +148,7 @@ func TestMachineConfigedGuest(t *testing.T) { {config: testGuestConfig("idontexist"), errors: true}, } guestMock := BuildTestGuestPlugin("myguest", "") - guestMock.On("Detect", mock.AnythingOfType("*core.Machine")).Return(false, nil) + guestMock.On("Detect", mock.AnythingOfType("*core.Machine")).Return(true, nil) guestMock.On("Parent").Return("", nil) pluginManager := plugin.TestManager(t, @@ -288,7 +161,7 @@ func TestMachineConfigedGuest(t *testing.T) { for _, tc := range tests { tp := TestProject(t, WithPluginManager(pluginManager)) - tm, _ := TestMachine(t, tp, + tm := TestMachine(t, tp, WithTestTargetConfig(tc.config), ) guest, err := tm.Guest() @@ -350,7 +223,7 @@ func TestMachineNoConfigGuest(t *testing.T) { pluginManager := plugin.TestManager(t, tc.plugins...) tp := TestProject(t, WithPluginManager(pluginManager)) - tm, _ := TestMachine(t, tp, WithTestTargetMinimalConfig()) + tm := TestMachine(t, tp) guest, err := tm.Guest() if tc.errors { require.Error(t, err) @@ -369,7 +242,7 @@ func TestMachineNoConfigGuest(t *testing.T) { } func TestMachineSetState(t *testing.T) { - tm, _ := TestMinimalMachine(t) + tm := TestMinimalMachine(t) type test struct { id string @@ -402,21 +275,13 @@ func TestMachineSetState(t *testing.T) { } } -func syncedFolderPlugin(t *testing.T, name string) *plugin.Plugin { - return plugin.TestPlugin(t, - BuildTestSyncedFolderPlugin(""), - plugin.WithPluginName(name), - plugin.WithPluginTypes(component.SyncedFolderType), - ) -} - func TestMachineSyncedFolders(t *testing.T) { mySyncedFolder := syncedFolderPlugin(t, "mysyncedfolder") myOtherSyncedFolder := syncedFolderPlugin(t, "myothersyncedfolder") type test struct { plugins []*plugin.Plugin - config *vagrant_plugin_sdk.Args_ConfigData + config *component.ConfigData errors bool expectedFolders int } @@ -427,7 +292,7 @@ func TestMachineSyncedFolders(t *testing.T) { errors: false, config: testSyncedFolderConfig( []*testSyncedFolder{ - &testSyncedFolder{ + { source: ".", destination: "/vagrant", kind: "mysyncedfolder", @@ -442,20 +307,20 @@ func TestMachineSyncedFolders(t *testing.T) { errors: false, config: testSyncedFolderConfig( []*testSyncedFolder{ - &testSyncedFolder{ + { source: ".", destination: "/vagrant", kind: "mysyncedfolder", }, - &testSyncedFolder{ + { source: "./two", destination: "/vagrant-two", kind: "mysyncedfolder", }, - &testSyncedFolder{ + { source: "./three", destination: "/vagrant-three", - kind: "mysyncedfolder", + kind: "myothersyncedfolder", }, }, ), @@ -467,20 +332,20 @@ func TestMachineSyncedFolders(t *testing.T) { errors: true, config: testSyncedFolderConfig( []*testSyncedFolder{ - &testSyncedFolder{ + { source: ".", destination: "/vagrant", - kind: "mysyncedfolder", + kind: "idontexist", }, - &testSyncedFolder{ + { source: "./two", destination: "/vagrant-two", kind: "mysyncedfolder", }, - &testSyncedFolder{ + { source: "./three", destination: "/vagrant-three", - kind: "mysyncedfolder", + kind: "myothersyncedfolder", }, }, ), @@ -490,7 +355,7 @@ func TestMachineSyncedFolders(t *testing.T) { for _, tc := range tests { pluginManager := plugin.TestManager(t, tc.plugins...) tp := TestProject(t, WithPluginManager(pluginManager)) - tm, _ := TestMachine(t, tp, + tm := TestMachine(t, tp, WithTestTargetConfig(tc.config), ) folders, err := tm.SyncedFolders() diff --git a/internal/core/project.go b/internal/core/project.go index 884db8492..f2e6c67e7 100644 --- a/internal/core/project.go +++ b/internal/core/project.go @@ -13,7 +13,9 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" goplugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "github.com/hashicorp/vagrant-plugin-sdk/component" @@ -436,12 +438,12 @@ func (p *Project) JobInfo() *component.JobInfo { // LoadTarget loads a target within the current project. If the target is not // found, it will be created. -func (p *Project) LoadTarget(topts ...TargetOption) (t *Target, err error) { +func (p *Project) LoadTarget(topts ...TargetOption) (*Target, error) { p.m.Lock() defer p.m.Unlock() // Create our target - t = &Target{ + t := &Target{ cache: cacher.New(), ctx: p.ctx, project: p, @@ -451,6 +453,7 @@ func (p *Project) LoadTarget(topts ...TargetOption) (t *Target, err error) { }, ui: p.ui, } + var err error // Apply any options provided for _, opt := range topts { @@ -481,12 +484,15 @@ func (p *Project) LoadTarget(topts ...TargetOption) (t *Target, err error) { if err != nil { return nil, err } - t.vagrantfile = tv.(*Vagrantfile) + // Set the vagrantfile if one was returned + if tv != nil { + t.vagrantfile = tv.(*Vagrantfile) + } } // If this is the first time through, re-init the target if err = t.init(); err != nil { - return + return nil, err } // If the data directory is set, set it @@ -521,7 +527,7 @@ func (p *Project) LoadTarget(topts ...TargetOption) (t *Target, err error) { p.targets[t.target.ResourceId] = t p.targets[t.target.Name] = t - return + return t, nil } // Client returns the API client for the backend server. @@ -724,14 +730,7 @@ func (p *Project) InitTargets() (err error) { updated := false for _, t := range targets { - _, err = p.Client().UpsertTarget(p.ctx, - &vagrant_server.UpsertTargetRequest{ - Target: &vagrant_server.Target{ - Name: t, - Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project), - }, - }, - ) + _, err = p.createTarget(t) if err != nil { p.logger.Error("failed to initialize target with project", "project", p.Name(), @@ -775,6 +774,44 @@ func (p *Project) refreshProject() (err error) { return } +// Create a target within this project if it does not already exist +func (p *Project) createTarget( + name string, // name of the target +) (*vagrant_server.Target, error) { + result, err := p.Client().FindTarget(p.ctx, + &vagrant_server.FindTargetRequest{ + Target: &vagrant_server.Target{ + Name: name, + Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project), + }, + }, + ) + // If we encountered any error except a not found, return it + if err != nil && status.Code(err) != codes.NotFound { + return nil, err + } + + // If we have no error here, we have an existing result + if err == nil { + return result.Target, nil + } + + // And if we are still here, create it + resp, err := p.Client().UpsertTarget(p.ctx, + &vagrant_server.UpsertTargetRequest{ + Target: &vagrant_server.Target{ + Name: name, + Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project), + }, + }, + ) + if err != nil { + return nil, err + } + + return resp.Target, nil +} + // Calls the function provided and converts the // result to an expected type. If no type conversion // is required, a `false` value for the expectedType @@ -890,9 +927,10 @@ func WithProjectRef(r *vagrant_plugin_sdk.Ref_Project) ProjectOption { result, err := p.Client().FindProject(p.ctx, &vagrant_server.FindProjectRequest{ Project: &vagrant_server.Project{ - Basis: r.Basis, - Name: r.Name, - Path: r.Path, + Basis: r.Basis, + Name: r.Name, + Path: r.Path, + ResourceId: r.ResourceId, }, }, ) @@ -916,7 +954,7 @@ func WithProjectRef(r *vagrant_plugin_sdk.Ref_Project) ProjectOption { } // Before we init, validate basis is consistent - if project.Basis.ResourceId != r.Basis.ResourceId { + if r.Basis != nil && project.Basis.ResourceId != r.Basis.ResourceId { p.logger.Error("invalid basis for project", "request-basis", r.Basis, "project-basis", project.Basis) return errors.New("project basis configuration is invalid") diff --git a/internal/core/project_test.go b/internal/core/project_test.go index ddc444674..514443b00 100644 --- a/internal/core/project_test.go +++ b/internal/core/project_test.go @@ -11,14 +11,11 @@ import ( func projectTargets(t *testing.T, project *Project, numTargets int) (targets []*Target) { targets = make([]*Target, numTargets) for i := 0; i < numTargets; i++ { - tt, err := TestTarget(t, project, &vagrant_server.Target{ + tt := TestTarget(t, project, &vagrant_server.Target{ ResourceId: fmt.Sprintf("id-%d", i), Name: fmt.Sprintf("target-%d", i), Uuid: fmt.Sprintf("uuid-%d", i), }) - if err != nil { - t.Error(err) - } targets = append(targets, tt) } return @@ -35,14 +32,8 @@ func TestNewProject(t *testing.T) { func TestProjectGetTarget(t *testing.T) { tp := TestMinimalProject(t) // Add targets to project - targetOne, err := TestTarget(t, tp, &vagrant_server.Target{ResourceId: "id-one", Name: "target-one"}) - if err != nil { - t.Error(err) - } - targetTwo, err := TestTarget(t, tp, &vagrant_server.Target{ResourceId: "id-two", Name: "target-two"}) - if err != nil { - t.Error(err) - } + targetOne := TestTarget(t, tp, &vagrant_server.Target{ResourceId: "id-one", Name: "target-one"}) + targetTwo := TestTarget(t, tp, &vagrant_server.Target{ResourceId: "id-two", Name: "target-two"}) // Get by id one, err := tp.Target("id-one", "") diff --git a/internal/core/target.go b/internal/core/target.go index 71d047283..e80513b6d 100644 --- a/internal/core/target.go +++ b/internal/core/target.go @@ -472,41 +472,20 @@ func (t *Target) init() (err error) { // If the configuration was updated during load, save it so // we can re-apply after loading stored data var conf *vagrant_plugin_sdk.Args_ConfigData - if t.target != nil && t.target.Configuration != nil { + if t.target.Configuration != nil { conf = t.target.Configuration } - // First we want to run a lookup if this target already exists - if t.target.ResourceId != "" { - resp, err := t.Client().FindTarget(t.ctx, - &vagrant_server.FindTargetRequest{ - Target: &vagrant_server.Target{ - ResourceId: t.target.ResourceId, - }, - }, - ) - if err != nil { - return err - } - - t.target = resp.Target - } else { - for _, pt := range t.project.project.Targets { - if t.target.Name == pt.Name { - resp, err := t.Client().FindTarget(t.ctx, - &vagrant_server.FindTargetRequest{ - Target: &vagrant_server.Target{ - ResourceId: pt.ResourceId, - }, - }, - ) - if err != nil { - return err - } - t.target = resp.Target - } - } + // Pull target info + resp, err := t.Client().FindTarget(t.ctx, + &vagrant_server.FindTargetRequest{ + Target: t.target, + }, + ) + if err != nil { + return } + t.target = resp.Target // If we have configuration data, re-apply it if conf != nil { @@ -524,6 +503,10 @@ func (t *Target) init() (err error) { // If we don't have configuration data, just stub if t.target.Configuration == nil { t.target.Configuration = &vagrant_plugin_sdk.Args_ConfigData{} + t.vagrantfile = t.project.vagrantfile.clone("target", t) + t.vagrantfile.root = &component.ConfigData{ + Data: map[string]interface{}{}, + } return } @@ -583,58 +566,34 @@ func WithTargetName(name string) TargetOption { // Configure target with proto ref func WithTargetRef(r *vagrant_plugin_sdk.Ref_Target) TargetOption { - return func(t *Target) (err error) { - // Project must be set before we continue - if t.project == nil { - return fmt.Errorf("project must be set before loading target") - } - + return func(t *Target) error { // Target ref must include a resource id or name if r.Name == "" && r.ResourceId == "" { return fmt.Errorf("target ref must include ResourceId and/or Name") } - var target *vagrant_server.Target + // Target ref must include project ref if resource id is empty + if r.Name == "" && r.Project == nil { + return fmt.Errorf("target ref must include Project for name lookup") + } + result, err := t.Client().FindTarget(t.ctx, &vagrant_server.FindTargetRequest{ Target: &vagrant_server.Target{ ResourceId: r.ResourceId, Name: r.Name, - Project: t.project.Ref().(*vagrant_plugin_sdk.Ref_Project), + Project: r.Project, }, }, ) - // TODO(spox): check for not found and error if something different - // if err != nil { - // return err - // } - if result != nil { - target = result.Target - } else { - var result *vagrant_server.UpsertTargetResponse - result, err = t.Client().UpsertTarget(t.ctx, - &vagrant_server.UpsertTargetRequest{ - Target: &vagrant_server.Target{ - Name: r.Name, - Project: t.project.Ref().(*vagrant_plugin_sdk.Ref_Project), - }, - }, - ) - if err != nil { - return - } - target = result.Target + if err != nil { + return err } - if r.Project != nil && target.Project.ResourceId != r.Project.ResourceId { - t.logger.Error("invalid project for target", - "request-project", r.Project, - "target-project", target.Project) - return fmt.Errorf("target project configuration is invalid") - } - t.target = target - return + t.target = result.Target + + return nil } } diff --git a/internal/core/target_index_test.go b/internal/core/target_index_test.go index 5b90c78b7..bf56b7486 100644 --- a/internal/core/target_index_test.go +++ b/internal/core/target_index_test.go @@ -79,8 +79,7 @@ func TestTargetIndexSet(t *testing.T) { t.Error(err) } - tt, err := TestMinimalTarget(t) - require.NoError(t, err) + tt := TestMinimalTarget(t) tt.target.Name = "newName" updated, err := ti.Set(tt) diff --git a/internal/core/target_test.go b/internal/core/target_test.go index 5db939c94..e67b838a4 100644 --- a/internal/core/target_test.go +++ b/internal/core/target_test.go @@ -9,7 +9,7 @@ import ( ) func TestTargetSpecializeMachine(t *testing.T) { - tt, _ := TestMinimalTarget(t) + tt := TestMinimalTarget(t) specialized, err := tt.Specialize((*core.Machine)(nil)) if err != nil { t.Errorf("Specialize function returned an error") @@ -28,8 +28,8 @@ func TestTargetSpecializeMachine(t *testing.T) { func TestTargetSpecializeMultiMachine(t *testing.T) { p := TestMinimalProject(t) - tt1, _ := TestTarget(t, p, &vagrant_server.Target{Name: "tt1"}) - tt2, _ := TestTarget(t, p, &vagrant_server.Target{Name: "tt2"}) + tt1 := TestTarget(t, p, &vagrant_server.Target{Name: "tt1"}) + tt2 := TestTarget(t, p, &vagrant_server.Target{Name: "tt2"}) specialized, err := tt1.Specialize((*core.Machine)(nil)) if err != nil { @@ -53,7 +53,7 @@ func TestTargetSpecializeMultiMachine(t *testing.T) { } func TestTargetSpecializeBad(t *testing.T) { - tt, _ := TestMinimalTarget(t) + tt := TestMinimalTarget(t) specialized, err := tt.Specialize((*core.Project)(nil)) if err != nil { diff --git a/internal/core/testing_basis.go b/internal/core/testing_basis.go index 06cb61f04..080d9821f 100644 --- a/internal/core/testing_basis.go +++ b/internal/core/testing_basis.go @@ -105,10 +105,3 @@ func TestBasis(t testing.T, opts ...BasisOption) (b *Basis) { b, _ = NewBasis(context.Background(), append(defaultOpts, opts...)...) return } - -// func WithTestBasisConfig(config *vagrant_plugin_sdk.Vagrantfile_Vagrantfile) BasisOption { -// return func(m *Basis) (err error) { -// m.basis.Configuration = config -// return -// } -// } diff --git a/internal/core/testing_project.go b/internal/core/testing_project.go index 3b52c2789..4b3bd9a2c 100644 --- a/internal/core/testing_project.go +++ b/internal/core/testing_project.go @@ -12,12 +12,17 @@ import ( // factories, configuration, etc. func TestProject(t testing.T, opts ...BasisOption) *Project { b := TestBasis(t, opts...) - p, _ := b.LoadProject([]ProjectOption{ + p, err := b.LoadProject([]ProjectOption{ WithProjectRef(&vagrant_plugin_sdk.Ref_Project{ Basis: b.Ref().(*vagrant_plugin_sdk.Ref_Basis), Name: "test-project"}, ), }...) + + if err != nil { + t.Fatal(err) + } + return p } @@ -27,11 +32,16 @@ func TestMinimalProject(t testing.T) *Project { pluginManager := plugin.TestManager(t) b := TestBasis(t, WithPluginManager(pluginManager)) - p, _ := b.LoadProject([]ProjectOption{ + p, err := b.LoadProject([]ProjectOption{ WithProjectRef(&vagrant_plugin_sdk.Ref_Project{ Basis: b.Ref().(*vagrant_plugin_sdk.Ref_Basis), Name: "test-project"}, ), }...) + + if err != nil { + t.Fatal(err) + } + return p } diff --git a/internal/core/testing_target.go b/internal/core/testing_target.go index d3f5de23c..173104ffd 100644 --- a/internal/core/testing_target.go +++ b/internal/core/testing_target.go @@ -2,8 +2,12 @@ package core import ( "context" + "fmt" + + "github.com/imdario/mergo" "github.com/hashicorp/go-multierror" + "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" @@ -14,32 +18,53 @@ import ( // TestTarget returns a fully in-memory and side-effect free Target that // can be used for testing. Additional options can be given to provide your own // factories, configuration, etc. -func TestTarget(t testing.T, tp *Project, tt *vagrant_server.Target) (target *Target, err error) { - testingTarget := ptypes.TestTarget(t, tt) - testingTarget.Project = tp.Ref().(*vagrant_plugin_sdk.Ref_Project) - tp.basis.client.UpsertTarget( +func TestTarget(t testing.T, p *Project, st *vagrant_server.Target, opts ...TestTargetOption) (target *Target) { + testingTarget := ptypes.TestTarget(t, st) + testingTarget.Project = p.Ref().(*vagrant_plugin_sdk.Ref_Project) + _, err := p.basis.client.UpsertTarget( context.Background(), &vagrant_server.UpsertTargetRequest{ - Project: tp.Ref().(*vagrant_plugin_sdk.Ref_Project), + Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project), Target: testingTarget, }, ) - target, err = tp.LoadTarget([]TargetOption{ - WithTargetRef(&vagrant_plugin_sdk.Ref_Target{Project: tp.Ref().(*vagrant_plugin_sdk.Ref_Project), Name: testingTarget.Name}), + if err != nil { + t.Fatal(err) + } + + target, err = p.LoadTarget([]TargetOption{ + WithTargetRef( + &vagrant_plugin_sdk.Ref_Target{ + Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project), + Name: testingTarget.Name, + }, + ), }...) if err != nil { t.Fatal(err) - return } - tp.project.Targets = append(tp.project.Targets, target.Ref().(*vagrant_plugin_sdk.Ref_Target)) + + if err = p.refreshProject(); err != nil { + t.Fatal(err) + } + + for _, opt := range opts { + if oerr := opt(target); oerr != nil { + err = multierror.Append(err, oerr) + } + } + if err != nil { + t.Fatal(err) + } + return } // TestMinimalTarget uses a minimal project to setup the most basic target // that will work for testing -func TestMinimalTarget(t testing.T) (target *Target, err error) { +func TestMinimalTarget(t testing.T) (target *Target) { tp := TestMinimalProject(t) - tp.basis.client.UpsertTarget( + _, err := tp.basis.client.UpsertTarget( context.Background(), &vagrant_server.UpsertTargetRequest{ Project: tp.Ref().(*vagrant_plugin_sdk.Ref_Project), @@ -49,10 +74,18 @@ func TestMinimalTarget(t testing.T) (target *Target, err error) { }, }, ) - target, err = tp.LoadTarget([]TargetOption{ - WithTargetRef(&vagrant_plugin_sdk.Ref_Target{Project: tp.Ref().(*vagrant_plugin_sdk.Ref_Project), Name: "test-target"}), - }...) + if err != nil { + t.Fatal(err) + } + target, err = tp.LoadTarget([]TargetOption{ + WithTargetRef( + &vagrant_plugin_sdk.Ref_Target{ + Project: tp.Ref().(*vagrant_plugin_sdk.Ref_Project), + Name: "test-target", + }, + ), + }...) if err != nil { t.Fatal(err) } @@ -63,63 +96,64 @@ func TestMinimalTarget(t testing.T) (target *Target, err error) { // TestMachine returns a fully in-memory and side-effect free Machine that // can be used for testing. Additional options can be given to provide your own // factories, configuration, etc. -func TestMachine(t testing.T, tp *Project, opts ...TestMachineOption) (machine *Machine, err error) { - tt, _ := TestTarget(t, tp, &vagrant_server.Target{}) +func TestMachine(t testing.T, tp *Project, opts ...TestTargetOption) (machine *Machine) { + tt := TestTarget(t, tp, &vagrant_server.Target{}) specialized, err := tt.Specialize((*core.Machine)(nil)) if err != nil { - return nil, err + t.Fatal(err) } + machine = specialized.(*Machine) for _, opt := range opts { if oerr := opt(machine); oerr != nil { err = multierror.Append(err, oerr) } } - if err != nil { t.Fatal(err) } + return } // TestMinimalMachine uses a minimal project to setup the most basic machine // that will work for testing -func TestMinimalMachine(t testing.T) (machine *Machine, err error) { +func TestMinimalMachine(t testing.T) (machine *Machine) { tp := TestMinimalProject(t) - tt, err := TestTarget(t, tp, &vagrant_server.Target{}) - if err != nil { - t.Fatal(err) - return - } + tt := TestTarget(t, tp, &vagrant_server.Target{}) specialized, err := tt.Specialize((*core.Machine)(nil)) if err != nil { t.Fatal(err) - return nil, err } machine = specialized.(*Machine) - WithTestTargetMinimalConfig()(machine) return } -type TestMachineOption func(*Machine) error +type TestTargetOption func(interface{}) error -func WithTestTargetMinimalConfig() TestMachineOption { - return func(m *Machine) (err error) { - m.target.Configuration = &vagrant_plugin_sdk.Args_ConfigData{} - return +func WithTestTargetConfig(config *component.ConfigData) TestTargetOption { + return func(raw interface{}) (err error) { + switch v := raw.(type) { + case *Target: + return mergo.Merge(v.vagrantfile.root, config) + case *Machine: + return mergo.Merge(v.vagrantfile.root, config) + default: + panic(fmt.Sprintf("Invalid type for TestTargetOption (%T)", raw)) + } } } -func WithTestTargetConfig(config *vagrant_plugin_sdk.Args_ConfigData) TestMachineOption { - return func(m *Machine) (err error) { - m.target.Configuration = config - return - } -} - -func WithTestTargetProvider(provider string) TestMachineOption { - return func(m *Machine) (err error) { - m.target.Provider = provider +func WithTestTargetProvider(provider string) TestTargetOption { + return func(raw interface{}) (err error) { + switch v := raw.(type) { + case *Target: + v.target.Provider = provider + case *Machine: + v.target.Provider = provider + default: + panic(fmt.Sprintf("Invalid type for TestTargetOption (%T)", raw)) + } return } } diff --git a/internal/core/vagrantfile.go b/internal/core/vagrantfile.go index bcc93da3f..36ad55b5f 100644 --- a/internal/core/vagrantfile.go +++ b/internal/core/vagrantfile.go @@ -452,14 +452,15 @@ func (v *Vagrantfile) Target( return } - // Convert to actual Vagrantfile for target setup - vf := conf.(*Vagrantfile) - - target, err = v.origin.LoadTarget( - WithTargetName(name), - WithTargetVagrantfile(vf), - ) + opts := []TargetOption{WithTargetName(name)} + var vf *Vagrantfile + if conf != nil { + // Convert to actual Vagrantfile for target setup + vf = conf.(*Vagrantfile) + opts = append(opts, WithTargetVagrantfile(vf)) + } + target, err = v.origin.LoadTarget(opts...) if err != nil { return } @@ -467,21 +468,24 @@ func (v *Vagrantfile) Target( // Since the target config gives us a Vagrantfile which is // attached to the project, we need to clone it and attach // it to the target we loaded - rawTarget := target.(*Target) - tvf := v.clone(name, rawTarget) - if err = tvf.Init(); err != nil { - return nil, err - } - rawTarget.vagrantfile = tvf + if vf != nil { + rawTarget := target.(*Target) + tvf := v.clone(name, rawTarget) + if err = tvf.Init(); err != nil { + return nil, err + } + rawTarget.vagrantfile = tvf - if err = vf.Close(); err != nil { - return nil, err + if err = vf.Close(); err != nil { + return nil, err + } } return } // Generate a new Vagrantfile for the given target +// NOTE: This function may return a nil result without an error // TODO(spox): Provider validation is not currently implemented func (v *Vagrantfile) TargetConfig( name, // name of the target @@ -498,11 +502,11 @@ func (v *Vagrantfile) TargetConfig( subvm, err := v.GetValue("vm", "__defined_vms", name) if err != nil { - v.logger.Error("failed to get subvm", + v.logger.Warn("failed to get subvm", "name", name, "error", err, ) - return nil, err + return nil, nil } if subvm == nil { diff --git a/internal/server/singleprocess/state/target.go b/internal/server/singleprocess/state/target.go index 7648690e9..e15f3a943 100644 --- a/internal/server/singleprocess/state/target.go +++ b/internal/server/singleprocess/state/target.go @@ -1,12 +1,12 @@ package state import ( - "google.golang.org/protobuf/proto" "github.com/google/uuid" "github.com/hashicorp/go-memdb" bolt "go.etcd.io/bbolt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk" "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" @@ -165,6 +165,9 @@ func (s *State) targetList( result = append(result, &vagrant_plugin_sdk.Ref_Target{ ResourceId: next.(*targetIndexRecord).Id, Name: next.(*targetIndexRecord).Name, + Project: &vagrant_plugin_sdk.Ref_Project{ + ResourceId: next.(*targetIndexRecord).ProjectId, + }, }) }