Skip to content

Commit

Permalink
Merge branch 'dev' of https://github.com/jfrog/jfrog-client-go into r…
Browse files Browse the repository at this point in the history
…bv2-ds-proj

# Conflicts:
#	lifecycle/services/delete.go
  • Loading branch information
RobiNino committed Mar 18, 2024
2 parents b6d8c52 + e419c2a commit 9da34ad
Show file tree
Hide file tree
Showing 19 changed files with 462 additions and 45 deletions.
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
- [Deleting a Group](#deleting-a-group)
- [Generating Full System Export](#generating-full-system-export)
- [Getting Info of a Folder in Artifactory](#getting-info-of-a-folder-in-artifactory)
- [Getting Info of a File in Artifactory](#getting-info-of-a-file-in-artifactory)
- [Getting a listing of files and folders within a folder in Artifactory](#getting-a-listing-of-files-and-folders-within-a-folder-in-artifactory)
- [Getting Storage Summary Info of Artifactory](#getting-storage-summary-info-of-artifactory)
- [Triggering Storage Info Recalculation in Artifactory](#triggering-storage-info-recalculation-in-artifactory)
Expand Down Expand Up @@ -207,8 +208,10 @@
- [Promoting a Release Bundle](#promoting-a-release-bundle)
- [Get Release Bundle Creation Status](#get-release-bundle-creation-status)
- [Get Release Bundle Promotion Status](#get-release-bundle-promotion-status)
- [Get Release Bundle Promotions](#get-release-bundle-promotions)
- [Distribute Release Bundle](#distribute-release-bundle)
- [Delete Release Bundle](#delete-release-bundle)
- [Delete Release Bundle Version](#delete-release-bundle-version)
- [Delete Release Bundle Version Promotion](#delete-release-bundle-version-promotion)
- [Remote Delete Release Bundle](#remote-delete-release-bundle)

## General
Expand Down Expand Up @@ -1353,6 +1356,12 @@ err := serviceManager.Export(params)
serviceManager.FolderInfo("repo/path/")
```

#### Getting Info of a File in Artifactory

```go
serviceManager.FileInfo("repo/path/file")
```

#### Getting a listing of files and folders within a folder in Artifactory

```go
Expand Down Expand Up @@ -2507,6 +2516,24 @@ projectKey := "default"
resp, err := serviceManager.GetReleaseBundlePromotionStatus(rbDetails, projectKey, createdMillis, sync)
```
#### Get Release Bundle Promotions
```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}

optionalQueryParams := lifecycle.GetPromotionsOptionalQueryParams{
Include: "MSG",
Offset: 1,
Limit: 10,
FilterBy: "DEV",
OrderBy: "created",
OrderAsc: true,
ProjectKey: "default",
}

resp, err := serviceManager.GetReleaseBundleVersionPromotions(rbDetails, optionalQueryParams)
```
#### Get Release Bundle Specification
```go
Expand Down Expand Up @@ -2543,15 +2570,28 @@ dsParams := DistributeReleaseBundleParams{
resp, err := serviceManager.DistributeReleaseBundle(rbDetails, dsParams)
```
#### Delete Release Bundle
#### Delete Release Bundle Version
```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}
queryParams := CommonOptionalQueryParams{}
queryParams.ProjectKey = "project"
queryParams.Async = true

resp, err := serviceManager.DeleteReleaseBundle(rbDetails, queryParams)
resp, err := serviceManager.DeleteReleaseBundleVersion(rbDetails, queryParams)
```
#### Delete Release Bundle Version Promotion
```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}

queryParams := CommonOptionalQueryParams{}
queryParams.ProjectKey = "project"
queryParams.Async = true

created := "1708612052952"
resp, err := serviceManager.DeleteReleaseBundleVersionPromotion(rbDetails, queryParams, created)
```
#### Remote Delete Release Bundle
Expand Down
5 changes: 5 additions & 0 deletions artifactory/emptymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type ArtifactoryServicesManager interface {
TriggerFederatedRepositoryFullSyncMirror(repoKey string, mirrorUrl string) error
Export(params services.ExportParams) error
FolderInfo(relativePath string) (*utils.FolderInfo, error)
FileInfo(relativePath string) (*utils.FileInfo, error)
FileList(relativePath string, optionalParams utils.FileListParams) (*utils.FileListResponse, error)
GetStorageInfo() (*utils.StorageInfo, error)
CalculateStorageInfo() error
Expand Down Expand Up @@ -448,6 +449,10 @@ func (esm *EmptyArtifactoryServicesManager) FolderInfo(string) (*utils.FolderInf
panic("Failed: Method is not implemented")
}

func (esm *EmptyArtifactoryServicesManager) FileInfo(string) (*utils.FileInfo, error) {
panic("Failed: Method is not implemented")
}

func (esm *EmptyArtifactoryServicesManager) FileList(string, utils.FileListParams) (*utils.FileListResponse, error) {
panic("Failed: Method is not implemented")
}
Expand Down
5 changes: 5 additions & 0 deletions artifactory/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,11 @@ func (sm *ArtifactoryServicesManagerImp) FolderInfo(relativePath string) (*utils
return storageService.FolderInfo(relativePath)
}

func (sm *ArtifactoryServicesManagerImp) FileInfo(relativePath string) (*utils.FileInfo, error) {
storageService := services.NewStorageService(sm.config.GetServiceDetails(), sm.client)
return storageService.FileInfo(relativePath)
}

func (sm *ArtifactoryServicesManagerImp) FileList(relativePath string, optionalParams utils.FileListParams) (*utils.FileListResponse, error) {
storageService := services.NewStorageService(sm.config.GetServiceDetails(), sm.client)
return storageService.FileList(relativePath, optionalParams)
Expand Down
2 changes: 2 additions & 0 deletions artifactory/services/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ func (ds *DeleteService) createFileHandlerFunc(result *utils.Result) fileDeleteH
}
log.Info(logMsgPrefix+"Deleting", resultItem.GetItemRelativePath())
if ds.DryRun {
// Mock success count on dry run
result.SuccessCount[threadId]++
return nil
}
httpClientsDetails := ds.GetArtifactoryDetails().CreateHttpClientDetails()
Expand Down
31 changes: 25 additions & 6 deletions artifactory/services/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,46 @@ func (s *StorageService) GetJfrogHttpClient() *jfroghttpclient.JfrogHttpClient {
return s.client
}

func (s *StorageService) FileInfo(relativePath string) (*utils.FileInfo, error) {
body, err := s.getPathInfo(relativePath)
if err != nil {
return nil, err
}

result := &utils.FileInfo{}
err = json.Unmarshal(body, result)
return result, errorutils.CheckError(err)
}

func (s *StorageService) FolderInfo(relativePath string) (*utils.FolderInfo, error) {
body, err := s.getPathInfo(relativePath)
if err != nil {
return nil, err
}

result := &utils.FolderInfo{}
err = json.Unmarshal(body, result)
return result, errorutils.CheckError(err)
}

func (s *StorageService) getPathInfo(relativePath string) ([]byte, error) {
client := s.GetJfrogHttpClient()
restAPI := path.Join(StorageRestApi, path.Clean(relativePath))
folderUrl, err := clientutils.BuildUrl(s.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string))
fullUrl, err := clientutils.BuildUrl(s.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string))
if err != nil {
return nil, err
}

httpClientsDetails := s.GetArtifactoryDetails().CreateHttpClientDetails()
resp, body, _, err := client.SendGet(folderUrl, true, &httpClientsDetails)
resp, body, _, err := client.SendGet(fullUrl, true, &httpClientsDetails)
if err != nil {
return nil, err
}
if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil {
return nil, err
}
log.Debug("Artifactory response:", resp.Status)

result := &utils.FolderInfo{}
err = json.Unmarshal(body, result)
return result, errorutils.CheckError(err)
return body, err
}

func (s *StorageService) FileList(relativePath string, optionalParams utils.FileListParams) (*utils.FileListResponse, error) {
Expand Down
16 changes: 11 additions & 5 deletions artifactory/services/utils/multipartupload.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ const (
aborted completionStatus = "ABORTED"

// API constants
uploadsApi = "/api/v1/uploads/"
artifactoryNodeIdHeader = "X-Artifactory-Node-Id"
uploadsApi = "/api/v1/uploads/"
routeToHeader = "X-JFrog-Route-To"
artifactoryNodeId = "X-Artifactory-Node-Id"

// Sizes and limits constants
MaxMultipartUploadFileSize = SizeTiB * 5
Expand Down Expand Up @@ -312,7 +313,7 @@ func (mu *MultipartUpload) completeAndPollForStatus(logMsgPrefix string, complet

func (mu *MultipartUpload) pollCompletionStatus(logMsgPrefix string, completionAttemptsLeft uint, sha1, nodeId string, multipartUploadClient *httputils.HttpClientDetails, progressReader ioutils.Progress) error {
multipartUploadClientWithNodeId := multipartUploadClient.Clone()
multipartUploadClientWithNodeId.Headers = map[string]string{artifactoryNodeIdHeader: nodeId}
multipartUploadClientWithNodeId.Headers = map[string]string{routeToHeader: nodeId}

lastMergeLog := time.Now()
pollingExecutor := &utils.RetryExecutor{
Expand Down Expand Up @@ -363,12 +364,17 @@ func (mu *MultipartUpload) completeMultipartUpload(logMsgPrefix, sha1 string, mu
return "", err
}
log.Debug("Artifactory response:", string(body), resp.Status)
return resp.Header.Get(artifactoryNodeIdHeader), errorutils.CheckResponseStatusWithBody(resp, body, http.StatusAccepted)
return resp.Header.Get(artifactoryNodeId), errorutils.CheckResponseStatusWithBody(resp, body, http.StatusAccepted)
}

func (mu *MultipartUpload) status(logMsgPrefix string, multipartUploadClientWithNodeId *httputils.HttpClientDetails) (status statusResponse, err error) {
url := fmt.Sprintf("%s%sstatus", mu.artifactoryUrl, uploadsApi)
resp, body, err := mu.client.GetHttpClient().SendPost(url, []byte{}, *multipartUploadClientWithNodeId, logMsgPrefix)
// If the Artifactory node returns a "Service unavailable" error (status 503), attempt to retry the upload completion process on a different node.
if resp != nil && resp.StatusCode == http.StatusServiceUnavailable {
unavailableNodeErr := fmt.Sprintf(logMsgPrefix + fmt.Sprintf("The Artifactory node ID '%s' is unavailable.", multipartUploadClientWithNodeId.Headers[routeToHeader]))
return statusResponse{Status: retryableError, Error: unavailableNodeErr}, nil
}
if err != nil {
return
}
Expand Down Expand Up @@ -421,7 +427,7 @@ func parseMultipartUploadStatus(status statusResponse) (shouldKeepPolling, shoul
return true, false, nil
case retryableError:
// Retryable error was received - stop polling and rerun the /complete API again
log.Warn("received error upon multipart upload completion process: '%s', retrying...", status.Error)
log.Warn(fmt.Printf("received error upon multipart upload completion process: '%s', retrying...", status.Error))
return false, true, nil
case finished, aborted:
// Upload finished or aborted
Expand Down
33 changes: 29 additions & 4 deletions artifactory/services/utils/multipartupload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func TestCompleteMultipartUpload(t *testing.T) {
assert.Equal(t, fmt.Sprintf("sha1=%s", sha1), r.URL.RawQuery)

// Add the "X-Artifactory-Node-Id" header to the response
w.Header().Add(artifactoryNodeIdHeader, nodeId)
w.Header().Add(artifactoryNodeId, nodeId)

// Send response 202 Accepted
w.WriteHeader(http.StatusAccepted)
Expand All @@ -211,8 +211,8 @@ func TestStatus(t *testing.T) {
// Check URL
assert.Equal(t, "/api/v1/uploads/status", r.URL.Path)

// Check "X-Artifactory-Node-Id" header
assert.Equal(t, nodeId, r.Header.Get(artifactoryNodeIdHeader))
// Check "X-JFrog-Route-To" header
assert.Equal(t, nodeId, r.Header.Get(routeToHeader))

// Send response 200 OK
w.WriteHeader(http.StatusOK)
Expand All @@ -227,12 +227,37 @@ func TestStatus(t *testing.T) {
defer cleanUp()

// Execute status
clientDetails := &httputils.HttpClientDetails{Headers: map[string]string{artifactoryNodeIdHeader: nodeId}}
clientDetails := &httputils.HttpClientDetails{Headers: map[string]string{routeToHeader: nodeId}}
status, err := multipartUpload.status("", clientDetails)
assert.NoError(t, err)
assert.Equal(t, statusResponse{Status: finished, Progress: utils.Pointer(100)}, status)
}

func TestStatusServiceUnavailable(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check method
assert.Equal(t, http.MethodPost, r.Method)

// Check URL
assert.Equal(t, "/api/v1/uploads/status", r.URL.Path)

// Send response 503 Service unavailable
w.WriteHeader(http.StatusServiceUnavailable)
_, err := w.Write([]byte("Service unavailable"))
assert.NoError(t, err)
})

// Create mock multipart upload with server
multipartUpload, cleanUp := createMockMultipartUpload(t, handler)
defer cleanUp()

// Execute status
clientDetails := &httputils.HttpClientDetails{Headers: map[string]string{routeToHeader: nodeId}}
status, err := multipartUpload.status("", clientDetails)
assert.NoError(t, err)
assert.Equal(t, statusResponse{Status: retryableError, Error: "The Artifactory node ID 'nodeId' is unavailable."}, status)
}

func TestAbort(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check method
Expand Down
20 changes: 20 additions & 0 deletions artifactory/services/utils/storageutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ const (
SizeTiB int64 = 1 << 40
)

type FileInfo struct {
Uri string `json:"uri,omitempty"`
DownloadUri string `json:"downloadUri,omitempty"`
Repo string `json:"repo,omitempty"`
Path string `json:"path,omitempty"`
RemoteUrl string `json:"remoteUrl,omitempty"`
Created string `json:"created,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
LastModified string `json:"lastModified,omitempty"`
ModifiedBy string `json:"modifiedBy,omitempty"`
LastUpdated string `json:"lastUpdated,omitempty"`
Size string `json:"size,omitempty"`
MimeType string `json:"mimeType,omitempty"`
Checksums struct {
Sha1 string `json:"sha1,omitempty"`
Sha256 string `json:"sha256,omitempty"`
Md5 string `json:"md5,omitempty"`
} `json:"checksums,omitempty"`
}

type FolderInfo struct {
Uri string `json:"uri,omitempty"`
Repo string `json:"repo,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion distribution/services/distribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (dr *DistributeReleaseBundleV1Service) GetRestApi(name, version string) str
}

func (dr *DistributeReleaseBundleV1Service) GetDistributeBody() any {
return distribution.CreateDistributeV1Body(dr.DistributeParams, dr.DryRun, dr.AutoCreateRepo)
return distribution.CreateDistributeV1Body(dr.DistributeParams.DistributionRules, dr.DryRun, dr.AutoCreateRepo)
}

func (dr *DistributeReleaseBundleV1Service) GetDistributionParams() distribution.DistributionParams {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/gookit/color v1.5.4
github.com/jfrog/archiver/v3 v3.6.0
github.com/jfrog/build-info-go v1.9.23
github.com/jfrog/gofrog v1.6.0
github.com/jfrog/gofrog v1.6.3
github.com/stretchr/testify v1.8.4
github.com/xanzy/ssh-agent v0.3.3
golang.org/x/crypto v0.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F
github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI=
github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c h1:M1QiuCYGCYN1IiGyxogrLzfetYGkkhE2pgDh5K4Wo9A=
github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c/go.mod h1:QHcKuesY4MrBVBuEwwBz4uIsX6mwYuMEDV09ng4AvAU=
github.com/jfrog/gofrog v1.6.0 h1:jOwb37nHY2PnxePNFJ6e6279Pgkr3di05SbQQw47Mq8=
github.com/jfrog/gofrog v1.6.0/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg=
github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc=
github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
Expand Down
Loading

0 comments on commit 9da34ad

Please sign in to comment.