// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package core import ( "testing" "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/plugin" "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestMachineSetValidId(t *testing.T) { tm := TestMinimalMachine(t) // Set valid id tm.SetID("something") newId, err := tm.ID() if err != nil { t.Errorf("Failed to get id") } require.Equal(t, newId, "something") // Ensure new id is save to db dbTarget, err := tm.Client().GetTarget(tm.ctx, &vagrant_server.GetTargetRequest{ Target: tm.Ref().(*vagrant_plugin_sdk.Ref_Target), }, ) if err != nil { t.Errorf("Failed to get target") } var dbMachine vagrant_server.Target_Machine err = dbTarget.Target.Record.UnmarshalTo(&dbMachine) require.NoError(t, err) require.Equal(t, dbMachine.Id, "something") } func TestMachineSetEmptyId(t *testing.T) { tm := TestMinimalMachine(t) oldId := tm.target.ResourceId // Set empty id tm.SetID("") newId, err := tm.ID() if err != nil { t.Errorf("Failed to get id") } require.Equal(t, newId, "") // Machine won't be deleted from db until project is closed, so close project first err = tm.project.Close() require.NoError(t, err) // Ensure machine is deleted from the db by checking for the old id dbTarget, err := tm.Client().GetTarget(tm.ctx, &vagrant_server.GetTargetRequest{ Target: &vagrant_plugin_sdk.Ref_Target{ ResourceId: oldId, Project: tm.target.Project, Name: tm.target.Name, }, }, ) require.Nil(t, dbTarget) require.Error(t, err) // Verify the DataDir still exists (see below test for more detail on why) dir, err := tm.DataDir() require.NoError(t, err) require.DirExists(t, dir.DataDir().String()) // Also check new id dbTarget, err = tm.Client().GetTarget(tm.ctx, &vagrant_server.GetTargetRequest{ Target: &vagrant_plugin_sdk.Ref_Target{ ResourceId: "", Project: tm.target.Project, Name: tm.target.Name, }, }, ) require.Nil(t, dbTarget) require.Error(t, err) } func TestMachineSetIdBlankThenSomethingPreservesDataDir(t *testing.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 require.NoError(t, tm.SetID("")) require.NoError(t, tm.SetID("preparing")) // The DataDir should still exist; the Docker provider relies on this // behavior in order for its provisioning sentinel file handling to work // properly. dir, err := tm.DataDir() require.NoError(t, err) require.DirExists(t, dir.DataDir().String()) } func TestMachineGetNonExistentBox(t *testing.T) { tp := TestMinimalProject(t) tm := TestMachine(t, tp, WithTestTargetConfig(testBoxConfig("somebox")), WithTestTargetProvider("testprovider"), ) box, err := tm.Box() require.NoError(t, err) name, err := box.Name() require.NoError(t, err) require.Equal(t, name, "somebox") provider, err := box.Provider() require.NoError(t, err) require.Equal(t, provider, "testprovider") metaurl, err := box.MetadataURL() require.NoError(t, err) require.Empty(t, metaurl) } func TestMachineGetExistentBox(t *testing.T) { tp := TestMinimalProject(t) tm := TestMachine(t, tp, WithTestTargetConfig(testBoxConfig("test/box")), WithTestTargetProvider("virtualbox"), ) testBox := newFullBox(t, testboxBoxData(), tp.basis) testBox.Save() box, err := tm.Box() require.NoError(t, err) name, err := box.Name() require.NoError(t, err) require.Equal(t, name, "test/box") provider, err := box.Provider() require.NoError(t, err) require.NotEmpty(t, provider) metaurl, err := box.MetadataURL() require.NoError(t, err) require.NotEmpty(t, metaurl) } func TestMachineConfigedGuest(t *testing.T) { commMock := BuildTestCommunicatorPlugin("ssh") commMock.On("Ready", mock.AnythingOfType("*core.Machine")).Return(true, nil) commPlugin := plugin.TestPlugin(t, commMock, plugin.WithPluginName("ssh"), plugin.WithPluginTypes(component.CommunicatorType), ) type test struct { config *component.ConfigData errors bool } tests := []test{ {config: testGuestConfig("myguest"), errors: false}, {config: testGuestConfig("idontexist"), errors: true}, } guestMock := BuildTestGuestPlugin("myguest", "") guestMock.On("Detect", mock.AnythingOfType("*core.Machine")).Return(true, nil) guestMock.On("Parent").Return("", nil) pluginManager := plugin.TestManager(t, plugin.TestPlugin(t, guestMock, plugin.WithPluginName("myguest"), plugin.WithPluginTypes(component.GuestType), ), commPlugin, ) for _, tc := range tests { tp := TestProject(t, WithPluginManager(pluginManager)) tm := TestMachine(t, tp, WithTestTargetConfig(tc.config), ) guest, err := tm.Guest() if tc.errors { require.Error(t, err) require.Nil(t, guest) require.Nil(t, tm.cache.Get("guest")) } else { require.NoError(t, err) require.NotNil(t, guest) require.NotNil(t, tm.cache.Get("guest")) } } } func TestMachineNoConfigGuest(t *testing.T) { commMock := BuildTestCommunicatorPlugin("ssh") commMock.On("Ready", mock.AnythingOfType("*core.Machine")).Return(true, nil) commPlugin := plugin.TestPlugin(t, commMock, plugin.WithPluginName("ssh"), plugin.WithPluginTypes(component.CommunicatorType), ) guestMock := BuildTestGuestPlugin("myguest", "") guestMock.On("Detect", mock.AnythingOfType("*core.Machine")).Return(true, nil) guestMock.On("Parent").Return("", nil) detectingPlugin := plugin.TestPlugin(t, guestMock, plugin.WithPluginName("myguest"), plugin.WithPluginTypes(component.GuestType), ) notGuestMock := BuildTestGuestPlugin("mynondetectingguest", "") notGuestMock.On("Detect", mock.AnythingOfType("*core.Machine")).Return(false, nil) nonDetectingPlugin := plugin.TestPlugin(t, notGuestMock, plugin.WithPluginName("mynondetectingguest"), plugin.WithPluginTypes(component.GuestType), ) guestChildMock := BuildTestGuestPlugin("myguest-child", "myguest") guestChildMock.On("Detect", mock.AnythingOfType("*core.Machine")).Return(true, nil) guestChildMock.SetParentComponent(guestMock) detectingChildPlugin := plugin.TestPlugin(t, guestChildMock, plugin.WithPluginName("myguest-child"), plugin.WithPluginTypes(component.GuestType), ) type test struct { plugins []*plugin.Plugin errors bool expectedPluginName string } tests := []test{ {plugins: []*plugin.Plugin{commPlugin, detectingPlugin}, errors: false, expectedPluginName: "myguest"}, {plugins: []*plugin.Plugin{commPlugin, detectingChildPlugin}, errors: true, expectedPluginName: "myguest-child"}, {plugins: []*plugin.Plugin{commPlugin, detectingChildPlugin, detectingPlugin}, errors: false, expectedPluginName: "myguest-child"}, {plugins: []*plugin.Plugin{commPlugin, detectingPlugin, nonDetectingPlugin}, errors: false, expectedPluginName: "myguest"}, {plugins: []*plugin.Plugin{commPlugin, nonDetectingPlugin}, errors: true}, {plugins: []*plugin.Plugin{commPlugin}, errors: true}, } for _, tc := range tests { pluginManager := plugin.TestManager(t, tc.plugins...) tp := TestProject(t, WithPluginManager(pluginManager)) tm := TestMachine(t, tp) guest, err := tm.Guest() if tc.errors { require.Error(t, err) require.Nil(t, guest) require.Nil(t, tm.cache.Get("guest")) } else { require.NoError(t, err) require.NotNil(t, guest) require.NotNil(t, tm.cache.Get("guest")) n, _ := guest.PluginName() if n != tc.expectedPluginName { t.Error("Found unexpected plugin, ", n) } } } } func TestMachineSetState(t *testing.T) { tm := TestMinimalMachine(t) type test struct { id string state vagrant_server.Operation_PhysicalState } tests := []test{ {id: "running", state: vagrant_server.Operation_CREATED}, {id: "not_created", state: vagrant_server.Operation_NOT_CREATED}, {id: "whakhgldksj", state: vagrant_server.Operation_UNKNOWN}, } for _, tc := range tests { // Set MachineState desiredState := &core.MachineState{ID: tc.id} tm.SetMachineState(desiredState) require.Equal(t, tc.id, tm.machine.State.Id) require.Equal(t, tc.state, tm.target.State) // Ensure new id is save to db dbTarget, err := tm.Client().GetTarget(tm.ctx, &vagrant_server.GetTargetRequest{ Target: tm.Ref().(*vagrant_plugin_sdk.Ref_Target), }, ) require.NoError(t, err) require.Equal(t, tc.state, dbTarget.Target.State) } } func TestMachineSyncedFolders(t *testing.T) { mySyncedFolder := syncedFolderPlugin(t, "mysyncedfolder") myOtherSyncedFolder := syncedFolderPlugin(t, "myothersyncedfolder") type test struct { plugins []*plugin.Plugin config *component.ConfigData errors bool expectedFolders int } tests := []test{ // One synced folder and plugin available { plugins: []*plugin.Plugin{mySyncedFolder}, errors: false, config: testSyncedFolderConfig( []*testSyncedFolder{ { source: ".", destination: "/vagrant", kind: "mysyncedfolder", }, }, ), expectedFolders: 1, }, // Many synced folders and available plugins { plugins: []*plugin.Plugin{mySyncedFolder, myOtherSyncedFolder}, errors: false, config: testSyncedFolderConfig( []*testSyncedFolder{ { source: ".", destination: "/vagrant", kind: "mysyncedfolder", }, { source: "./two", destination: "/vagrant-two", kind: "mysyncedfolder", }, { source: "./three", destination: "/vagrant-three", kind: "myothersyncedfolder", }, }, ), expectedFolders: 3, }, // Synced folder with unavailable plugin { plugins: []*plugin.Plugin{mySyncedFolder, myOtherSyncedFolder}, errors: true, config: testSyncedFolderConfig( []*testSyncedFolder{ { source: ".", destination: "/vagrant", kind: "idontexist", }, { source: "./two", destination: "/vagrant-two", kind: "mysyncedfolder", }, { source: "./three", destination: "/vagrant-three", kind: "myothersyncedfolder", }, }, ), }, } for _, tc := range tests { pluginManager := plugin.TestManager(t, tc.plugins...) tp := TestProject(t, WithPluginManager(pluginManager)) tm := TestMachine(t, tp, WithTestTargetConfig(tc.config), ) folders, err := tm.SyncedFolders() if tc.errors { require.Error(t, err) } else { require.NoError(t, err) require.NotNil(t, folders) require.Len(t, folders, tc.expectedFolders) } } } func stringPtr(s string) *string { return &s }