github.com/jfrog/jfrog-cli-core/v2@v2.51.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 }