Skip to content
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
49 changes: 36 additions & 13 deletions versionmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package versionmanager
import (
"context"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
Expand All @@ -46,6 +47,7 @@ import (

var (
errEmptyVersion = errors.New("empty version")
errVersionNotInstalled = errors.New("version is not installed")
errNoCompatible = errors.New("no compatible version found")
ErrNoCompatibleLocally = errors.New("no compatible version found locally")
ErrNoVersionFilesFound = errors.New("no version files found")
Expand Down Expand Up @@ -344,9 +346,8 @@ func (m VersionManager) Uninstall(requestedVersion string) error {

if versionfinder.IsValid(requestedVersion) {
cleanedVersion := versionfinder.Clean(requestedVersion)
m.uninstallSpecificVersion(installPath, cleanedVersion)

return nil
return m.uninstallSpecificVersion(installPath, cleanedVersion)
}

versions, err := m.innerListLocal(installPath, true)
Expand All @@ -362,7 +363,7 @@ func (m VersionManager) Uninstall(requestedVersion string) error {
if len(selected) == 0 {
m.Conf.Displayer.Display(loghelper.Concat("No matching ", m.FolderName, " versions"))

return nil
return errVersionNotInstalled
}

m.Conf.Displayer.Display(loghelper.Concat("Selected ", m.FolderName, " versions for uninstallation :"))
Expand All @@ -380,11 +381,14 @@ func (m VersionManager) Uninstall(requestedVersion string) error {
return nil
}

var errs []error
for _, version := range selected {
m.uninstallSpecificVersion(installPath, version)
if err := m.uninstallSpecificVersion(installPath, version); err != nil {
errs = append(errs, err)
}
}

return nil
return errors.Join(errs...)
}

func (m VersionManager) UninstallMultiple(versions []string) error {
Expand All @@ -398,11 +402,14 @@ func (m VersionManager) UninstallMultiple(versions []string) error {
defer disableExit()
defer deleteLock()

var errs []error
for _, version := range versions {
m.uninstallSpecificVersion(installPath, version)
if err := m.uninstallSpecificVersion(installPath, version); err != nil {
errs = append(errs, err)
}
}

return nil
return errors.Join(errs...)
}

func (m VersionManager) Use(ctx context.Context, requestedVersion string, workingDir bool) error {
Expand Down Expand Up @@ -550,20 +557,36 @@ func (m VersionManager) searchInstallRemote(ctx context.Context, predicateInfo t
return "", errNoCompatible
}

func (m VersionManager) uninstallSpecificVersion(installPath string, version string) {
func (m VersionManager) uninstallSpecificVersion(installPath string, version string) error {
if version == "" {
m.Conf.Displayer.Display(errEmptyVersion.Error())

return
return errEmptyVersion
}

targetPath := filepath.Join(installPath, version)
err := os.RemoveAll(targetPath)
if err == nil {
m.Conf.Displayer.Display(loghelper.Concat("Uninstallation of ", m.FolderName, " ", version, " successful (directory ", targetPath, " removed)"))
} else {
if _, err := os.Stat(targetPath); err != nil {
if errors.Is(err, os.ErrNotExist) {
msg := loghelper.Concat(m.FolderName, " ", version, " is not installed")
m.Conf.Displayer.Display(msg)

return fmt.Errorf("%w: %s", errVersionNotInstalled, msg)
}

m.Conf.Displayer.Display(loghelper.Concat("Uninstallation of ", m.FolderName, " ", version, " failed with error : ", err.Error()))

return err
}

if err := os.RemoveAll(targetPath); err != nil {
m.Conf.Displayer.Display(loghelper.Concat("Uninstallation of ", m.FolderName, " ", version, " failed with error : ", err.Error()))

return err
}

m.Conf.Displayer.Display(loghelper.Concat("Uninstallation of ", m.FolderName, " ", version, " successful (directory ", targetPath, " removed)"))

return nil
}

func removeFile(filePath string, conf *config.Config) error {
Expand Down
2 changes: 1 addition & 1 deletion versionmanager/tenvlib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func (t Tenv) Uninstall(_ context.Context, toolName string, requestedVersion str
return err
}

return manager.UninstallMultiple([]string{requestedVersion})
return manager.Uninstall(requestedVersion)
}

func (t Tenv) UninstallMultiple(_ context.Context, toolName string, versions []string) error {
Expand Down
150 changes: 150 additions & 0 deletions versionmanager/uninstall_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package versionmanager

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

"github.com/tofuutils/tenv/v4/config"
terraformretriever "github.com/tofuutils/tenv/v4/versionmanager/retriever/terraform"
)

func errsFromJoin(err error) []error {
type joinError interface{ Unwrap() []error }

var jerr joinError
if !errors.As(err, &jerr) {
return nil
}

return jerr.Unwrap()
}

func buildManager(t *testing.T, toolFolder string) VersionManager {
t.Helper()

conf, confErr := config.DefaultConfig()
if confErr != nil {
t.Fatal(confErr)
}

conf.RootPath = t.TempDir()
conf.LockPath = conf.RootPath
conf.InitDisplayer(false)

return Make(&conf, "", toolFolder, nil, terraformretriever.Make(&conf), nil)
}

func TestUninstall(t *testing.T) {
t.Parallel()

t.Run("Uninstall_not_installed_exact_version", func(t *testing.T) {
t.Parallel()

mgr := buildManager(t, "Terraform")
uninstallErr := mgr.Uninstall("1.0.0")
if uninstallErr == nil {
t.Fatal("expected error when uninstalling a version that is not installed, got nil")
}

if !errors.Is(uninstallErr, errVersionNotInstalled) {
t.Errorf("expected errVersionNotInstalled, got: %v", uninstallErr)
}
})

t.Run("UninstallMultiple_returns_error_for_not_installed", func(t *testing.T) {
t.Parallel()

mgr := buildManager(t, "Terraform")
uninstallErr := mgr.UninstallMultiple([]string{"1.0.0"})
if uninstallErr == nil {
t.Fatal("expected error when uninstalling a version that is not installed, got nil")
}

if !errors.Is(uninstallErr, errVersionNotInstalled) {
t.Errorf("expected errVersionNotInstalled, got: %v", uninstallErr)
}
})

t.Run("UninstallMultiple_returns_joined_errors_for_multiple_not_installed", func(t *testing.T) {
t.Parallel()

mgr := buildManager(t, "OpenTofu")
uninstallErr := mgr.UninstallMultiple([]string{"1.0.0", "1.1.0"})
if uninstallErr == nil {
t.Fatal("expected error when uninstalling versions that are not installed, got nil")
}

unwrapped := errsFromJoin(uninstallErr)
if len(unwrapped) == 0 {
t.Fatal("expected joined error with at least one wrapped error")
}

found := false
for _, u := range unwrapped {
if errors.Is(u, errVersionNotInstalled) {
found = true

break
}
}

if !found {
t.Errorf("expected errVersionNotInstalled wrapped in joined error, got: %v", uninstallErr)
}
})

t.Run("Uninstall_installed_version_succeeds", func(t *testing.T) {
t.Parallel()

mgr := buildManager(t, "Terraform")
installPath, pathErr := mgr.InstallPath()
if pathErr != nil {
t.Fatal(pathErr)
}

versionDir := filepath.Join(installPath, "1.2.3")
if mkdirErr := os.MkdirAll(versionDir, 0o755); mkdirErr != nil {
t.Fatal(mkdirErr)
}

uninstallErr := mgr.Uninstall("1.2.3")
if uninstallErr != nil {
t.Fatalf("unexpected error uninstalling installed version: %v", uninstallErr)
}

if _, statErr := os.Stat(versionDir); !os.IsNotExist(statErr) {
t.Error("expected version directory to be removed after uninstall")
}
})

t.Run("Uninstall_same_version_twice_returns_error", func(t *testing.T) {
t.Parallel()

mgr := buildManager(t, "Terragrunt")
installPath, pathErr := mgr.InstallPath()
if pathErr != nil {
t.Fatal(pathErr)
}

versionDir := filepath.Join(installPath, "0.49.0")
if mkdirErr := os.MkdirAll(versionDir, 0o755); mkdirErr != nil {
t.Fatal(mkdirErr)
}

firstErr := mgr.Uninstall("0.49.0")
if firstErr != nil {
t.Fatalf("first uninstall should succeed, got: %v", firstErr)
}

secondErr := mgr.Uninstall("0.49.0")
if secondErr == nil {
t.Fatal("expected error on second uninstall of already-removed version, got nil")
}

if !errors.Is(secondErr, errVersionNotInstalled) {
t.Errorf("expected errVersionNotInstalled on second uninstall, got: %v", secondErr)
}
})
}