github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/clean/run_lin_mac.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package clean
     5  
     6  import (
     7  	"errors"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	svcApp "github.com/ActiveState/cli/cmd/state-svc/app"
    12  	"github.com/ActiveState/cli/internal/config"
    13  	"github.com/ActiveState/cli/internal/constants"
    14  	"github.com/ActiveState/cli/internal/errs"
    15  	"github.com/ActiveState/cli/internal/fileutils"
    16  	"github.com/ActiveState/cli/internal/installation"
    17  	"github.com/ActiveState/cli/internal/installation/storage"
    18  	"github.com/ActiveState/cli/internal/legacytray"
    19  	"github.com/ActiveState/cli/internal/locale"
    20  	"github.com/ActiveState/cli/internal/logging"
    21  	"github.com/ActiveState/cli/internal/osutils"
    22  	"github.com/ActiveState/cli/internal/output"
    23  	"github.com/ActiveState/cli/internal/strutils"
    24  )
    25  
    26  func (u *Uninstall) runUninstall(params *UninstallParams) error {
    27  	// we aggregate installation errors, such that we can display all installation problems in the end
    28  	// TODO: This behavior should be replaced with a proper rollback mechanism https://www.pivotaltracker.com/story/show/178134918
    29  	var aggErr, err error
    30  	if params.All {
    31  		err := removeCache(storage.CachePath())
    32  		if err != nil {
    33  			logging.Debug("Could not remove cache at %s: %s", storage.CachePath(), errs.JoinMessage(err))
    34  			aggErr = locale.WrapError(aggErr, "uninstall_remove_cache_err", "Failed to remove cache directory {{.V0}}.", storage.CachePath())
    35  		}
    36  	}
    37  
    38  	err = undoPrepare()
    39  	if err != nil {
    40  		logging.Debug("Could not undo prepare: %s", errs.JoinMessage(err))
    41  		aggErr = locale.WrapError(aggErr, "uninstall_prepare_err", "Failed to undo some installation steps.")
    42  	}
    43  
    44  	if err := removeApp(); err != nil {
    45  		logging.Debug("Could not remove app: %s", errs.JoinMessage(err))
    46  		aggErr = locale.WrapError(aggErr, "uninstall_remove_app_err", "Failed to remove service application")
    47  	}
    48  
    49  	err = removeInstall(u.cfg)
    50  	if err != nil {
    51  		if dirNotEmpty := (&dirNotEmptyError{}); errors.As(err, &dirNotEmpty) {
    52  			logging.Debug("Could not remove install as dir is not empty: %s", errs.JoinMessage(err))
    53  			aggErr = locale.WrapError(aggErr, "uninstall_warn_not_empty_already_localized", dirNotEmpty.Error())
    54  		} else {
    55  			logging.Debug("Could not remove install: %s", errs.JoinMessage(err))
    56  			aggErr = locale.WrapError(aggErr, "uninstall_remove_executables_err", "Failed to remove all State Tool files in installation directory")
    57  		}
    58  	}
    59  
    60  	err = removeEnvPaths(u.cfg)
    61  	if err != nil {
    62  		logging.Debug("Could not remove env paths: %s", errs.JoinMessage(err))
    63  		aggErr = locale.WrapError(aggErr, "uninstall_remove_paths_err", "Failed to remove PATH entries from environment")
    64  	}
    65  
    66  	if params.All {
    67  		path := u.cfg.ConfigPath()
    68  		if err := u.cfg.Close(); err != nil {
    69  			logging.Debug("Could not close config: %s", errs.JoinMessage(err))
    70  			aggErr = locale.WrapError(aggErr, "uninstall_close_config", "Could not stop config database connection.")
    71  		}
    72  
    73  		err = removeConfig(path, u.out)
    74  		if err != nil {
    75  			logging.Debug("Could not remove config: %s", errs.JoinMessage(err))
    76  			aggErr = locale.WrapError(aggErr, "uninstall_remove_config_err", "Failed to remove configuration directory {{.V0}}", u.cfg.ConfigPath())
    77  		}
    78  	}
    79  
    80  	if aggErr != nil {
    81  		return aggErr
    82  	}
    83  
    84  	u.out.Notice(locale.T("clean_success_message"))
    85  	return nil
    86  }
    87  
    88  func removeConfig(configPath string, out output.Outputer) error {
    89  	file, err := os.Open(logging.FilePath())
    90  	if err != nil {
    91  		return locale.WrapError(err, "err_clean_open_log", "Could not open logging file at: {{.V0}}", logging.FilePath())
    92  	}
    93  	err = file.Sync()
    94  	if err != nil {
    95  		return locale.WrapError(err, "err_clean_sync", "Could not sync logging file")
    96  	}
    97  	err = file.Close()
    98  	if err != nil {
    99  		return locale.WrapError(err, "err_clean_close", "Could not close logging file")
   100  	}
   101  
   102  	err = os.RemoveAll(configPath)
   103  	if err != nil {
   104  		return locale.WrapError(err, "err_clean_config_remove", "Could not remove config directory")
   105  	}
   106  
   107  	out.Notice(locale.Tl("clean_config_succes", "Successfully removed State Tool config directory"))
   108  	return nil
   109  }
   110  
   111  func removeInstall(cfg *config.Instance) error {
   112  	var aggErr error
   113  
   114  	// Get the install path before we remove the actual executable
   115  	// to avoid any errors from this function
   116  	installPath, err := installation.InstallPathFromExecPath()
   117  	if err != nil {
   118  		aggErr = errs.Wrap(aggErr, "Could not get installation path")
   119  	}
   120  
   121  	if transitionalStatePath := cfg.GetString(installation.CfgTransitionalStateToolPath); transitionalStatePath != "" {
   122  		if err := os.Remove(transitionalStatePath); err != nil && !errors.Is(err, os.ErrNotExist) {
   123  			aggErr = errs.Wrap(aggErr, "Could not remove %s: %v", transitionalStatePath, err)
   124  		}
   125  	}
   126  
   127  	if fileutils.DirExists(installPath) {
   128  		err = cleanInstallDir(installPath, cfg)
   129  		if err != nil {
   130  			aggErr = errs.Wrap(err, "Could not clean install path")
   131  		}
   132  		if err := removeEmptyDir(installPath); err != nil {
   133  			aggErr = errs.Wrap(err, "Could not remove install path: %s", installPath)
   134  		}
   135  	}
   136  
   137  	return aggErr
   138  }
   139  
   140  func removeApp() error {
   141  	svcApp, err := svcApp.New()
   142  	if err != nil {
   143  		return locale.WrapError(err, "err_autostart_app")
   144  	}
   145  
   146  	err = svcApp.Uninstall()
   147  	if err != nil {
   148  		return locale.WrapError(err, "err_uninstall_app", "Could not uninstall the State Tool service app.")
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func verifyInstallation() error {
   155  	return nil
   156  }
   157  
   158  type dirNotEmptyError struct {
   159  	*locale.LocalizedError
   160  }
   161  
   162  func removeEmptyDir(dir string) error {
   163  	if !fileutils.DirExists(dir) {
   164  		return nil
   165  	}
   166  
   167  	empty, err := fileutils.IsEmptyDir(dir)
   168  	if err != nil {
   169  		return errs.Wrap(err, "Could not check if directory is empty")
   170  	}
   171  
   172  	if empty {
   173  		removeErr := os.RemoveAll(dir)
   174  		if removeErr != nil {
   175  			return errs.Wrap(removeErr, "Could not remove directory")
   176  		}
   177  	} else {
   178  		files, err := fileutils.ListDirSimple(dir, true)
   179  		if err != nil {
   180  			return errs.Wrap(err, "Could not list directory")
   181  		}
   182  
   183  		content, err := strutils.ParseTemplate(
   184  			"{{- range $file := .Files}}\n - {{$file}}\n{{- end}}",
   185  			map[string]interface{}{"Files": files},
   186  			nil)
   187  		if err != nil {
   188  			return errs.Wrap(err, "Could not parse file list template")
   189  		}
   190  		return &dirNotEmptyError{locale.NewExternalError("uninstall_warn_not_empty", "", content)}
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func cleanInstallDir(dir string, cfg *config.Instance) error {
   197  	err := legacytray.DetectAndRemove(dir, cfg)
   198  	if err != nil {
   199  		return errs.Wrap(err, "Could not remove legacy tray")
   200  	}
   201  
   202  	execs, err := installation.Executables()
   203  	if err != nil {
   204  		return errs.Wrap(err, "Could not get executable paths")
   205  	}
   206  
   207  	var asFiles = []string{
   208  		installation.InstallDirMarker,
   209  		constants.StateInstallerCmd + osutils.ExeExtension,
   210  	}
   211  
   212  	// Remove all of the state tool executables and finally the
   213  	// bin directory
   214  	asFiles = append(asFiles, execs...)
   215  	asFiles = append(asFiles, installation.BinDirName)
   216  
   217  	for _, file := range asFiles {
   218  		f := filepath.Join(dir, file)
   219  
   220  		var err error
   221  		if fileutils.DirExists(f) && fileutils.IsDir(f) {
   222  			err = os.RemoveAll(f)
   223  		} else if fileutils.FileExists(f) {
   224  			err = os.Remove(f)
   225  		}
   226  		if err != nil {
   227  			return errs.Wrap(err, "Could not clean install directory")
   228  		}
   229  	}
   230  
   231  	return nil
   232  }