Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistent vm naming #524

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
trellis-cli
dist
tmp
.DS_Store
3 changes: 2 additions & 1 deletion cli_config/cli_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ type VmImage struct {
type VmConfig struct {
Manager string `yaml:"manager"`
HostsResolver string `yaml:"hosts_resolver"`
Images []VmImage `yaml:"images"`
Ubuntu string `yaml:"ubuntu"`
InstanceName string `yaml:"instance_name"` // Custom name for the Lima VM instance
Images []VmImage `yaml:"images"` // VM image configuration (as a slice)
}

type Config struct {
Expand Down
49 changes: 49 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Package cmd contains all command-line interface commands for Trellis CLI.
package cmd

import (
"fmt"
"runtime"

"github.com/mitchellh/cli"
"github.com/roots/trellis-cli/pkg/lima"
"github.com/roots/trellis-cli/pkg/vm"
"github.com/roots/trellis-cli/trellis"
)

// This file provides centralized declarations of functions as variables
// to enable mocking in tests and prevent duplicate implementations.
// Previously, these functions were defined in multiple places, causing
// "redeclared in this block" errors during compilation.

// Declare these as variables so they can be overridden in tests
var (
// newVmManager creates a VM manager instance based on configuration.
// Using a function variable allows for mocking in tests and centralizes VM manager creation logic.
// This approach eliminates duplicate implementations that previously existed across files.
newVmManager = func(t *trellis.Trellis, ui cli.Ui) (vm.Manager, error) {
// Select appropriate VM manager based on configuration
switch t.CliConfig.Vm.Manager {
case "auto":
switch runtime.GOOS {
case "darwin":
return lima.NewManager(t, ui)
default:
return nil, fmt.Errorf("no VM managers are supported on %s yet", runtime.GOOS)
}
case "lima":
return lima.NewManager(t, ui)
case "mock":
return vm.NewMockManager(t, ui)
}

return nil, fmt.Errorf("vm manager not found")
}

// NewProvisionCommand creates a new ProvisionCommand instance.
// This was moved here from provision.go to follow the same pattern
// of using function variables for testability.
NewProvisionCommand = func(ui cli.Ui, trellis *trellis.Trellis) *ProvisionCommand {
return &ProvisionCommand{UI: ui, Trellis: trellis}
}
)
6 changes: 0 additions & 6 deletions cmd/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ import (
"github.com/roots/trellis-cli/trellis"
)

func NewProvisionCommand(ui cli.Ui, trellis *trellis.Trellis) *ProvisionCommand {
c := &ProvisionCommand{UI: ui, Trellis: trellis}
c.init()
return c
}

type ProvisionCommand struct {
UI cli.Ui
flags *flag.FlagSet
Expand Down
23 changes: 1 addition & 22 deletions cmd/vm.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
package cmd

import (
"fmt"
"os"
"path/filepath"
"runtime"

"github.com/mitchellh/cli"
"github.com/roots/trellis-cli/pkg/lima"
"github.com/roots/trellis-cli/pkg/vm"
"github.com/roots/trellis-cli/trellis"
)

const VagrantInventoryFilePath string = ".vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory"

func newVmManager(trellis *trellis.Trellis, ui cli.Ui) (manager vm.Manager, err error) {
switch trellis.CliConfig.Vm.Manager {
case "auto":
switch runtime.GOOS {
case "darwin":
return lima.NewManager(trellis, ui)
default:
return nil, fmt.Errorf("No VM managers are supported on %s yet.", runtime.GOOS)
}
case "lima":
return lima.NewManager(trellis, ui)
case "mock":
return vm.NewMockManager(trellis, ui)
}

return nil, fmt.Errorf("VM manager not found")
}

func findDevInventory(trellis *trellis.Trellis, ui cli.Ui) string {
// Use the newVmManager variable from main.go
manager, managerErr := newVmManager(trellis, ui)

if managerErr == nil {
Expand Down
8 changes: 7 additions & 1 deletion cmd/vm_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"flag"
"strings"
"os"
"path/filepath"

"github.com/manifoldco/promptui"
"github.com/mitchellh/cli"
Expand Down Expand Up @@ -67,7 +69,11 @@ func (c *VmDeleteCommand) Run(args []string) int {
if err := manager.DeleteInstance(siteName); err != nil {
c.UI.Error("Error: " + err.Error())
return 1
}
}

// Remove instance file if it exists
instancePath := filepath.Join(c.Trellis.ConfigPath(), "lima", "instance")
os.Remove(instancePath) // Ignore errors as file may not exist
}

return 0
Expand Down
60 changes: 60 additions & 0 deletions cmd/vm_delete_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cmd

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/mitchellh/cli"
"github.com/roots/trellis-cli/pkg/vm"
"github.com/roots/trellis-cli/trellis"
)

Expand Down Expand Up @@ -54,3 +57,60 @@ func TestVmDeleteRunValidations(t *testing.T) {
})
}
}

func TestVmDeleteRemovesInstanceFile(t *testing.T) {
cleanup := trellis.LoadFixtureProject(t)
defer cleanup()

// Setup test environment
ui := cli.NewMockUi()
mockTrellis := trellis.NewTrellis()
mockTrellis.LoadProject()

// Create the lima directory and instance file
limaDir := filepath.Join(mockTrellis.ConfigPath(), "lima")
err := os.MkdirAll(limaDir, 0755)
if err != nil {
t.Fatalf("failed to create lima directory: %v", err)
}

instancePath := filepath.Join(limaDir, "instance")
err = os.WriteFile(instancePath, []byte("example.com"), 0644)
if err != nil {
t.Fatalf("failed to write instance file: %v", err)
}

// Verify file exists before test
if _, err := os.Stat(instancePath); os.IsNotExist(err) {
t.Fatalf("failed to create test instance file")
}

// Create command
vmDeleteCommand := NewVmDeleteCommand(ui, mockTrellis)
vmDeleteCommand.force = true // Skip confirmation prompt

// Replace VM manager with mock
mockManager := &MockVmManager{}

// Save original function and restore after test
originalManagerFunc := newVmManager
defer func() { newVmManager = originalManagerFunc }()

newVmManager = func(t *trellis.Trellis, ui cli.Ui) (vm.Manager, error) {
return mockManager, nil
}

// Run command
code := vmDeleteCommand.Run([]string{})

// Check command succeeded
if code != 0 {
t.Errorf("expected exit code 0, got %d", code)
}

// Check instance file was removed
_, err = os.Stat(instancePath)
if !os.IsNotExist(err) {
t.Error("expected instance file to be deleted")
}
}
15 changes: 13 additions & 2 deletions cmd/vm_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ func (c *VmStartCommand) Run(args []string) int {
return 1
}

siteName, _, err := c.Trellis.MainSiteFromEnvironment("development")
// CHANGE: Use GetVMInstanceName instead of MainSiteFromEnvironment
siteName, err := c.Trellis.GetVMInstanceName()
if err != nil {
c.UI.Error("Error: could not automatically set VM name: " + err.Error())
c.UI.Error("Error: could not get VM name: " + err.Error())
return 1
}

if siteName == "" {
c.UI.Error("Error: could not automatically set VM name. No site found in development environment.")
return 1
}

Expand Down Expand Up @@ -80,6 +86,11 @@ func (c *VmStartCommand) Run(args []string) int {
return 1
}

// Save the instance name for future reference
if err = c.Trellis.SaveVMInstanceName(siteName); err != nil {
c.UI.Warn("Warning: Failed to save VM instance name. VM was created successfully, but future commands may not recognize it.")
}

if err = manager.StartInstance(siteName); err != nil {
c.UI.Error("Error starting VM.")
c.UI.Error(err.Error())
Expand Down
Loading