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 }