github.com/mondo192/jfrog-client-go@v1.0.0/utils/utils.go (about)

     1  package utils
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/url"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/mondo192/jfrog-client-go/utils/io"
    16  
    17  	"github.com/jfrog/build-info-go/entities"
    18  	"github.com/jfrog/gofrog/stringutils"
    19  
    20  	"github.com/mondo192/jfrog-client-go/utils/io/fileutils"
    21  
    22  	"github.com/mondo192/jfrog-client-go/utils/errorutils"
    23  	"github.com/mondo192/jfrog-client-go/utils/log"
    24  )
    25  
    26  const (
    27  	Development = "development"
    28  	Agent       = "jfrog-client-go"
    29  	Version     = "1.28.0"
    30  )
    31  
    32  // In order to limit the number of items loaded from a reader into the memory, we use a buffers with this size limit.
    33  var (
    34  	MaxBufferSize          = 50000
    35  	userAgent              = getDefaultUserAgent()
    36  	curlyParenthesesRegexp = regexp.MustCompile(`\{(\d+?)}`)
    37  )
    38  
    39  func getVersion() string {
    40  	return Version
    41  }
    42  
    43  func GetUserAgent() string {
    44  	return userAgent
    45  }
    46  
    47  func SetUserAgent(newUserAgent string) {
    48  	userAgent = newUserAgent
    49  }
    50  
    51  func getDefaultUserAgent() string {
    52  	return fmt.Sprintf("%s/%s", Agent, getVersion())
    53  }
    54  
    55  // Get the local root path, from which to start collecting artifacts to be used for:
    56  // 1. Uploaded to Artifactory,
    57  // 2. Adding to the local build-info, to be later published to Artifactory.
    58  func GetRootPath(path string, patternType PatternType, parentheses ParenthesesSlice) string {
    59  	// The first step is to split the local path pattern into sections, by the file separator.
    60  	separator := "/"
    61  	sections := strings.Split(path, separator)
    62  	if len(sections) == 1 {
    63  		separator = "\\"
    64  		if strings.Contains(path, "\\\\") {
    65  			sections = strings.Split(path, "\\\\")
    66  		} else {
    67  			sections = strings.Split(path, separator)
    68  		}
    69  	}
    70  
    71  	// Now we start building the root path, making sure to leave out the sub-directory that includes the pattern.
    72  	rootPath := ""
    73  	for _, section := range sections {
    74  		if section == "" {
    75  			continue
    76  		}
    77  		if patternType == RegExp {
    78  			if strings.Contains(section, "(") {
    79  				break
    80  			}
    81  		} else {
    82  			if strings.Contains(section, "*") {
    83  				break
    84  			}
    85  			if strings.Contains(section, "(") {
    86  				temp := rootPath + section
    87  				if isWildcardParentheses(temp, parentheses) {
    88  					break
    89  				}
    90  			}
    91  			if patternType == AntPattern {
    92  				if strings.Contains(section, "?") {
    93  					break
    94  				}
    95  			}
    96  		}
    97  		if rootPath != "" {
    98  			rootPath += separator
    99  		}
   100  		if section == "~" {
   101  			rootPath += GetUserHomeDir()
   102  		} else {
   103  			rootPath += section
   104  		}
   105  	}
   106  	if len(sections) > 0 && sections[0] == "" {
   107  		rootPath = separator + rootPath
   108  	}
   109  	if rootPath == "" {
   110  		return "."
   111  	}
   112  	return rootPath
   113  }
   114  
   115  // Return true if the ‘str’ argument contains open parenthesis, that is related to a placeholder.
   116  // The ‘parentheses’ argument contains all the indexes of placeholder parentheses.
   117  func isWildcardParentheses(str string, parentheses ParenthesesSlice) bool {
   118  	toFind := "("
   119  	currStart := 0
   120  	for {
   121  		idx := strings.Index(str, toFind)
   122  		if idx == -1 {
   123  			break
   124  		}
   125  		if parentheses.IsPresent(idx) {
   126  			return true
   127  		}
   128  		currStart += idx + len(toFind)
   129  		str = str[idx+len(toFind):]
   130  	}
   131  	return false
   132  }
   133  
   134  func StringToBool(boolVal string, defaultValue bool) (bool, error) {
   135  	if len(boolVal) > 0 {
   136  		result, err := strconv.ParseBool(boolVal)
   137  		return result, errorutils.CheckError(err)
   138  	}
   139  	return defaultValue, nil
   140  }
   141  
   142  func AddTrailingSlashIfNeeded(url string) string {
   143  	if url != "" && !strings.HasSuffix(url, "/") {
   144  		url += "/"
   145  	}
   146  	return url
   147  }
   148  
   149  func IndentJson(jsonStr []byte) string {
   150  	return doIndentJson(jsonStr, "", "  ")
   151  }
   152  
   153  func IndentJsonArray(jsonStr []byte) string {
   154  	return doIndentJson(jsonStr, "  ", "  ")
   155  }
   156  
   157  func doIndentJson(jsonStr []byte, prefix, indent string) string {
   158  	var content bytes.Buffer
   159  	err := json.Indent(&content, jsonStr, prefix, indent)
   160  	if err == nil {
   161  		return content.String()
   162  	}
   163  	return string(jsonStr)
   164  }
   165  
   166  func MergeMaps(src map[string]string, dst map[string]string) {
   167  	for k, v := range src {
   168  		dst[k] = v
   169  	}
   170  }
   171  
   172  func CopyMap(src map[string]string) (dst map[string]string) {
   173  	dst = make(map[string]string)
   174  	for k, v := range src {
   175  		dst[k] = v
   176  	}
   177  	return
   178  }
   179  
   180  func ConvertLocalPatternToRegexp(localPath string, patternType PatternType) string {
   181  	if localPath == "./" || localPath == ".\\" || localPath == ".\\\\" {
   182  		return "^.*$"
   183  	}
   184  	localPath = strings.TrimPrefix(localPath, ".\\\\")
   185  	localPath = strings.TrimPrefix(localPath, "./")
   186  	localPath = strings.TrimPrefix(localPath, ".\\")
   187  
   188  	switch patternType {
   189  	case AntPattern:
   190  		localPath = AntToRegex(cleanPath(localPath))
   191  	case WildCardPattern:
   192  		localPath = stringutils.WildcardPatternToRegExp(cleanPath(localPath))
   193  	}
   194  
   195  	return localPath
   196  }
   197  
   198  // Clean /../ | /./ using filepath.Clean.
   199  func cleanPath(path string) string {
   200  	temp := path[len(path)-1:]
   201  	path = filepath.Clean(path)
   202  	if temp == `\` || temp == "/" {
   203  		path += temp
   204  	}
   205  	if io.IsWindows() {
   206  		// Since filepath.Clean replaces \\ with \, we revert this action.
   207  		path = strings.ReplaceAll(path, `\`, `\\`)
   208  	}
   209  	return path
   210  }
   211  
   212  // BuildTargetPath Replaces matched regular expression from path to corresponding placeholder {i} at target.
   213  // Example 1:
   214  //
   215  //	pattern = "repoA/1(.*)234" ; path = "repoA/1hello234" ; target = "{1}" ; ignoreRepo = false
   216  //	returns "hello"
   217  //
   218  // Example 2:
   219  //
   220  //	pattern = "repoA/1(.*)234" ; path = "repoB/1hello234" ; target = "{1}" ; ignoreRepo = true
   221  //	returns "hello"
   222  //
   223  // return (parsed target, placeholders replaced in target, error)
   224  func BuildTargetPath(pattern, path, target string, ignoreRepo bool) (string, bool, error) {
   225  	asteriskIndex := strings.Index(pattern, "*")
   226  	slashIndex := strings.Index(pattern, "/")
   227  	if shouldRemoveRepo(ignoreRepo, asteriskIndex, slashIndex) {
   228  		// Removing the repository part of the path is required when working with virtual repositories, as the pattern
   229  		// may contain the virtual-repository name, but the path contains the local-repository name.
   230  		pattern = removeRepoFromPath(pattern)
   231  		path = removeRepoFromPath(path)
   232  	}
   233  	pattern = addEscapingParentheses(pattern, target)
   234  	pattern = stringutils.WildcardPatternToRegExp(pattern)
   235  	if slashIndex < 0 {
   236  		// If '/' doesn't exist, add an optional trailing-slash to support cases in which the provided pattern
   237  		// is only the repository name.
   238  		dollarIndex := strings.LastIndex(pattern, "$")
   239  		pattern = pattern[:dollarIndex]
   240  		pattern += "(/.*)?$"
   241  	}
   242  
   243  	r, err := regexp.Compile(pattern)
   244  	err = errorutils.CheckError(err)
   245  	if err != nil {
   246  		return "", false, err
   247  	}
   248  
   249  	groups := r.FindStringSubmatch(path)
   250  	if len(groups) > 0 {
   251  		target, replaceOccurred, err := ReplacePlaceHolders(groups, target, false)
   252  		if err != nil {
   253  			return "", false, err
   254  		}
   255  		return target, replaceOccurred, nil
   256  	}
   257  	return target, false, nil
   258  }
   259  
   260  // ReplacePlaceHolders replace placeholders with their matching regular expressions.
   261  // group - Regular expression matched group to replace with placeholders.
   262  // toReplace - Target pattern to replace.
   263  // isRegexp - When using a regular expression, all parentheses content in the target will be at the given group parameter.
   264  // A non-regular expression will, however, allow us to consider the parentheses as literal characters.
   265  // The size of the group (containing the parentheses content) can be smaller than the maximum placeholder indexer - in this case, special treatment is required.
   266  // Example : pattern: (a)/(b)/(c), target: "target/{1}{3}" => '(a)' and '(c)' will be considered as placeholders, and '(b)' will be treated as the directory's actual name.
   267  // In this case, the index of '(c)' in the group is 2, but its placeholder indexer is 3.
   268  // Return - The parsed placeholders string, along with a boolean to indicate whether they have been replaced or not.
   269  func ReplacePlaceHolders(groups []string, toReplace string, isRegexp bool) (string, bool, error) {
   270  	maxPlaceholderIndex, err := getMaxPlaceholderIndex(toReplace)
   271  	if err != nil {
   272  		return "", false, err
   273  	}
   274  	preReplaced := toReplace
   275  	// Index for the placeholder number.
   276  	placeHolderIndexer := 1
   277  	for i := 1; i < len(groups); i++ {
   278  		group := strings.ReplaceAll(groups[i], "\\", "/")
   279  		// Handling non-regular expression cases
   280  		for !isRegexp && !strings.Contains(toReplace, "{"+strconv.Itoa(placeHolderIndexer)+"}") {
   281  			placeHolderIndexer++
   282  			if placeHolderIndexer > maxPlaceholderIndex {
   283  				break
   284  			}
   285  		}
   286  		toReplace = strings.ReplaceAll(toReplace, "{"+strconv.Itoa(placeHolderIndexer)+"}", group)
   287  		placeHolderIndexer++
   288  	}
   289  	replaceOccurred := preReplaced != toReplace
   290  	return toReplace, replaceOccurred, nil
   291  }
   292  
   293  // Returns the higher index between all placeHolders target instances.
   294  // Example: for input "{1}{5}{3}" returns 5.
   295  func getMaxPlaceholderIndex(toReplace string) (int, error) {
   296  	placeholders := curlyParenthesesRegexp.FindAllString(toReplace, -1)
   297  	max := 0
   298  	for _, placeholder := range placeholders {
   299  		num, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSuffix(placeholder, "}"), "{"))
   300  		if err != nil {
   301  			return 0, errorutils.CheckError(err)
   302  		}
   303  		if num > max {
   304  			max = num
   305  		}
   306  	}
   307  	return max, nil
   308  }
   309  
   310  func GetLogMsgPrefix(threadId int, dryRun bool) string {
   311  	var strDryRun string
   312  	if dryRun {
   313  		strDryRun = "[Dry run] "
   314  	}
   315  	return "[Thread " + strconv.Itoa(threadId) + "] " + strDryRun
   316  }
   317  
   318  func TrimPath(path string) string {
   319  	path = strings.Replace(path, "\\", "/", -1)
   320  	path = strings.Replace(path, "//", "/", -1)
   321  	path = strings.Replace(path, "../", "", -1)
   322  	path = strings.Replace(path, "./", "", -1)
   323  	return path
   324  }
   325  
   326  func Bool2Int(b bool) int {
   327  	if b {
   328  		return 1
   329  	}
   330  	return 0
   331  }
   332  
   333  func ReplaceTildeWithUserHome(path string) string {
   334  	if len(path) > 1 && path[0:1] == "~" {
   335  		return GetUserHomeDir() + path[1:]
   336  	}
   337  	return path
   338  }
   339  
   340  func GetUserHomeDir() string {
   341  	if io.IsWindows() {
   342  		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   343  		if home == "" {
   344  			home = os.Getenv("USERPROFILE")
   345  		}
   346  		return strings.Replace(home, "\\", "\\\\", -1)
   347  	}
   348  	return os.Getenv("HOME")
   349  }
   350  
   351  func GetBoolEnvValue(flagName string, defValue bool) (bool, error) {
   352  	envVarValue := os.Getenv(flagName)
   353  	if envVarValue == "" {
   354  		return defValue, nil
   355  	}
   356  	val, err := strconv.ParseBool(envVarValue)
   357  	err = CheckErrorWithMessage(err, "can't parse environment variable "+flagName)
   358  	return val, err
   359  }
   360  
   361  func CheckErrorWithMessage(err error, message string) error {
   362  	if err != nil {
   363  		log.Error(message)
   364  		err = errorutils.CheckError(err)
   365  	}
   366  	return err
   367  }
   368  
   369  func ConvertSliceToMap(slice []string) map[string]bool {
   370  	mapFromSlice := make(map[string]bool)
   371  	for _, value := range slice {
   372  		mapFromSlice[value] = true
   373  	}
   374  	return mapFromSlice
   375  }
   376  
   377  func removeRepoFromPath(path string) string {
   378  	if idx := strings.Index(path, "/"); idx != -1 {
   379  		return path[idx:]
   380  	}
   381  	return path
   382  }
   383  
   384  func shouldRemoveRepo(ignoreRepo bool, asteriskIndex, slashIndex int) bool {
   385  	if !ignoreRepo || slashIndex < 0 {
   386  		return false
   387  	}
   388  	if asteriskIndex < 0 {
   389  		return true
   390  	}
   391  	return IsSlashPrecedeAsterisk(asteriskIndex, slashIndex)
   392  }
   393  
   394  func IsSlashPrecedeAsterisk(asteriskIndex, slashIndex int) bool {
   395  	return slashIndex < asteriskIndex && slashIndex >= 0
   396  }
   397  
   398  // Split str by the provided separator, escaping the separator if it is prefixed by a back-slash.
   399  func SplitWithEscape(str string, separator rune) []string {
   400  	var parts []string
   401  	var current bytes.Buffer
   402  	escaped := false
   403  	for _, char := range str {
   404  		if char == '\\' {
   405  			if escaped {
   406  				current.WriteRune(char)
   407  			}
   408  			escaped = true
   409  		} else if char == separator && !escaped {
   410  			parts = append(parts, current.String())
   411  			current.Reset()
   412  		} else {
   413  			escaped = false
   414  			current.WriteRune(char)
   415  		}
   416  	}
   417  	parts = append(parts, current.String())
   418  	return parts
   419  }
   420  
   421  func AddProps(oldProps, additionalProps string) string {
   422  	if len(oldProps) > 0 && !strings.HasSuffix(oldProps, ";") && len(additionalProps) > 0 {
   423  		oldProps += ";"
   424  	}
   425  	return oldProps + additionalProps
   426  }
   427  
   428  type Artifact struct {
   429  	LocalPath           string
   430  	TargetPath          string
   431  	SymlinkTargetPath   string
   432  	TargetPathInArchive string
   433  }
   434  
   435  const (
   436  	WildCardPattern PatternType = "wildcard"
   437  	RegExp          PatternType = "regexp"
   438  	AntPattern      PatternType = "ant"
   439  )
   440  
   441  type PatternType string
   442  
   443  type PatternTypes struct {
   444  	RegExp bool
   445  	Ant    bool
   446  }
   447  
   448  func GetPatternType(patternTypes PatternTypes) PatternType {
   449  	if patternTypes.RegExp {
   450  		return RegExp
   451  	}
   452  	if patternTypes.Ant {
   453  		return AntPattern
   454  	}
   455  	return WildCardPattern
   456  }
   457  
   458  type Sha256Summary struct {
   459  	sha256    string
   460  	succeeded bool
   461  }
   462  
   463  func NewSha256Summary() *Sha256Summary {
   464  	return &Sha256Summary{}
   465  }
   466  
   467  func (bps *Sha256Summary) IsSucceeded() bool {
   468  	return bps.succeeded
   469  }
   470  
   471  func (bps *Sha256Summary) SetSucceeded(succeeded bool) *Sha256Summary {
   472  	bps.succeeded = succeeded
   473  	return bps
   474  }
   475  
   476  func (bps *Sha256Summary) GetSha256() string {
   477  	return bps.sha256
   478  }
   479  
   480  func (bps *Sha256Summary) SetSha256(sha256 string) *Sha256Summary {
   481  	bps.sha256 = sha256
   482  	return bps
   483  }
   484  
   485  // Represents a file transfer from SourcePath to TargetPath.
   486  // Each of the paths can be on the local machine (full or relative) or in Artifactory (without Artifactory URL).
   487  // The file's Sha256 is calculated by Artifactory during the upload. we read the sha256 from the HTTP's response body.
   488  type FileTransferDetails struct {
   489  	SourcePath string `json:"sourcePath,omitempty"`
   490  	TargetPath string `json:"targetPath,omitempty"`
   491  	RtUrl      string `json:"rtUrl,omitempty"`
   492  	Sha256     string `json:"sha256,omitempty"`
   493  }
   494  
   495  // Represent deployed artifact's details returned from build-info project for maven and gradle.
   496  type DeployableArtifactDetails struct {
   497  	SourcePath       string `json:"sourcePath,omitempty"`
   498  	ArtifactDest     string `json:"artifactDest,omitempty"`
   499  	Sha256           string `json:"sha256,omitempty"`
   500  	DeploySucceeded  bool   `json:"deploySucceeded,omitempty"`
   501  	TargetRepository string `json:"targetRepository,omitempty"`
   502  }
   503  
   504  func (details *DeployableArtifactDetails) CreateFileTransferDetails(rtUrl, targetRepository string) (FileTransferDetails, error) {
   505  	targetUrl, err := url.Parse(path.Join(targetRepository, details.ArtifactDest))
   506  	if err != nil {
   507  		return FileTransferDetails{}, err
   508  	}
   509  	return FileTransferDetails{SourcePath: details.SourcePath, TargetPath: targetUrl.String(), Sha256: details.Sha256, RtUrl: rtUrl}, nil
   510  }
   511  
   512  type UploadResponseBody struct {
   513  	Checksums entities.Checksum `json:"checksums,omitempty"`
   514  }
   515  
   516  func SaveFileTransferDetailsInTempFile(filesDetails *[]FileTransferDetails) (filePath string, err error) {
   517  	tempFile, err := fileutils.CreateTempFile()
   518  	if err != nil {
   519  		return "", err
   520  	}
   521  	defer func() {
   522  		e := tempFile.Close()
   523  		if err == nil {
   524  			err = errorutils.CheckError(e)
   525  		}
   526  	}()
   527  	filePath = tempFile.Name()
   528  	return filePath, SaveFileTransferDetailsInFile(filePath, filesDetails)
   529  }
   530  
   531  func SaveFileTransferDetailsInFile(filePath string, details *[]FileTransferDetails) error {
   532  	// Marshal and save files details to a file.
   533  	// The details will be saved in a json format in an array with key "files" for printing later
   534  	finalResult := struct {
   535  		Files *[]FileTransferDetails `json:"files"`
   536  	}{}
   537  	finalResult.Files = details
   538  	files, err := json.Marshal(finalResult)
   539  	if err != nil {
   540  		return errorutils.CheckError(err)
   541  	}
   542  	return errorutils.CheckError(os.WriteFile(filePath, files, 0700))
   543  }
   544  
   545  // Extract sha256 of the uploaded file (calculated by artifactory) from the response's body.
   546  // In case of uploading archive with "--explode" the response body will be empty and sha256 won't be shown at
   547  // the detailed summary.
   548  func ExtractSha256FromResponseBody(body []byte) (string, error) {
   549  	if len(body) > 0 {
   550  		responseBody := new(UploadResponseBody)
   551  		err := json.Unmarshal(body, &responseBody)
   552  		if errorutils.CheckError(err) != nil {
   553  			return "", err
   554  		}
   555  		return responseBody.Checksums.Sha256, nil
   556  	}
   557  	return "", nil
   558  }