github.com/cobalt77/jfrog-client-go@v0.14.5/artifactory/services/upload.go (about)

     1  package services
     2  
     3  import (
     4  	"net/http"
     5  	"os"
     6  	"path/filepath"
     7  	"regexp"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	rthttpclient "github.com/cobalt77/jfrog-client-go/artifactory/httpclient"
    13  	"github.com/cobalt77/jfrog-client-go/artifactory/services/fspatterns"
    14  	"github.com/cobalt77/jfrog-client-go/artifactory/services/utils"
    15  	"github.com/cobalt77/jfrog-client-go/auth"
    16  	clientutils "github.com/cobalt77/jfrog-client-go/utils"
    17  	"github.com/cobalt77/jfrog-client-go/utils/errorutils"
    18  	ioutils "github.com/cobalt77/jfrog-client-go/utils/io"
    19  	"github.com/cobalt77/jfrog-client-go/utils/io/fileutils"
    20  	"github.com/cobalt77/jfrog-client-go/utils/io/fileutils/checksum"
    21  	"github.com/cobalt77/jfrog-client-go/utils/io/httputils"
    22  	"github.com/cobalt77/jfrog-client-go/utils/log"
    23  	"github.com/jfrog/gofrog/parallel"
    24  )
    25  
    26  type UploadService struct {
    27  	client     *rthttpclient.ArtifactoryHttpClient
    28  	Progress   ioutils.Progress
    29  	ArtDetails auth.ServiceDetails
    30  	DryRun     bool
    31  	Threads    int
    32  }
    33  
    34  func NewUploadService(client *rthttpclient.ArtifactoryHttpClient) *UploadService {
    35  	return &UploadService{client: client}
    36  }
    37  
    38  func (us *UploadService) SetThreads(threads int) {
    39  	us.Threads = threads
    40  }
    41  
    42  func (us *UploadService) GetJfrogHttpClient() *rthttpclient.ArtifactoryHttpClient {
    43  	return us.client
    44  }
    45  
    46  func (us *UploadService) SetServiceDetails(artDetails auth.ServiceDetails) {
    47  	us.ArtDetails = artDetails
    48  }
    49  
    50  func (us *UploadService) SetDryRun(isDryRun bool) {
    51  	us.DryRun = isDryRun
    52  }
    53  
    54  func (us *UploadService) UploadFiles(uploadParams ...UploadParams) (artifactsFileInfo []utils.FileInfo, totalUploaded, totalFailed int, err error) {
    55  	// Uploading threads are using this struct to report upload results.
    56  	uploadSummary := *utils.NewUploadResult(us.Threads)
    57  	producerConsumer := parallel.NewRunner(us.Threads, 100, false)
    58  	errorsQueue := clientutils.NewErrorsQueue(1)
    59  	us.prepareUploadTasks(producerConsumer, errorsQueue, uploadSummary, uploadParams...)
    60  	return us.performUploadTasks(producerConsumer, &uploadSummary, errorsQueue)
    61  }
    62  
    63  func (us *UploadService) prepareUploadTasks(producer parallel.Runner, errorsQueue *clientutils.ErrorsQueue, uploadSummary utils.UploadResult, uploadParamsSlice ...UploadParams) {
    64  	go func() {
    65  		defer producer.Done()
    66  		// Iterate over file-spec groups and produce upload tasks.
    67  		// When encountering an error, log and move to next group.
    68  		vcsCache := clientutils.NewVcsDetals()
    69  		for _, uploadParams := range uploadParamsSlice {
    70  			artifactHandlerFunc := us.createArtifactHandlerFunc(&uploadSummary, uploadParams)
    71  			err := collectFilesForUpload(uploadParams, producer, artifactHandlerFunc, errorsQueue, vcsCache)
    72  			if err != nil {
    73  				log.Error(err)
    74  				errorsQueue.AddError(err)
    75  			}
    76  		}
    77  	}()
    78  }
    79  
    80  func (us *UploadService) performUploadTasks(consumer parallel.Runner, uploadSummary *utils.UploadResult, errorsQueue *clientutils.ErrorsQueue) (artifactsFileInfo []utils.FileInfo, totalUploaded, totalFailed int, err error) {
    81  	// Blocking until consuming is finished.
    82  	consumer.Run()
    83  	err = errorsQueue.GetError()
    84  
    85  	totalUploaded = utils.SumIntArray(uploadSummary.SuccessCount)
    86  	totalUploadAttempted := utils.SumIntArray(uploadSummary.TotalCount)
    87  
    88  	log.Debug("Uploaded", strconv.Itoa(totalUploaded), "artifacts.")
    89  	totalFailed = totalUploadAttempted - totalUploaded
    90  	if totalFailed > 0 {
    91  		log.Error("Failed uploading", strconv.Itoa(totalFailed), "artifacts.")
    92  	}
    93  	artifactsFileInfo = utils.FlattenFileInfoArray(uploadSummary.FileInfo)
    94  	return
    95  }
    96  
    97  func addProps(oldProps, additionalProps string) string {
    98  	if len(oldProps) > 0 && !strings.HasSuffix(oldProps, ";") && len(additionalProps) > 0 {
    99  		oldProps += ";"
   100  	}
   101  	return oldProps + additionalProps
   102  }
   103  
   104  func addSymlinkProps(artifact clientutils.Artifact, uploadParams UploadParams) (string, error) {
   105  	artifactProps := ""
   106  	artifactSymlink := artifact.Symlink
   107  	if uploadParams.IsSymlink() && len(artifactSymlink) > 0 {
   108  		sha1Property := ""
   109  		fileInfo, err := os.Stat(artifact.LocalPath)
   110  		if err != nil {
   111  			// If error occurred, but not due to nonexistence of Symlink target -> return empty
   112  			if !os.IsNotExist(err) {
   113  				return "", err
   114  			}
   115  			// If Symlink target exists -> get SHA1 if isn't a directory
   116  		} else if !fileInfo.IsDir() {
   117  			file, err := os.Open(artifact.LocalPath)
   118  			if err != nil {
   119  				return "", errorutils.CheckError(err)
   120  			}
   121  			defer file.Close()
   122  			checksumInfo, err := checksum.Calc(file, checksum.SHA1)
   123  			if err != nil {
   124  				return "", err
   125  			}
   126  			sha1 := checksumInfo[checksum.SHA1]
   127  			sha1Property = ";" + utils.SYMLINK_SHA1 + "=" + sha1
   128  		}
   129  		artifactProps += utils.ARTIFACTORY_SYMLINK + "=" + artifactSymlink + sha1Property
   130  	}
   131  	props := uploadParams.GetProps()
   132  	artifactProps = addProps(props, artifactProps)
   133  	return artifactProps, nil
   134  }
   135  
   136  func collectFilesForUpload(uploadParams UploadParams, producer parallel.Runner, artifactHandlerFunc artifactContext, errorsQueue *clientutils.ErrorsQueue, vcsCache *clientutils.VcsCache) error {
   137  	if strings.Index(uploadParams.GetTarget(), "/") < 0 {
   138  		uploadParams.SetTarget(uploadParams.GetTarget() + "/")
   139  	}
   140  	uploadParams.SetPattern(clientutils.ReplaceTildeWithUserHome(uploadParams.GetPattern()))
   141  	// Save parentheses index in pattern, witch have corresponding placeholder.
   142  	rootPath, err := fspatterns.GetRootPath(uploadParams.GetPattern(), uploadParams.GetTarget(), uploadParams.IsRegexp(), uploadParams.IsSymlink())
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	isDir, err := fileutils.IsDirExists(rootPath, uploadParams.IsSymlink())
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	// If the path is a single file (or a symlink while preserving symlinks) upload it and return
   153  	if !isDir || (fileutils.IsPathSymlink(rootPath) && uploadParams.IsSymlink()) {
   154  		artifact, err := fspatterns.GetSingleFileToUpload(rootPath, uploadParams.GetTarget(), uploadParams.IsFlat(), uploadParams.IsSymlink())
   155  		if err != nil {
   156  			return err
   157  		}
   158  		props, err := addSymlinkProps(artifact, uploadParams)
   159  		if err != nil {
   160  			return err
   161  		}
   162  		if uploadParams.IsAddVcsProps() {
   163  			vcsProps, err := getVcsProps(artifact.LocalPath, vcsCache)
   164  			if err != nil {
   165  				return err
   166  			}
   167  			uploadParams.BuildProps += vcsProps
   168  		}
   169  		uploadData := UploadData{Artifact: artifact, Props: props, BuildProps: uploadParams.BuildProps}
   170  		task := artifactHandlerFunc(uploadData)
   171  		producer.AddTaskWithError(task, errorsQueue.AddError)
   172  		return err
   173  	}
   174  	uploadParams.SetPattern(clientutils.PrepareLocalPathForUpload(uploadParams.GetPattern(), uploadParams.IsRegexp()))
   175  	err = collectPatternMatchingFiles(uploadParams, rootPath, producer, artifactHandlerFunc, errorsQueue, vcsCache)
   176  	return err
   177  }
   178  
   179  func collectPatternMatchingFiles(uploadParams UploadParams, rootPath string, producer parallel.Runner, artifactHandlerFunc artifactContext, errorsQueue *clientutils.ErrorsQueue, vcsCache *clientutils.VcsCache) error {
   180  	excludePathPattern := fspatterns.PrepareExcludePathPattern(uploadParams)
   181  	patternRegex, err := regexp.Compile(uploadParams.GetPattern())
   182  	if errorutils.CheckError(err) != nil {
   183  		return err
   184  	}
   185  
   186  	paths, err := fspatterns.GetPaths(rootPath, uploadParams.IsRecursive(), uploadParams.IsIncludeDirs(), uploadParams.IsSymlink())
   187  	if err != nil {
   188  		return err
   189  	}
   190  	// Longest paths first
   191  	sort.Sort(sort.Reverse(sort.StringSlice(paths)))
   192  	// 'foldersPaths' is a subset of the 'paths' array. foldersPaths is in use only when we need to upload folders with flat=true.
   193  	// 'foldersPaths' will contain only the directories paths which are in the 'paths' array.
   194  	var foldersPaths []string
   195  	for index, path := range paths {
   196  		matches, isDir, isSymlinkFlow, err := fspatterns.PrepareAndFilterPaths(path, excludePathPattern, uploadParams.IsSymlink(), uploadParams.IsIncludeDirs(), patternRegex)
   197  		if err != nil {
   198  			return err
   199  		}
   200  
   201  		if matches != nil && len(matches) > 0 {
   202  			target := uploadParams.GetTarget()
   203  			tempPaths := paths
   204  			tempIndex := index
   205  			// In case we need to upload directories with flat=true, we want to avoid the creation of unnecessary paths in Artifactory.
   206  			// To achieve this, we need to take into consideration the directories which had already been uploaded, ignoring all files paths.
   207  			// When flat=false we take into consideration folder paths which were created implicitly by file upload
   208  			if uploadParams.IsFlat() && uploadParams.IsIncludeDirs() && isDir {
   209  				foldersPaths = append(foldersPaths, path)
   210  				tempPaths = foldersPaths
   211  				tempIndex = len(foldersPaths) - 1
   212  			}
   213  			taskData := &uploadTaskData{target: target, path: path, isDir: isDir, isSymlinkFlow: isSymlinkFlow,
   214  				paths: tempPaths, groups: matches, index: tempIndex, size: len(matches), uploadParams: uploadParams,
   215  				producer: producer, artifactHandlerFunc: artifactHandlerFunc, errorsQueue: errorsQueue,
   216  			}
   217  			createUploadTask(taskData, vcsCache)
   218  		}
   219  	}
   220  	return nil
   221  }
   222  
   223  type uploadTaskData struct {
   224  	target              string
   225  	path                string
   226  	isDir               bool
   227  	isSymlinkFlow       bool
   228  	paths               []string
   229  	groups              []string
   230  	index               int
   231  	size                int
   232  	uploadParams        UploadParams
   233  	producer            parallel.Runner
   234  	artifactHandlerFunc artifactContext
   235  	errorsQueue         *clientutils.ErrorsQueue
   236  }
   237  
   238  func createUploadTask(taskData *uploadTaskData, vcsCache *clientutils.VcsCache) error {
   239  	for i := 1; i < taskData.size; i++ {
   240  		group := strings.Replace(taskData.groups[i], "\\", "/", -1)
   241  		taskData.target = strings.Replace(taskData.target, "{"+strconv.Itoa(i)+"}", group, -1)
   242  	}
   243  	var task parallel.TaskFunc
   244  
   245  	// Get symlink target (returns empty string if regular file) - Used in upload name / symlinks properties
   246  	symlinkPath, err := fspatterns.GetFileSymlinkPath(taskData.path)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	// If preserving symlinks or symlink target is empty, use root path name for upload (symlink itself / regular file)
   252  	if taskData.uploadParams.IsSymlink() || symlinkPath == "" {
   253  		taskData.target = getUploadTarget(taskData.path, taskData.target, taskData.uploadParams.IsFlat())
   254  	} else {
   255  		taskData.target = getUploadTarget(symlinkPath, taskData.target, taskData.uploadParams.IsFlat())
   256  	}
   257  
   258  	artifact := clientutils.Artifact{LocalPath: taskData.path, TargetPath: taskData.target, Symlink: symlinkPath}
   259  	props, e := addSymlinkProps(artifact, taskData.uploadParams)
   260  	if e != nil {
   261  		return e
   262  	}
   263  	if taskData.uploadParams.IsAddVcsProps() {
   264  		vcsProps, err := getVcsProps(taskData.path, vcsCache)
   265  		if err != nil {
   266  			return err
   267  		}
   268  		taskData.uploadParams.BuildProps += vcsProps
   269  	}
   270  	uploadData := UploadData{Artifact: artifact, Props: props, BuildProps: taskData.uploadParams.BuildProps}
   271  	if taskData.isDir && taskData.uploadParams.IsIncludeDirs() && !taskData.isSymlinkFlow {
   272  		if taskData.path != "." && (taskData.index == 0 || !utils.IsSubPath(taskData.paths, taskData.index, fileutils.GetFileSeparator())) {
   273  			uploadData.IsDir = true
   274  		} else {
   275  			return nil
   276  		}
   277  	}
   278  	task = taskData.artifactHandlerFunc(uploadData)
   279  	taskData.producer.AddTaskWithError(task, taskData.errorsQueue.AddError)
   280  	return nil
   281  }
   282  
   283  // Construct the target path while taking `flat` flag into account.
   284  func getUploadTarget(rootPath, target string, isFlat bool) string {
   285  	if strings.HasSuffix(target, "/") {
   286  		if isFlat {
   287  			fileName, _ := fileutils.GetFileAndDirFromPath(rootPath)
   288  			target += fileName
   289  		} else {
   290  			target += clientutils.TrimPath(rootPath)
   291  		}
   292  	}
   293  	return target
   294  }
   295  
   296  func addPropsToTargetPath(targetPath, props, buildProps, debConfig string) (string, error) {
   297  	propsStr := strings.Join([]string{props, getDebianProps(debConfig)}, ";")
   298  	properties, err := utils.ParseProperties(propsStr, utils.SplitCommas)
   299  	if err != nil {
   300  		return "", err
   301  	}
   302  	buildProperties, err := utils.ParseProperties(buildProps, utils.JoinCommas)
   303  	if err != nil {
   304  		return "", err
   305  	}
   306  	return strings.Join([]string{targetPath, properties.ToEncodedString(), buildProperties.ToEncodedString()}, ";"), nil
   307  }
   308  
   309  func prepareUploadData(localPath, baseTargetPath, props, buildProps string, uploadParams UploadParams, logMsgPrefix string) (fileInfo os.FileInfo, targetPath string, err error) {
   310  	targetPath, err = addPropsToTargetPath(baseTargetPath, props, buildProps, uploadParams.GetDebian())
   311  	if errorutils.CheckError(err) != nil {
   312  		return
   313  	}
   314  	log.Info(logMsgPrefix+"Uploading artifact:", localPath)
   315  
   316  	fileInfo, err = os.Lstat(localPath)
   317  	errorutils.CheckError(err)
   318  	return
   319  }
   320  
   321  // Uploads the file in the specified local path to the specified target path.
   322  // Returns true if the file was successfully uploaded.
   323  func (us *UploadService) uploadFile(localPath, targetPath, pathInArtifactory, props, buildProps string, uploadParams UploadParams, logMsgPrefix string) (utils.FileInfo, bool, error) {
   324  	fileInfo, targetPathWithProps, err := prepareUploadData(localPath, targetPath, props, buildProps, uploadParams, logMsgPrefix)
   325  	if err != nil {
   326  		return utils.FileInfo{}, false, err
   327  	}
   328  
   329  	var checksumDeployed = false
   330  	var resp *http.Response
   331  	var details *fileutils.FileDetails
   332  	var body []byte
   333  	httpClientsDetails := us.ArtDetails.CreateHttpClientDetails()
   334  	if errorutils.CheckError(err) != nil {
   335  		return utils.FileInfo{}, false, err
   336  	}
   337  	if uploadParams.IsSymlink() && fileutils.IsFileSymlink(fileInfo) {
   338  		resp, details, body, err = us.uploadSymlink(targetPathWithProps, logMsgPrefix, httpClientsDetails, uploadParams)
   339  	} else {
   340  		resp, details, body, checksumDeployed, err = us.doUpload(localPath, targetPathWithProps, logMsgPrefix, httpClientsDetails, fileInfo, uploadParams)
   341  	}
   342  	if err != nil {
   343  		return utils.FileInfo{}, false, err
   344  	}
   345  	logUploadResponse(logMsgPrefix, resp, body, checksumDeployed, us.DryRun)
   346  	artifact := createBuildArtifactItem(details, localPath, targetPath, pathInArtifactory)
   347  	return artifact, us.DryRun || checksumDeployed || resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK, nil
   348  }
   349  
   350  func (us *UploadService) uploadSymlink(targetPath, logMsgPrefix string, httpClientsDetails httputils.HttpClientDetails, uploadParams UploadParams) (resp *http.Response, details *fileutils.FileDetails, body []byte, err error) {
   351  	details, err = fspatterns.CreateSymlinkFileDetails()
   352  	if err != nil {
   353  		return
   354  	}
   355  	resp, body, err = utils.UploadFile("", targetPath, logMsgPrefix, &us.ArtDetails, details, httpClientsDetails, us.client, uploadParams.GetRetries(), nil)
   356  	return
   357  }
   358  
   359  func (us *UploadService) doUpload(localPath, targetPath, logMsgPrefix string, httpClientsDetails httputils.HttpClientDetails, fileInfo os.FileInfo, uploadParams UploadParams) (*http.Response, *fileutils.FileDetails, []byte, bool, error) {
   360  	var details *fileutils.FileDetails
   361  	var checksumDeployed bool
   362  	var resp *http.Response
   363  	var body []byte
   364  	var err error
   365  	addExplodeHeader(&httpClientsDetails, uploadParams.IsExplodeArchive())
   366  	if fileInfo.Size() >= uploadParams.MinChecksumDeploy && !uploadParams.IsExplodeArchive() {
   367  		resp, details, body, err = us.tryChecksumDeploy(localPath, targetPath, httpClientsDetails, us.client)
   368  		if err != nil {
   369  			return resp, details, body, checksumDeployed, err
   370  		}
   371  		checksumDeployed = !us.DryRun && (resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK)
   372  	}
   373  	if !us.DryRun && !checksumDeployed {
   374  		var body []byte
   375  		resp, body, err = utils.UploadFile(localPath, targetPath, logMsgPrefix, &us.ArtDetails, details,
   376  			httpClientsDetails, us.client, uploadParams.Retries, us.Progress)
   377  		if err != nil {
   378  			return resp, details, body, checksumDeployed, err
   379  		}
   380  	}
   381  	if details == nil {
   382  		details, err = fileutils.GetFileDetails(localPath)
   383  	}
   384  	return resp, details, body, checksumDeployed, err
   385  }
   386  
   387  func logUploadResponse(logMsgPrefix string, resp *http.Response, body []byte, checksumDeployed, isDryRun bool) {
   388  	if resp != nil && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
   389  		log.Error(logMsgPrefix + "Artifactory response: " + resp.Status + "\n" + clientutils.IndentJson(body))
   390  		return
   391  	}
   392  	if !isDryRun {
   393  		var strChecksumDeployed string
   394  		if checksumDeployed {
   395  			strChecksumDeployed = " (Checksum deploy)"
   396  		} else {
   397  			strChecksumDeployed = ""
   398  		}
   399  		log.Debug(logMsgPrefix, "Artifactory response:", resp.Status, strChecksumDeployed)
   400  	}
   401  }
   402  
   403  func createBuildArtifactItem(details *fileutils.FileDetails, localPath, targetPath, pathInArtifactory string) utils.FileInfo {
   404  	return utils.FileInfo{
   405  		LocalPath:               localPath,
   406  		ArtifactoryPath:         targetPath,
   407  		InternalArtifactoryPath: pathInArtifactory,
   408  		FileHashes: &utils.FileHashes{
   409  			Sha256: details.Checksum.Sha256,
   410  			Sha1:   details.Checksum.Sha1,
   411  			Md5:    details.Checksum.Md5,
   412  		},
   413  	}
   414  }
   415  
   416  func addExplodeHeader(httpClientsDetails *httputils.HttpClientDetails, isExplode bool) {
   417  	if isExplode {
   418  		utils.AddHeader("X-Explode-Archive", "true", &httpClientsDetails.Headers)
   419  	}
   420  }
   421  
   422  func (us *UploadService) tryChecksumDeploy(filePath, targetPath string, httpClientsDetails httputils.HttpClientDetails,
   423  	client *rthttpclient.ArtifactoryHttpClient) (resp *http.Response, details *fileutils.FileDetails, body []byte, err error) {
   424  	if us.DryRun {
   425  		return
   426  	}
   427  	details, err = fileutils.GetFileDetails(filePath)
   428  	if err != nil {
   429  		return
   430  	}
   431  
   432  	requestClientDetails := httpClientsDetails.Clone()
   433  	utils.AddHeader("X-Checksum-Deploy", "true", &requestClientDetails.Headers)
   434  	utils.AddChecksumHeaders(requestClientDetails.Headers, details)
   435  	utils.AddAuthHeaders(requestClientDetails.Headers, us.ArtDetails)
   436  
   437  	resp, body, err = client.SendPut(targetPath, nil, requestClientDetails)
   438  	return
   439  }
   440  
   441  func getDebianProps(debianPropsStr string) string {
   442  	if debianPropsStr == "" {
   443  		return ""
   444  	}
   445  	result := ""
   446  	debProps := clientutils.SplitWithEscape(debianPropsStr, '/')
   447  	for k, v := range []string{"deb.distribution", "deb.component", "deb.architecture"} {
   448  		debProp := strings.Join([]string{v, debProps[k]}, "=")
   449  		result = strings.Join([]string{result, debProp}, ";")
   450  	}
   451  	return result
   452  }
   453  
   454  type UploadParams struct {
   455  	*utils.ArtifactoryCommonParams
   456  	Deb               string
   457  	BuildProps        string
   458  	Symlink           bool
   459  	ExplodeArchive    bool
   460  	Flat              bool
   461  	AddVcsProps       bool
   462  	Retries           int
   463  	MinChecksumDeploy int64
   464  }
   465  
   466  func (up *UploadParams) IsFlat() bool {
   467  	return up.Flat
   468  }
   469  
   470  func (up *UploadParams) IsSymlink() bool {
   471  	return up.Symlink
   472  }
   473  
   474  func (up *UploadParams) IsAddVcsProps() bool {
   475  	return up.AddVcsProps
   476  }
   477  
   478  func (up *UploadParams) IsExplodeArchive() bool {
   479  	return up.ExplodeArchive
   480  }
   481  
   482  func (up *UploadParams) GetDebian() string {
   483  	return up.Deb
   484  }
   485  
   486  func (up *UploadParams) GetRetries() int {
   487  	return up.Retries
   488  }
   489  
   490  type UploadData struct {
   491  	Artifact   clientutils.Artifact
   492  	Props      string
   493  	BuildProps string
   494  	IsDir      bool
   495  }
   496  
   497  type artifactContext func(UploadData) parallel.TaskFunc
   498  
   499  func (us *UploadService) createArtifactHandlerFunc(uploadResult *utils.UploadResult, uploadParams UploadParams) artifactContext {
   500  	return func(artifact UploadData) parallel.TaskFunc {
   501  		return func(threadId int) (e error) {
   502  			if artifact.IsDir {
   503  				us.createFolderInArtifactory(artifact)
   504  				return
   505  			}
   506  			var uploaded bool
   507  			var target string
   508  			var artifactFileInfo utils.FileInfo
   509  			uploadResult.TotalCount[threadId]++
   510  			logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, us.DryRun)
   511  			target, e = utils.BuildArtifactoryUrl(us.ArtDetails.GetUrl(), artifact.Artifact.TargetPath, make(map[string]string))
   512  			if e != nil {
   513  				return
   514  			}
   515  			artifactFileInfo, uploaded, e = us.uploadFile(artifact.Artifact.LocalPath, target, artifact.Artifact.TargetPath, artifact.Props, artifact.BuildProps, uploadParams, logMsgPrefix)
   516  			if e != nil {
   517  				return
   518  			}
   519  			if uploaded {
   520  				uploadResult.SuccessCount[threadId]++
   521  				uploadResult.FileInfo[threadId] = append(uploadResult.FileInfo[threadId], artifactFileInfo)
   522  			}
   523  			return
   524  		}
   525  	}
   526  }
   527  
   528  func (us *UploadService) createFolderInArtifactory(artifact UploadData) error {
   529  	url, err := utils.BuildArtifactoryUrl(us.ArtDetails.GetUrl(), artifact.Artifact.TargetPath, make(map[string]string))
   530  	url = clientutils.AddTrailingSlashIfNeeded(url)
   531  	if err != nil {
   532  		return err
   533  	}
   534  	content := make([]byte, 0)
   535  	httpClientsDetails := us.ArtDetails.CreateHttpClientDetails()
   536  	resp, body, err := us.client.SendPut(url, content, &httpClientsDetails)
   537  	if err != nil {
   538  		log.Debug(resp)
   539  		return err
   540  	}
   541  	logUploadResponse("Uploaded directory:", resp, body, false, us.DryRun)
   542  	return err
   543  }
   544  
   545  func NewUploadParams() UploadParams {
   546  	return UploadParams{ArtifactoryCommonParams: &utils.ArtifactoryCommonParams{}, MinChecksumDeploy: 10240}
   547  }
   548  
   549  func getVcsProps(path string, vcsCache *clientutils.VcsCache) (string, error) {
   550  	path, err := filepath.Abs(path)
   551  	if err != nil {
   552  		return "", errorutils.CheckError(err)
   553  	}
   554  	props := ""
   555  	revision, url, err := vcsCache.GetVcsDetails(filepath.Dir(path))
   556  	if err != nil {
   557  		return "", errorutils.CheckError(err)
   558  	}
   559  	if revision != "" {
   560  		props += ";vcs.revision=" + revision
   561  	}
   562  	if url != "" {
   563  		props += ";vcs.url=" + url
   564  	}
   565  	return props, nil
   566  }