Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Artifactory Release Lifecycle Management - Add Import bundle function #921

Merged
merged 9 commits into from
Apr 8, 2024
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
- [Delete Release Bundle Version](#delete-release-bundle-version)
- [Delete Release Bundle Version Promotion](#delete-release-bundle-version-promotion)
- [Export Release Bundle](#export-release-bundle)
- [Import Release Bundle](#import-release-bundle)
- [Remote Delete Release Bundle](#remote-delete-release-bundle)

## General
Expand Down Expand Up @@ -2705,6 +2706,13 @@ modifictions:= []utils.PathMapping{{
res,err:= serviceManager.ExportReleaseBundle(rbDetails, modifications, queryParams)
```

#### Import Release Bundle Archive

```go
// Imports an exported release bundle archive
res,err:= serviceManager.releaseService.ImportReleaseBundle(filePath)
```

#### Delete Release Bundle Version

```go
Expand Down
5 changes: 5 additions & 0 deletions artifactory/emptymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type ArtifactoryServicesManager interface {
FileList(relativePath string, optionalParams utils.FileListParams) (*utils.FileListResponse, error)
GetStorageInfo() (*utils.StorageInfo, error)
CalculateStorageInfo() error
ImportReleaseBundle(string) error
}

// By using this struct, you have the option of overriding only some of the ArtifactoryServicesManager
Expand Down Expand Up @@ -465,6 +466,10 @@ func (esm *EmptyArtifactoryServicesManager) CalculateStorageInfo() error {
panic("Failed: Method is not implemented")
}

func (esm *EmptyArtifactoryServicesManager) ImportReleaseBundle(string) error {
panic("Failed: Method is not implemented")
}

// Compile time check of interface implementation.
// Since EmptyArtifactoryServicesManager can be used by tests external to this project, we want this project's tests to fail,
// if EmptyArtifactoryServicesManager stops implementing the ArtifactoryServicesManager interface.
Expand Down
5 changes: 5 additions & 0 deletions artifactory/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,8 @@ func (sm *ArtifactoryServicesManagerImp) CalculateStorageInfo() error {
storageService := services.NewStorageService(sm.config.GetServiceDetails(), sm.client)
return storageService.StorageInfoRefresh()
}

func (sm *ArtifactoryServicesManagerImp) ImportReleaseBundle(filePath string) error {
releaseService := services.NewReleaseService(sm.config.GetServiceDetails(), sm.client)
return releaseService.ImportReleaseBundle(filePath)
}
78 changes: 78 additions & 0 deletions artifactory/services/releasebundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package services

import (
"encoding/json"
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/jfrog/jfrog-client-go/auth"
"github.com/jfrog/jfrog-client-go/http/jfroghttpclient"
utils2 "github.com/jfrog/jfrog-client-go/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"net/http"
)

const (
conflictErrorMessage = "Bundle already exists"
ReleaseBundleImportRestApiEndpoint = "api/release/import/"
octetStream = "application/octet-stream"
)

type releaseService struct {
client *jfroghttpclient.JfrogHttpClient
ArtDetails auth.ServiceDetails
}

type ErrorResponseWithMessage struct {
Errors []ErrorDetail `json:"errors"`
}

type ErrorDetail struct {
Status int `json:"status"`
Message string `json:"message"`
}

func NewReleaseService(artDetails auth.ServiceDetails, client *jfroghttpclient.JfrogHttpClient) *releaseService {
return &releaseService{client: client, ArtDetails: artDetails}
}

func (rs *releaseService) GetJfrogHttpClient() *jfroghttpclient.JfrogHttpClient {
return rs.client
}

func (rs *releaseService) ImportReleaseBundle(filePath string) (err error) {
// Load desired file
content, err := fileutils.ReadFile(filePath)
if err != nil {
return
}
// Upload file
httpClientsDetails := rs.ArtDetails.CreateHttpClientDetails()

url := utils2.AddTrailingSlashIfNeeded(rs.ArtDetails.GetUrl() + ReleaseBundleImportRestApiEndpoint)

utils.SetContentType(octetStream, &httpClientsDetails.Headers)
var resp *http.Response
var body []byte
log.Info("Uploading archive...")
if resp, body, err = rs.client.SendPost(url, content, &httpClientsDetails); err != nil {
return
}
// When a release bundle already exists, the API returns 400.
// Check the error message, and if it's a conflict, don't fail the operation.
if resp.StatusCode == http.StatusBadRequest {
response := ErrorResponseWithMessage{}
if err = json.Unmarshal(body, &response); err != nil {
return
}
if response.Errors[0].Message == conflictErrorMessage {
log.Warn("Bundle already exists, did not upload a new bundle")
return
}
}
if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusAccepted); err != nil {
return
}
log.Info("Release Bundle Imported Successfully")
return
}
52 changes: 52 additions & 0 deletions tests/releasebundle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package tests

import (
"github.com/jfrog/jfrog-client-go/artifactory"
artifactoryAuth "github.com/jfrog/jfrog-client-go/artifactory/auth"
"github.com/jfrog/jfrog-client-go/artifactory/services"
"github.com/jfrog/jfrog-client-go/config"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)

func TestImportReleaseBundle(t *testing.T) {
mockServer, rbService := createMockServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/"+services.ReleaseBundleImportRestApiEndpoint {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte(`
{
"errors" : [ {
"status" : 400,
"message" : "Bundle already exists"
} ]
}
`))
assert.NoError(t, err)
}
})
defer mockServer.Close()
err := rbService.ImportReleaseBundle("releasebundle_test.go")
assert.NoError(t, err)
}

func createMockServer(t *testing.T, testHandler http.HandlerFunc) (*httptest.Server, artifactory.ArtifactoryServicesManager) {
testServer := httptest.NewServer(testHandler)

rtDetails := artifactoryAuth.NewArtifactoryDetails()
rtDetails.SetUrl(testServer.URL + "/")

serviceConfig, err := config.NewConfigBuilder().
SetServiceDetails(rtDetails).
SetDryRun(false).
Build()

if err != nil {
t.Error(err)
}

artService, err := artifactory.New(serviceConfig)
assert.NoError(t, err)
return testServer, artService
}
Loading