github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/installation/storage/storage.go (about) 1 package storage 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "runtime" 8 "strings" 9 10 "github.com/ActiveState/cli/internal/condition" 11 "github.com/ActiveState/cli/internal/constants" 12 "github.com/ActiveState/cli/internal/osutils/user" 13 "github.com/google/uuid" 14 "github.com/shibukawa/configdir" 15 ) 16 17 func AppDataPath() (string, error) { 18 configDirs := configdir.New(constants.InternalConfigNamespace, fmt.Sprintf("%s-%s", constants.LibraryName, constants.ChannelName)) 19 20 localPath, envSet := os.LookupEnv(constants.ConfigEnvVarName) 21 if envSet { 22 return AppDataPathWithParent(localPath) 23 } else if condition.InUnitTest() { 24 var err error 25 localPath, err = appDataPathInTest() 26 if err != nil { 27 // panic as this only happening in tests 28 panic(err) 29 } 30 return localPath, nil 31 } 32 33 // Account for HOME dir not being set, meaning querying global folders will fail 34 // This is a workaround for docker envs that don't usually have $HOME set 35 _, envSet = os.LookupEnv("HOME") 36 if !envSet && runtime.GOOS != "windows" { 37 homeDir, err := user.HomeDir() 38 if err != nil { 39 if !condition.InUnitTest() { 40 return "", fmt.Errorf("Could not get user home directory: %w", err) 41 } 42 // Use temp dir if we're in a test (we don't want to write to our src directory) 43 var err error 44 localPath, err = os.MkdirTemp("", "cli-config-test") 45 if err != nil { 46 return "", fmt.Errorf("could not create temp dir: %w", err) 47 } 48 return AppDataPathWithParent(localPath) 49 } 50 os.Setenv("HOME", homeDir) 51 } 52 53 dir := configDirs.QueryFolders(configdir.Global)[0].Path 54 if err := os.MkdirAll(dir, os.ModePerm); err != nil { 55 return "", fmt.Errorf("could not create appdata dir: %s", dir) 56 } 57 58 return dir, nil 59 } 60 61 var _appDataPathInTest string 62 63 func appDataPathInTest() (string, error) { 64 if _appDataPathInTest != "" { 65 return _appDataPathInTest, nil 66 } 67 68 localPath, err := os.MkdirTemp("", "cli-config") 69 if err != nil { 70 return "", fmt.Errorf("Could not create temp dir: %w", err) 71 } 72 err = os.RemoveAll(localPath) 73 if err != nil { 74 return "", fmt.Errorf("Could not remove generated config dir for tests: %w", err) 75 } 76 77 _appDataPathInTest = localPath 78 79 return localPath, nil 80 } 81 82 func AppDataPathWithParent(parentDir string) (string, error) { 83 configDirs := configdir.New(constants.InternalConfigNamespace, fmt.Sprintf("%s-%s", constants.LibraryName, constants.ChannelName)) 84 configDirs.LocalPath = parentDir 85 dir := configDirs.QueryFolders(configdir.Local)[0].Path 86 87 if err := os.MkdirAll(dir, os.ModePerm); err != nil { 88 return "", fmt.Errorf("could not create appdata dir: %s", dir) 89 } 90 91 return dir, nil 92 } 93 94 // CachePath returns the path at which our cache is stored 95 func CachePath() string { 96 var err error 97 var cachePath string 98 // When running tests we use a unique cache dir that's located in a temp folder, to avoid collisions 99 if condition.InUnitTest() { 100 prefix := "state-cache-tests" 101 cachePath, err = os.MkdirTemp("", prefix) 102 if err != nil { 103 panic(fmt.Sprintf("Could not create temp dir for CachePath testing: %v", err)) 104 } 105 106 if runtime.GOOS == "windows" { 107 if drive, envExists := os.LookupEnv("SystemDrive"); envExists { 108 cachePath = filepath.Join(drive, "temp", prefix+uuid.New().String()[0:8]) 109 } 110 } 111 } else if path := os.Getenv(constants.CacheEnvVarName); path != "" { 112 cachePath = path 113 } else { 114 cachePath = configdir.New(constants.InternalConfigNamespace, "").QueryCacheFolder().Path 115 if runtime.GOOS == "windows" { 116 // Explicitly append "cache" dir as the cachedir on Windows is the same as the local appdata dir (conflicts with config) 117 cachePath = filepath.Join(cachePath, "cache") 118 } 119 } 120 121 return cachePath 122 } 123 124 func GlobalBinDir() string { 125 return filepath.Join(CachePath(), "bin") 126 } 127 128 // ArtifactCacheDir is where cached artifacts are stored. 129 // This is a shared cache for downloaded artifacts, not installed artifacts. 130 func ArtifactCacheDir() string { 131 return filepath.Join(CachePath(), constants.ArtifactMetaDir) 132 } 133 134 // InstallSource returns the installation source of the State Tool 135 func InstallSource() (string, error) { 136 path, err := AppDataPath() 137 if err != nil { 138 return "", fmt.Errorf("Could not detect AppDataPath: %w", err) 139 } 140 141 installFilePath := filepath.Join(path, constants.InstallSourceFile) 142 installFileData, err := os.ReadFile(installFilePath) 143 if err != nil { 144 return "unknown", nil 145 } 146 147 return strings.TrimSpace(string(installFileData)), nil 148 }