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  }