Skip to content

Commit

Permalink
improve uninstall #181
Browse files Browse the repository at this point in the history
Signed-off-by: Denis Vaumoron <dvaumoron@gmail.com>
  • Loading branch information
dvaumoron committed Jul 6, 2024
1 parent 4434c02 commit c63da7e
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 28 deletions.
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.22.0 // indirect
)
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,21 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -110,8 +110,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
Expand Down
58 changes: 58 additions & 0 deletions versionmanager/lastuse/last.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
*
* Copyright 2024 tofuutils authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package lastuse

import (
"os"
"path/filepath"
"time"

"github.com/hashicorp/go-hclog"

"github.com/tofuutils/tenv/v2/pkg/loghelper"
)

const fileName = "last-use.txt"

func Read(dirPath string, displayer loghelper.Displayer) time.Time {
data, err := os.ReadFile(filepath.Join(dirPath, fileName))
if err != nil {
displayer.Log(hclog.Warn, "Unable to read date in file", loghelper.Error, err)

return time.Time{}
}

parsed, err := time.Parse(time.DateOnly, string(data))
if err != nil {
displayer.Log(hclog.Warn, "Unable to parse date in file", loghelper.Error, err)

return time.Time{}
}

return parsed
}

func WriteNow(dirPath string, displayer loghelper.Displayer) {
lastUsePath := filepath.Join(dirPath, fileName)
nowData := time.Now().AppendFormat(nil, time.DateOnly)

if err := os.WriteFile(lastUsePath, nowData, 0o644); err != nil {
displayer.Log(hclog.Warn, "Unable to write date in file", loghelper.Error, err)
}
}
66 changes: 54 additions & 12 deletions versionmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ func (m VersionManager) Install(requestedVersion string) error {
// try to ensure the directory exists with a MkdirAll call.
// (made lazy method : not always useful and allows flag override for root path).
func (m VersionManager) InstallPath() (string, error) {
dir := filepath.Join(m.conf.RootPath, m.FolderName)
dirPath := filepath.Join(m.conf.RootPath, m.FolderName)

return dir, os.MkdirAll(dir, 0o755)
return dirPath, os.MkdirAll(dirPath, 0o755)
}

func (m VersionManager) ListLocal(reverseOrder bool) ([]string, error) {
Expand Down Expand Up @@ -282,11 +282,6 @@ func (m VersionManager) SetConstraint(constraint string) error {
}

func (m VersionManager) Uninstall(requestedVersion string) error {
parsedVersion, err := version.NewVersion(requestedVersion) // check the use of a parsable version
if err != nil {
return err
}

installPath, err := m.InstallPath()
if err != nil {
return err
Expand All @@ -297,13 +292,46 @@ func (m VersionManager) Uninstall(requestedVersion string) error {
defer disableExit()
defer deleteLock()

cleanedVersion := parsedVersion.String()
targetPath := filepath.Join(installPath, cleanedVersion)
if err = os.RemoveAll(targetPath); err == nil {
m.conf.Displayer.Display(loghelper.Concat("Uninstallation of ", m.FolderName, " ", cleanedVersion, " successful (directory ", targetPath, " removed)"))
parsedVersion, err := version.NewVersion(requestedVersion) // check the use of a parsable version
if err == nil {
return m.uninstallSpecificVersion(installPath, parsedVersion.String())
}

return err
versions, err := m.ListLocal(true)
if err != nil {
return err
}

selected, err := semantic.SelectVersionsToUninstall(requestedVersion, installPath, versions, m.conf.Displayer)
if err != nil {
return err
}

if len(selected) == 0 {
m.conf.Displayer.Display("No matching versions")

return nil
}

m.conf.Displayer.Display(loghelper.Concat("Selected ", m.FolderName, " versions for uninstallation :"))
m.conf.Displayer.Display(strings.Join(selected, ", "))
m.conf.Displayer.Display("Uninstall ? [y/N]")

buffer := make([]byte, 1)
os.Stdin.Read(buffer)
readed := buffer[0]

if doUninstall := readed == 'y' || readed == 'Y'; !doUninstall {
return nil
}

for _, version := range selected {
if err = m.uninstallSpecificVersion(installPath, version); err != nil {
m.conf.Displayer.Display(loghelper.Concat("Uninstallation of ", m.FolderName, " ", version, " failed with error : ", err.Error()))
}
}

return nil
}

func (m VersionManager) Use(requestedVersion string, workingDir bool) error {
Expand Down Expand Up @@ -428,6 +456,20 @@ func (m VersionManager) searchInstallRemote(predicateInfo types.PredicateInfo, n
return "", errNoCompatible
}

func (m VersionManager) uninstallSpecificVersion(installPath string, version string) error {
if version == "" {
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)"))
}

return err
}

func removeFile(filePath string, conf *config.Config) error {
err := os.RemoveAll(filePath)
if err == nil {
Expand Down
2 changes: 1 addition & 1 deletion versionmanager/proxy/agnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ func ExecAgnostic(conf *config.Config, builders map[string]builder.BuilderFunc,
os.Exit(1)
}

RunCmd(installPath, detectedVersion, execName, cmdArgs, conf.GithubActions)
RunCmd(installPath, detectedVersion, execName, cmdArgs, conf.GithubActions, conf.Displayer)
}
12 changes: 9 additions & 3 deletions versionmanager/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import (

"github.com/tofuutils/tenv/v2/config"
cmdproxy "github.com/tofuutils/tenv/v2/pkg/cmdproxy"
"github.com/tofuutils/tenv/v2/pkg/loghelper"
"github.com/tofuutils/tenv/v2/versionmanager/builder"
"github.com/tofuutils/tenv/v2/versionmanager/lastuse"
)

var errDelimiter = errors.New("key and value should not contains delimiter")
Expand All @@ -48,9 +50,13 @@ func Exec(conf *config.Config, builderFunc builder.BuilderFunc, hclParser *hclpa
os.Exit(1)
}

RunCmd(installPath, detectedVersion, execName, cmdArgs, conf.GithubActions)
RunCmd(installPath, detectedVersion, execName, cmdArgs, conf.GithubActions, conf.Displayer)
}

func RunCmd(installPath string, detectedVersion string, execName string, cmdArgs []string, gha bool) {
cmdproxy.Run(filepath.Join(installPath, detectedVersion, execName), cmdArgs, gha)
func RunCmd(installPath string, detectedVersion string, execName string, cmdArgs []string, gha bool, displayer loghelper.Displayer) {
versionPath := filepath.Join(installPath, detectedVersion)

lastuse.WriteNow(versionPath, displayer)

cmdproxy.Run(filepath.Join(versionPath, execName), cmdArgs, gha)
}
120 changes: 120 additions & 0 deletions versionmanager/semantic/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
*
* Copyright 2024 tofuutils authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package semantic

import (
"errors"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/hashicorp/go-version"
"github.com/tofuutils/tenv/v2/pkg/loghelper"
"github.com/tofuutils/tenv/v2/versionmanager/lastuse"
)

const (
allKey = "all"
butLast = "but-last"

notUsedForPrefix = "not-used-for:"
notUsedSincePrefix = "not-used-since:"

notUsedForPrefixLen = len(notUsedForPrefix)
notUsedSincePrefixLen = len(notUsedSincePrefix)
)

var errDurationParsing = errors.New("unrecognized duration format")

// versions must be sorted in descending order.
func SelectVersionsToUninstall(behaviourOrConstraint string, installPath string, versions []string, displayer loghelper.Displayer) ([]string, error) {
switch {
case behaviourOrConstraint == allKey:
return versions, nil
case behaviourOrConstraint == butLast:
if len(versions) == 0 {
return nil, nil
}

return versions[1:], nil // allowed by descending order
case strings.HasPrefix(behaviourOrConstraint, notUsedForPrefix):
forStr := behaviourOrConstraint[notUsedForPrefixLen:]

var err error
daysInt, monthsInt := 0, 0
lastIndex := len(forStr) - 1
switch forStr[lastIndex] {
case 'd', 'D':
daysInt, err = strconv.Atoi(forStr[:lastIndex])
if err != nil {
return nil, err
}
case 'm', 'M':
monthsInt, err = strconv.Atoi(forStr[:lastIndex])
if err != nil {
return nil, err
}
default:
return nil, errDurationParsing
}

beforeDate := time.Now().AddDate(0, -monthsInt, -daysInt)
pred := predicateBeforeDate(installPath, beforeDate, displayer)

return filterStrings(versions, pred), nil
case strings.HasPrefix(behaviourOrConstraint, notUsedSincePrefix):
dateStr := behaviourOrConstraint[notUsedSincePrefixLen:]

beforeDate, err := time.Parse(time.DateOnly, dateStr)
if err != nil {
return nil, err
}
pred := predicateBeforeDate(installPath, beforeDate, displayer)

return filterStrings(versions, pred), nil
default:
constraint, err := version.NewConstraint(behaviourOrConstraint)
if err != nil {
return nil, err
}
pred := predicateFromConstraint(constraint)

return filterStrings(versions, pred), nil
}
}

func filterStrings(stringSlice []string, pred func(string) bool) []string {
selected := make([]string, 0, len(stringSlice))
for _, str := range stringSlice {
if pred(str) {
selected = append(selected, str)
}
}

return selected
}

func predicateBeforeDate(installPath string, beforeDate time.Time, displayer loghelper.Displayer) func(string) bool {
return func(versionStr string) bool {
useDate := lastuse.Read(filepath.Join(installPath, versionStr), displayer)

return useDate.Before(beforeDate)
}
}

0 comments on commit c63da7e

Please sign in to comment.