github.com/jfrog/jfrog-cli-core/v2@v2.52.0/utils/coreutils/utils.go (about)

     1  package coreutils
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/fs"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"regexp"
    13  	"runtime"
    14  	"strings"
    15  
    16  	"github.com/jfrog/jfrog-client-go/utils"
    17  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    18  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    19  	"github.com/jfrog/jfrog-client-go/utils/log"
    20  )
    21  
    22  const (
    23  	GettingStartedGuideUrl = "https://github.com/jfrog/jfrog-cli/blob/v2/guides/getting-started-with-jfrog-using-the-cli.md"
    24  	JFrogComUrl            = "https://jfrog.com/"
    25  	JFrogHelpUrl           = JFrogComUrl + "help/r/"
    26  )
    27  
    28  const (
    29  	// ReleasesRemoteEnv should be used for downloading the CLI dependencies (extractor jars, analyzerManager etc.) through an Artifactory remote
    30  	// repository, instead of downloading directly from releases.jfrog.io. The remote repository should be
    31  	// configured to proxy releases.jfrog.io.
    32  	// This env var should store a server ID and a remote repository in form of '<ServerID>/<RemoteRepo>'
    33  	ReleasesRemoteEnv = "JFROG_CLI_RELEASES_REPO"
    34  	// DeprecatedExtractorsRemoteEnv is deprecated, it is replaced with ReleasesRemoteEnv.
    35  	// Its functionality was similar to ReleasesRemoteEnv, but it proxies releases.jfrog.io/artifactory/oss-release-local instead.
    36  	DeprecatedExtractorsRemoteEnv = "JFROG_CLI_EXTRACTORS_REMOTE"
    37  	// JFrog releases URL
    38  	JfrogReleasesUrl = "https://releases.jfrog.io/artifactory/"
    39  )
    40  
    41  // Error modes (how should the application behave when the CheckError function is invoked):
    42  type OnError string
    43  
    44  var cliTempDir string
    45  
    46  // User agent - the user of the program that uses this library (usually another program, or the same as the client agent), i.e 'jfrog-pipelines'
    47  var cliUserAgentName string
    48  var cliUserAgentVersion string
    49  
    50  // Client agent - the program that uses this library, i.e 'jfrog-cli-go'
    51  var clientAgentName string
    52  var clientAgentVersion string
    53  
    54  var cliExecutableName string
    55  
    56  func init() {
    57  	// Initialize error handling.
    58  	if os.Getenv(ErrorHandling) == string(OnErrorPanic) {
    59  		errorutils.CheckError = PanicOnError
    60  	}
    61  
    62  	// Initialize the temp base-dir path of the CLI executions.
    63  	cliTempDir = os.Getenv(TempDir)
    64  	if cliTempDir == "" {
    65  		cliTempDir = os.TempDir()
    66  	}
    67  	fileutils.SetTempDirBase(cliTempDir)
    68  }
    69  
    70  func SetIfEmpty(str *string, defaultStr string) bool {
    71  	if *str == "" {
    72  		*str = defaultStr
    73  		return true
    74  	}
    75  	return false
    76  }
    77  
    78  func IsAnyEmpty(strings ...string) bool {
    79  	for _, str := range strings {
    80  		if str == "" {
    81  			return true
    82  		}
    83  	}
    84  	return false
    85  }
    86  
    87  // Exit codes:
    88  type ExitCode struct {
    89  	Code int
    90  }
    91  
    92  var ExitCodeNoError = ExitCode{0}
    93  var ExitCodeError = ExitCode{1}
    94  var ExitCodeFailNoOp = ExitCode{2}
    95  var ExitCodeVulnerableBuild = ExitCode{3}
    96  
    97  type CliError struct {
    98  	ExitCode
    99  	ErrorMsg string
   100  }
   101  
   102  func (err CliError) Error() string {
   103  	return err.ErrorMsg
   104  }
   105  
   106  func PanicOnError(err error) error {
   107  	if err != nil {
   108  		panic(err)
   109  	}
   110  	return err
   111  }
   112  
   113  func ExitOnErr(err error) {
   114  	var cliError CliError
   115  	if errors.As(err, &cliError) {
   116  		traceExit(cliError.ExitCode, err)
   117  	}
   118  	if exitCode := GetExitCode(err, 0, 0, false); exitCode != ExitCodeNoError {
   119  		traceExit(exitCode, err)
   120  	}
   121  }
   122  
   123  func traceExit(exitCode ExitCode, err error) {
   124  	if err != nil && len(err.Error()) > 0 {
   125  		log.Error(err)
   126  	}
   127  	os.Exit(exitCode.Code)
   128  }
   129  
   130  func GetExitCode(err error, success, failed int, failNoOp bool) ExitCode {
   131  	// Error occurred - Return 1
   132  	if err != nil || failed > 0 {
   133  		return ExitCodeError
   134  	}
   135  	// No errors, but also no files affected - Return 2 if failNoOp
   136  	if success == 0 && failNoOp {
   137  		return ExitCodeFailNoOp
   138  	}
   139  	// Otherwise - Return 0
   140  	return ExitCodeNoError
   141  }
   142  
   143  // When running a command in an external process, if the command fails to run or doesn't complete successfully ExitError is returned.
   144  // We would like to return a regular error instead of ExitError,
   145  // because some frameworks (such as codegangsta used by JFrog CLI) automatically exit when this error is returned.
   146  func ConvertExitCodeError(err error) error {
   147  	var exitError *exec.ExitError
   148  	if errors.As(err, &exitError) {
   149  		err = errors.New(err.Error())
   150  	}
   151  	return err
   152  }
   153  
   154  // GetCliConfigVersion returns the latest version of the config.yml file on the file system at '.jfrog'.
   155  func GetCliConfigVersion() int {
   156  	return 6
   157  }
   158  
   159  // GetPluginsConfigVersion returns the latest plugins layout version on the file system (at '.jfrog/plugins').
   160  func GetPluginsConfigVersion() int {
   161  	return 1
   162  }
   163  
   164  func SumTrueValues(boolArr []bool) int {
   165  	counter := 0
   166  	for _, val := range boolArr {
   167  		counter += utils.Bool2Int(val)
   168  	}
   169  	return counter
   170  }
   171  
   172  func SpecVarsStringToMap(rawVars string) map[string]string {
   173  	if len(rawVars) == 0 {
   174  		return nil
   175  	}
   176  	varCandidates := strings.Split(rawVars, ";")
   177  	varsList := []string{}
   178  	for _, v := range varCandidates {
   179  		if len(varsList) > 0 && isEndsWithEscapeChar(varsList[len(varsList)-1]) {
   180  			currentLastVar := varsList[len(varsList)-1]
   181  			varsList[len(varsList)-1] = strings.TrimSuffix(currentLastVar, "\\") + ";" + v
   182  			continue
   183  		}
   184  		varsList = append(varsList, v)
   185  	}
   186  	return varsAsMap(varsList)
   187  }
   188  
   189  func isEndsWithEscapeChar(lastVar string) bool {
   190  	return strings.HasSuffix(lastVar, "\\")
   191  }
   192  
   193  func varsAsMap(vars []string) map[string]string {
   194  	result := map[string]string{}
   195  	for _, v := range vars {
   196  		keyVal := strings.SplitN(v, "=", 2)
   197  		if len(keyVal) != 2 {
   198  			continue
   199  		}
   200  		result[keyVal[0]] = keyVal[1]
   201  	}
   202  	return result
   203  }
   204  
   205  func IsWindows() bool {
   206  	return runtime.GOOS == "windows"
   207  }
   208  
   209  func IsLinux() bool {
   210  	return runtime.GOOS == "linux"
   211  }
   212  
   213  func IsMac() bool {
   214  	return runtime.GOOS == "darwin"
   215  }
   216  
   217  func GetOSAndArc() (string, error) {
   218  	arch := runtime.GOARCH
   219  	// Windows
   220  	if IsWindows() {
   221  		return "windows-amd64", nil
   222  	}
   223  	// Mac
   224  	if IsMac() {
   225  		if arch == "arm64" {
   226  			return "mac-arm64", nil
   227  		} else {
   228  			return "mac-amd64", nil
   229  		}
   230  	}
   231  	// Linux
   232  	if IsLinux() {
   233  		switch arch {
   234  		case "i386", "i486", "i586", "i686", "i786", "x86":
   235  			return "linux-386", nil
   236  		case "amd64", "x86_64", "x64":
   237  			return "linux-amd64", nil
   238  		case "arm", "armv7l":
   239  			return "linux-arm", nil
   240  		case "arm64", "aarch64":
   241  			return "linux-arm64", nil
   242  		case "ppc64", "ppc64le":
   243  			return "linux-" + arch, nil
   244  		}
   245  	}
   246  	return "", errorutils.CheckErrorf("unsupported OS: %s-%s", runtime.GOOS, arch)
   247  }
   248  
   249  // Return the path of CLI temp dir.
   250  // This path should be persistent, meaning - should not be cleared at the end of a CLI run.
   251  func GetCliPersistentTempDirPath() string {
   252  	return cliTempDir
   253  }
   254  
   255  func GetWorkingDirectory() (string, error) {
   256  	currentDir, err := os.Getwd()
   257  	if err != nil {
   258  		return "", errorutils.CheckError(err)
   259  	}
   260  
   261  	if currentDir, err = filepath.Abs(currentDir); err != nil {
   262  		return "", errorutils.CheckError(err)
   263  	}
   264  
   265  	return currentDir, nil
   266  }
   267  
   268  // Receives a list of relative path working dirs, returns a list of full paths working dirs
   269  func GetFullPathsWorkingDirs(workingDirs []string) ([]string, error) {
   270  	if len(workingDirs) == 0 {
   271  		currentDir, err := GetWorkingDirectory()
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  		return []string{currentDir}, nil
   276  	}
   277  
   278  	var fullPathsWorkingDirs []string
   279  	for _, wd := range workingDirs {
   280  		fullPathWd, err := filepath.Abs(wd)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  		fullPathsWorkingDirs = append(fullPathsWorkingDirs, fullPathWd)
   285  	}
   286  	return fullPathsWorkingDirs, nil
   287  }
   288  
   289  type Credentials interface {
   290  	SetUser(string)
   291  	SetPassword(string)
   292  	GetUser() string
   293  	GetPassword() string
   294  }
   295  
   296  func ReplaceVars(content []byte, specVars map[string]string) []byte {
   297  	log.Debug("Replacing variables in the provided content: \n" + string(content))
   298  	for key, val := range specVars {
   299  		key = "${" + key + "}"
   300  		log.Debug(fmt.Sprintf("Replacing '%s' with '%s'", key, val))
   301  		content = bytes.ReplaceAll(content, []byte(key), []byte(val))
   302  	}
   303  	log.Debug("The reformatted content is: \n" + string(content))
   304  	return content
   305  }
   306  
   307  func GetJfrogHomeDir() (string, error) {
   308  	if os.Getenv(HomeDir) != "" {
   309  		return os.Getenv(HomeDir), nil
   310  	}
   311  
   312  	userHomeDir := fileutils.GetHomeDir()
   313  	if userHomeDir == "" {
   314  		err := errorutils.CheckErrorf("couldn't find home directory. Make sure your HOME environment variable is set")
   315  		if err != nil {
   316  			return "", err
   317  		}
   318  	}
   319  	return filepath.Join(userHomeDir, ".jfrog"), nil
   320  }
   321  
   322  func CreateDirInJfrogHome(dirName string) (string, error) {
   323  	homeDir, err := GetJfrogHomeDir()
   324  	if err != nil {
   325  		return "", err
   326  	}
   327  	folderName := filepath.Join(homeDir, dirName)
   328  	err = fileutils.CreateDirIfNotExist(folderName)
   329  	return folderName, err
   330  }
   331  
   332  func GetJfrogSecurityDir() (string, error) {
   333  	homeDir, err := GetJfrogHomeDir()
   334  	if err != nil {
   335  		return "", err
   336  	}
   337  	return filepath.Join(homeDir, JfrogSecurityDirName), nil
   338  }
   339  
   340  func GetJfrogCertsDir() (string, error) {
   341  	securityDir, err := GetJfrogSecurityDir()
   342  	if err != nil {
   343  		return "", err
   344  	}
   345  	return filepath.Join(securityDir, JfrogCertsDirName), nil
   346  }
   347  
   348  func GetJfrogSecurityConfFilePath() (string, error) {
   349  	securityDir, err := GetJfrogSecurityDir()
   350  	if err != nil {
   351  		return "", err
   352  	}
   353  	return filepath.Join(securityDir, JfrogSecurityConfFile), nil
   354  }
   355  
   356  func GetJfrogBackupDir() (string, error) {
   357  	homeDir, err := GetJfrogHomeDir()
   358  	if err != nil {
   359  		return "", err
   360  	}
   361  	return filepath.Join(homeDir, JfrogBackupDirName), nil
   362  }
   363  
   364  func GetJfrogPluginsDir() (string, error) {
   365  	homeDir, err := GetJfrogHomeDir()
   366  	if err != nil {
   367  		return "", err
   368  	}
   369  	return filepath.Join(homeDir, JfrogPluginsDirName), nil
   370  }
   371  
   372  func GetJfrogPluginsResourcesDir(pluginsName string) (string, error) {
   373  	pluginsDir, err := GetJfrogPluginsDir()
   374  	if err != nil {
   375  		return "", err
   376  	}
   377  	return filepath.Join(pluginsDir, pluginsName, PluginsResourcesDirName), nil
   378  }
   379  
   380  func GetPluginsDirContent() ([]os.DirEntry, error) {
   381  	pluginsDir, err := GetJfrogPluginsDir()
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  	exists, err := fileutils.IsDirExists(pluginsDir, false)
   386  	if err != nil || !exists {
   387  		return nil, err
   388  	}
   389  	content, err := os.ReadDir(pluginsDir)
   390  	return content, errorutils.CheckError(err)
   391  }
   392  
   393  func ChmodPluginsDirectoryContent() error {
   394  	plugins, err := GetPluginsDirContent()
   395  	if err != nil || plugins == nil {
   396  		return err
   397  	}
   398  	pluginsDir, err := GetJfrogPluginsDir()
   399  	if err != nil {
   400  		return err
   401  	}
   402  	for _, p := range plugins {
   403  		err = os.Chmod(filepath.Join(pluginsDir, p.Name()), 0777)
   404  		if err != nil {
   405  			return err
   406  		}
   407  	}
   408  	return nil
   409  }
   410  
   411  func GetJfrogLocksDir() (string, error) {
   412  	homeDir, err := GetJfrogHomeDir()
   413  	if err != nil {
   414  		return "", err
   415  	}
   416  	return filepath.Join(homeDir, JfrogLocksDirName), nil
   417  }
   418  
   419  func GetJfrogConfigLockDir() (string, error) {
   420  	configLockDirName := "config"
   421  	locksDirPath, err := GetJfrogLocksDir()
   422  	if err != nil {
   423  		return "", err
   424  	}
   425  	return filepath.Join(locksDirPath, configLockDirName), nil
   426  }
   427  
   428  func GetJfrogPluginsLockDir() (string, error) {
   429  	pluginsLockDirName := "plugins"
   430  	locksDirPath, err := GetJfrogLocksDir()
   431  	if err != nil {
   432  		return "", err
   433  	}
   434  	return filepath.Join(locksDirPath, pluginsLockDirName), nil
   435  }
   436  
   437  func GetJfrogTransferLockDir() (string, error) {
   438  	transferLockDirName := "transfer"
   439  	locksDirPath, err := GetJfrogLocksDir()
   440  	if err != nil {
   441  		return "", err
   442  	}
   443  	return filepath.Join(locksDirPath, transferLockDirName), nil
   444  }
   445  
   446  func GetJfrogTransferRunStatusFilePath() (string, error) {
   447  	transferDir, err := GetJfrogTransferDir()
   448  	if err != nil {
   449  		return "", err
   450  	}
   451  	return filepath.Join(transferDir, JfrogTransferRunStatusFileName), nil
   452  }
   453  
   454  func GetJfrogTransferRepositoriesDir() (string, error) {
   455  	transferDir, err := GetJfrogTransferDir()
   456  	if err != nil {
   457  		return "", err
   458  	}
   459  	return filepath.Join(transferDir, JfrogTransferRepositoriesDirName), nil
   460  }
   461  
   462  func GetJfrogTransferTempDir() (string, error) {
   463  	transferDir, err := GetJfrogTransferDir()
   464  	if err != nil {
   465  		return "", err
   466  	}
   467  	return filepath.Join(transferDir, JfrogTransferTempDirName), nil
   468  }
   469  
   470  // Ask a yes or no question, with a default answer.
   471  func AskYesNo(promptPrefix string, defaultValue bool) bool {
   472  	defStr := "[n]"
   473  	if defaultValue {
   474  		defStr = "[y]"
   475  	}
   476  	promptPrefix += " (y/n) " + defStr + "? "
   477  	var answer string
   478  	for {
   479  		fmt.Print(promptPrefix)
   480  		_, _ = fmt.Scanln(&answer)
   481  		parsed, valid := parseYesNo(answer, defaultValue)
   482  		if valid {
   483  			return parsed
   484  		}
   485  		log.Output("Please enter a valid option.")
   486  	}
   487  }
   488  
   489  func parseYesNo(s string, def bool) (ans, valid bool) {
   490  	s = strings.TrimSpace(s)
   491  	if s == "" {
   492  		return def, true
   493  	}
   494  	matchedYes, err := regexp.MatchString("^yes$|^y$", strings.ToLower(s))
   495  	if errorutils.CheckError(err) != nil {
   496  		log.Error(err)
   497  		return matchedYes, false
   498  	}
   499  	if matchedYes {
   500  		return true, true
   501  	}
   502  
   503  	matchedNo, err := regexp.MatchString("^no$|^n$", strings.ToLower(s))
   504  	if errorutils.CheckError(err) != nil {
   505  		log.Error(err)
   506  		return matchedNo, false
   507  	}
   508  	if matchedNo {
   509  		return false, true
   510  	}
   511  	return false, false
   512  }
   513  
   514  func GetJsonIndent(o any) (strJson string, err error) {
   515  	byteJson, err := json.MarshalIndent(o, "", "  ")
   516  	if err != nil {
   517  		err = errorutils.CheckError(err)
   518  		return
   519  	}
   520  	strJson = string(byteJson)
   521  	return
   522  }
   523  
   524  func GetCliUserAgent() string {
   525  	if cliUserAgentVersion == "" {
   526  		return cliUserAgentName
   527  	}
   528  	return fmt.Sprintf("%s/%s", cliUserAgentName, cliUserAgentVersion)
   529  }
   530  
   531  func SetCliUserAgentName(cliUserAgentNameToSet string) {
   532  	cliUserAgentName = cliUserAgentNameToSet
   533  }
   534  
   535  func GetCliUserAgentName() string {
   536  	return cliUserAgentName
   537  }
   538  
   539  func SetCliUserAgentVersion(versionToSet string) {
   540  	cliUserAgentVersion = versionToSet
   541  }
   542  
   543  func GetCliUserAgentVersion() string {
   544  	return cliUserAgentVersion
   545  }
   546  
   547  func SetClientAgentName(clientAgentToSet string) {
   548  	clientAgentName = clientAgentToSet
   549  }
   550  
   551  func GetClientAgentName() string {
   552  	return clientAgentName
   553  }
   554  
   555  func SetClientAgentVersion(versionToSet string) {
   556  	clientAgentVersion = versionToSet
   557  }
   558  
   559  func GetClientAgentVersion() string {
   560  	return clientAgentVersion
   561  }
   562  
   563  func SetCliExecutableName(executableName string) {
   564  	cliExecutableName = executableName
   565  }
   566  
   567  func GetCliExecutableName() string {
   568  	return cliExecutableName
   569  }
   570  
   571  // Turn a list of strings into a sentence.
   572  // For example, turn ["one", "two", "three"] into "one, two and three".
   573  // For a single element: "one".
   574  func ListToText(list []string) string {
   575  	if len(list) == 1 {
   576  		return list[0]
   577  	}
   578  	return strings.Join(list[0:len(list)-1], ", ") + " and " + list[len(list)-1]
   579  }
   580  
   581  func RemoveAllWhiteSpaces(input string) string {
   582  	return strings.Join(strings.Fields(input), "")
   583  }
   584  
   585  func GetJfrogTransferDir() (string, error) {
   586  	homeDir, err := GetJfrogHomeDir()
   587  	if err != nil {
   588  		return "", err
   589  	}
   590  	return filepath.Join(homeDir, JfrogTransferDirName), nil
   591  }
   592  
   593  func GetServerIdAndRepo(remoteEnv string) (serverID string, repoName string, err error) {
   594  	serverAndRepo := os.Getenv(remoteEnv)
   595  	if serverAndRepo == "" {
   596  		log.Debug(remoteEnv, "is not set")
   597  		return
   598  	}
   599  	// The serverAndRepo is in the form of '<ServerID>/<RemoteRepo>'
   600  	serverID, repoName, separatorExists := strings.Cut(serverAndRepo, "/")
   601  	// Check that the format is valid
   602  	if !separatorExists || repoName == "" || serverID == "" {
   603  		err = errorutils.CheckErrorf("'%s' environment variable is '%s' but should be '<server ID>/<repo name>'", remoteEnv, serverAndRepo)
   604  	}
   605  	return
   606  }
   607  
   608  func GetMaskedCommandString(cmd *exec.Cmd) string {
   609  	cmdString := strings.Join(cmd.Args, " ")
   610  	// Mask url if required
   611  	matchedResult := regexp.MustCompile(utils.CredentialsInUrlRegexp).FindString(cmdString)
   612  	if matchedResult != "" {
   613  		cmdString = strings.ReplaceAll(cmdString, matchedResult, "***@")
   614  	}
   615  
   616  	matchedResults := regexp.MustCompile(`--(?:password|access-token)=(\S+)`).FindStringSubmatch(cmdString)
   617  	if len(matchedResults) > 1 && matchedResults[1] != "" {
   618  		cmdString = strings.ReplaceAll(cmdString, matchedResults[1], "***")
   619  	}
   620  	return cmdString
   621  }
   622  
   623  func SetPermissionsRecursively(dirPath string, mode os.FileMode) error {
   624  	err := filepath.WalkDir(dirPath, func(path string, info fs.DirEntry, e error) error {
   625  		if e != nil {
   626  			return e
   627  		}
   628  		e = os.Chmod(path, mode)
   629  		if e != nil {
   630  			return e
   631  		}
   632  		return nil
   633  	})
   634  	if err != nil {
   635  		return errorutils.CheckErrorf("failed while setting permission to '%s' files: %s", dirPath, err.Error())
   636  	}
   637  	return nil
   638  }