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

Support for Distribution path mapping with AQL #861

Merged
merged 4 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions artifactory/services/utils/specutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ type Aql struct {
ItemsFind string `json:"items.find"`
}

type PathMapping struct {
Input string `json:"input"`
Output string `json:"output"`
}

type CommonParams struct {
Aql Aql
PathMapping PathMapping
Pattern string
Exclusions []string
Target string
Expand Down
12 changes: 9 additions & 3 deletions distribution/services/utils/distributionutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,23 @@ func NewReleaseBundleParams(name, version string) ReleaseBundleParams {

func CreateBundleBody(releaseBundleParams ReleaseBundleParams, dryRun bool) (*ReleaseBundleBody, error) {
var bundleQueries []BundleQuery
var pathMappings []rtUtils.PathMapping

// Create release bundle queries
for _, specFile := range releaseBundleParams.SpecFiles {
// Create path mapping
if specFile.GetSpecType() == rtUtils.AQL {
pathMappings = distribution.CreatePathMappings(specFile.PathMapping.Input, specFile.PathMapping.Output)
} else {
pathMappings = distribution.CreatePathMappingsFromPatternAndTarget(specFile.Pattern, specFile.Target)
}

// Create AQL
aql, err := createAql(specFile)
if err != nil {
return nil, err
}

// Create path mapping
pathMappings := distribution.CreatePathMappings(specFile.Pattern, specFile.Target)

// Create added properties
addedProps := createAddedProps(specFile)

Expand Down
12 changes: 7 additions & 5 deletions distribution/services/utils/releasebundlebody.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package utils

import "github.com/jfrog/jfrog-client-go/utils/distribution"
import (
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
)

// REST body for create and update a release bundle
type ReleaseBundleBody struct {
Expand All @@ -22,10 +24,10 @@ type BundleSpec struct {
}

type BundleQuery struct {
QueryName string `json:"query_name,omitempty"`
Aql string `json:"aql,omitempty"`
PathMappings []distribution.PathMapping `json:"mappings,omitempty"`
AddedProps []AddedProps `json:"added_props,omitempty"`
QueryName string `json:"query_name,omitempty"`
Aql string `json:"aql,omitempty"`
PathMappings []utils.PathMapping `json:"mappings,omitempty"`
AddedProps []AddedProps `json:"added_props,omitempty"`
}

type AddedProps struct {
Expand Down
5 changes: 3 additions & 2 deletions lifecycle/services/distribute.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package services

import (
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/jfrog/jfrog-client-go/auth"
"github.com/jfrog/jfrog-client-go/http/jfroghttpclient"
"github.com/jfrog/jfrog-client-go/utils/distribution"
Expand Down Expand Up @@ -62,7 +63,7 @@ func (dr *DistributeReleaseBundleService) createDistributeBody() ReleaseBundleDi
return ReleaseBundleDistributeBody{
ReleaseBundleDistributeV1Body: distribution.CreateDistributeV1Body(dr.DistributeParams, dr.DryRun, dr.AutoCreateRepo),
Modifications: Modifications{
PathMappings: distribution.CreatePathMappings(dr.Pattern, dr.Target),
PathMappings: distribution.CreatePathMappingsFromPatternAndTarget(dr.Pattern, dr.Target),
},
}
}
Expand All @@ -73,7 +74,7 @@ type ReleaseBundleDistributeBody struct {
}

type Modifications struct {
PathMappings []distribution.PathMapping `json:"mappings"`
PathMappings []utils.PathMapping `json:"mappings"`
}

type PathMapping struct {
Expand Down
137 changes: 106 additions & 31 deletions tests/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
Expand Down Expand Up @@ -52,7 +53,9 @@ func TestDistributionServices(t *testing.T) {
t.Run("createSignDistributeDelete", createSignDistributeDelete)
t.Run("createSignSyncDistributeDelete", createSignSyncDistributeDelete)
t.Run("createDistributeMapping", createDistributeMapping)
t.Run("createDistributeMappingPlaceholder", createDistributeMappingPlaceholder)
t.Run("createDistributeMappingFromPatternAndTarget", createDistributeMappingFromPatternAndTarget)
t.Run("createDistributeMappingWithPlaceholder", createDistributeMappingWithPlaceholder)
t.Run("createDistributeMappingFromPatternAndTargetWithPlaceholder", createDistributeMappingFromPatternAndTargetWithPlaceholder)

artifactoryCleanup(t)
deleteGpgKeys(t)
Expand Down Expand Up @@ -123,6 +126,9 @@ func createUpdate(t *testing.T) {
// Verify was not created.
getLocalBundle(t, bundleName, false)

// Redefine specFiles to create params from scratch
createBundleParams.SpecFiles[0] = &utils.CommonParams{Pattern: getRtTargetRepo() + "b.in"}

// Create unsigned release bundle
summary, err := testsBundleCreateService.CreateReleaseBundle(createBundleParams)
if !assert.NoError(t, err) {
Expand All @@ -147,6 +153,9 @@ func createUpdate(t *testing.T) {
// Verify the release bundle was not updated.
assertCreatedLocalBundle(t, bundleName, createBundleParams)

// Redefine specFiles to create params from scratch
updateBundleParams.SpecFiles[0] = &utils.CommonParams{Pattern: getRtTargetRepo() + "test/a.in"}

summary, err = testsBundleUpdateService.UpdateReleaseBundle(updateBundleParams)
if !assert.NoError(t, err) {
return
Expand Down Expand Up @@ -357,12 +366,21 @@ func createDistributeMapping(t *testing.T) {

// Create release bundle with path mapping from <RtTargetRepo>/b.in to <RtTargetRepo>/b.out
createBundleParams := services.NewCreateReleaseBundleParams(bundleName, bundleVersion)
createBundleParams.SpecFiles = []*utils.CommonParams{{Pattern: getRtTargetRepo() + "b.in", Target: getRtTargetRepo() + "b.out"}}
createBundleParams.SpecFiles = []*utils.CommonParams{
{
Aql: utils.Aql{
ItemsFind: "{\"$or\":[{\"$and\":[{\"repo\":{\"$match\":\"" + strings.TrimSuffix(getRtTargetRepo(), "/") + "\"},\"name\":{\"$match\":\"b.in\"}}]}]}",
},
PathMapping: utils.PathMapping{
Input: getRtTargetRepo() + "b.in",
Output: getRtTargetRepo() + "b.out",
},
},
}
createBundleParams.SignImmediately = true
summary, err := testsBundleCreateService.CreateReleaseBundle(createBundleParams)
if !assert.NoError(t, err) {
return
}
assert.NoError(t, err)

defer deleteRemoteAndLocalBundle(t, bundleName)
assert.NotNil(t, summary)
verifyValidSha256(t, summary.GetSha256())
Expand All @@ -377,51 +395,85 @@ func createDistributeMapping(t *testing.T) {
err = testsBundleDistributeService.Distribute()
assert.NoError(t, err)

// Distribute release bundle
assertReleaseBundleDistribution(t, bundleName)

// Make sure <RtTargetRepo>/b.out does exist in Artifactory
searchParams := artifactoryServices.NewSearchParams()
searchParams.Pattern = getRtTargetRepo() + "b.out"
reader, err := testsSearchService.Search(searchParams)
assertFileExistsInArtifactory(t, getRtTargetRepo()+"b.out")
}

func createDistributeMappingFromPatternAndTarget(t *testing.T) {
bundleName := initRemoteDistributionTest(t, "client-test-bundle-"+getRunId())

// Create release bundle with path mapping from <RtTargetRepo>/b.in to <RtTargetRepo>/b.out
createBundleParams := services.NewCreateReleaseBundleParams(bundleName, bundleVersion)
createBundleParams.SpecFiles = []*utils.CommonParams{{Pattern: getRtTargetRepo() + "b.in", Target: getRtTargetRepo() + "b.out"}}
createBundleParams.SignImmediately = true
summary, err := testsBundleCreateService.CreateReleaseBundle(createBundleParams)
assert.NoError(t, err)
readerCloseAndAssert(t, reader)
length, err := reader.Length()

defer deleteRemoteAndLocalBundle(t, bundleName)
assert.NotNil(t, summary)
verifyValidSha256(t, summary.GetSha256())

// Distribute release bundle
assertReleaseBundleDistribution(t, bundleName)

// Make sure <RtTargetRepo>/b.out does exist in Artifactory
assertFileExistsInArtifactory(t, getRtTargetRepo()+"b.out")
}

func createDistributeMappingWithPlaceholder(t *testing.T) {
bundleName := initRemoteDistributionTest(t, "client-test-bundle-"+getRunId())

// Create release bundle with path mapping from <RtTargetRepo>/b.in to <RtTargetRepo>/b.out
createBundleParams := services.NewCreateReleaseBundleParams(bundleName, bundleVersion)
createBundleParams.SpecFiles = []*utils.CommonParams{
{
Aql: utils.Aql{
ItemsFind: "{\"$or\":[{\"$and\":[{\"repo\":{\"$match\":\"" + strings.TrimSuffix(getRtTargetRepo(), "/") + "\"},\"name\":{\"$match\":\"*.in\"}}]}]}",
},
PathMapping: utils.PathMapping{
Input: "(" + getRtTargetRepo() + ")" + "(.*).in",
Output: "$1$2.out",
},
},
}

createBundleParams.SignImmediately = true
summary, err := testsBundleCreateService.CreateReleaseBundle(createBundleParams)
assert.NoError(t, err)
assert.Equal(t, 1, length)

defer deleteRemoteAndLocalBundle(t, bundleName)
assert.NotNil(t, summary)
verifyValidSha256(t, summary.GetSha256())

// Distribute release bundle
assertReleaseBundleDistribution(t, bundleName)

// Make sure <RtTargetRepo>/b.out does exist in Artifactory
assertFileExistsInArtifactory(t, getRtTargetRepo()+"b.out")
}

func createDistributeMappingPlaceholder(t *testing.T) {
func createDistributeMappingFromPatternAndTargetWithPlaceholder(t *testing.T) {
bundleName := initRemoteDistributionTest(t, "client-test-bundle-"+getRunId())

// Create release bundle with path mapping from <RtTargetRepo>/b.in to <RtTargetRepo>/b.out
createBundleParams := services.NewCreateReleaseBundleParams(bundleName, bundleVersion)
createBundleParams.SpecFiles = []*utils.CommonParams{{Pattern: "(" + getRtTargetRepo() + ")" + "(*).in", Target: "{1}{2}.out"}}
createBundleParams.SignImmediately = true
summary, err := testsBundleCreateService.CreateReleaseBundle(createBundleParams)
if !assert.NoError(t, err) {
return
}
assert.NoError(t, err)

defer deleteRemoteAndLocalBundle(t, bundleName)
assert.NotNil(t, summary)
verifyValidSha256(t, summary.GetSha256())

// Distribute release bundle
distributeBundleParams := distribution.NewDistributeReleaseBundleParams(bundleName, bundleVersion)
distributeBundleParams.DistributionRules = []*distribution.DistributionCommonParams{{SiteName: "*"}}
testsBundleDistributeService.Sync = true
// On distribution with path mapping, the target repository cannot be auto-created
testsBundleDistributeService.AutoCreateRepo = false
testsBundleDistributeService.DistributeParams = distributeBundleParams
err = testsBundleDistributeService.Distribute()
assert.NoError(t, err)
assertReleaseBundleDistribution(t, bundleName)

// Make sure <RtTargetRepo>/b.out does exist in Artifactory
searchParams := artifactoryServices.NewSearchParams()
searchParams.Pattern = getRtTargetRepo() + "b.out"
reader, err := testsSearchService.Search(searchParams)
assert.NoError(t, err)
readerCloseAndAssert(t, reader)
length, err := reader.Length()
assert.NoError(t, err)
assert.Equal(t, 1, length)
assertFileExistsInArtifactory(t, getRtTargetRepo()+"b.out")
}

// Send GPG keys to Distribution and Artifactory to allow signing of release bundles
Expand Down Expand Up @@ -567,3 +619,26 @@ func deleteRemoteAndLocalBundle(t *testing.T, bundleName string) {
artifactoryCleanup(t)
assert.NoError(t, err)
}

func assertFileExistsInArtifactory(t *testing.T, filePath string) {
searchParams := artifactoryServices.NewSearchParams()
searchParams.Pattern = filePath
reader, err := testsSearchService.Search(searchParams)
assert.NoError(t, err)
readerCloseAndAssert(t, reader)
length, err := reader.Length()
assert.NoError(t, err)
assert.Equal(t, 1, length)
}

func assertReleaseBundleDistribution(t *testing.T, bundleName string) {
// Distribute release bundle
distributeBundleParams := distribution.NewDistributeReleaseBundleParams(bundleName, bundleVersion)
distributeBundleParams.DistributionRules = []*distribution.DistributionCommonParams{{SiteName: "*"}}
testsBundleDistributeService.Sync = true
// On distribution with path mapping, the target repository cannot be auto-created
testsBundleDistributeService.AutoCreateRepo = false
testsBundleDistributeService.DistributeParams = distributeBundleParams
err := testsBundleDistributeService.Distribute()
assert.NoError(t, err)
}
22 changes: 15 additions & 7 deletions utils/distribution/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ package distribution

import (
"github.com/jfrog/gofrog/stringutils"
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"regexp"
)

var fileSpecCaptureGroup = regexp.MustCompile(`({\d})`)

// Create the path mapping from the input spec
func CreatePathMappings(pattern, target string) []PathMapping {
// Create the path mapping from the input spec which includes pattern and target
func CreatePathMappingsFromPatternAndTarget(pattern, target string) []utils.PathMapping {
if len(target) == 0 {
return []PathMapping{}
return []utils.PathMapping{}
}

// Convert the file spec pattern and target to match the path mapping input and output specifications, respectfully.
return []PathMapping{{
return []utils.PathMapping{{
// The file spec pattern is wildcard based. Convert it to Regex:
Input: stringutils.WildcardPatternToRegExp(pattern),
// The file spec target contain placeholders-style matching groups, like {1}.
Expand All @@ -26,7 +27,14 @@ func CreatePathMappings(pattern, target string) []PathMapping {
}}
}

type PathMapping struct {
Input string `json:"input"`
Output string `json:"output"`
// Create the path mapping from the input spec
func CreatePathMappings(input, output string) []utils.PathMapping {
if len(input) == 0 || len(output) == 0 {
return []utils.PathMapping{}
}

return []utils.PathMapping{{
Input: input,
Output: output,
}}
}
38 changes: 36 additions & 2 deletions utils/distribution/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"testing"
)

func TestCreatePathMappings(t *testing.T) {
func TestCreatePathMappingsFromPatternAndTarget(t *testing.T) {
tests := []struct {
specPattern string
specTarget string
Expand All @@ -27,7 +27,41 @@ func TestCreatePathMappings(t *testing.T) {
for _, test := range tests {
t.Run(test.specPattern, func(t *testing.T) {
specFile := &utils.CommonParams{Pattern: test.specPattern, Target: test.specTarget}
pathMappings := CreatePathMappings(specFile.Pattern, specFile.Target)
pathMappings := CreatePathMappingsFromPatternAndTarget(specFile.Pattern, specFile.Target)
if test.expectedMappingInput == "" {
assert.Empty(t, pathMappings)
return
}
assert.Len(t, pathMappings, 1)
actualPathMapping := pathMappings[0]
assert.Equal(t, test.expectedMappingInput, actualPathMapping.Input)
assert.Equal(t, test.expectedMappingOutput, actualPathMapping.Output)
})
}
}

func TestCreatePathMappings(t *testing.T) {
tests := []struct {
specInput string
specOutput string
expectedMappingInput string
expectedMappingOutput string
}{
{"", "", "", ""},
{"repo/path/file.in", "", "", ""},
{"", "repo/path/file.out", "", ""},
{"a/b/c", "a/b/x", "a/b/c", "a/b/x"},
}

for _, test := range tests {
t.Run(test.specInput, func(t *testing.T) {
specFile := &utils.CommonParams{
PathMapping: utils.PathMapping{
Input: test.specInput,
Output: test.specOutput,
},
}
pathMappings := CreatePathMappings(specFile.PathMapping.Input, specFile.PathMapping.Output)
if test.expectedMappingInput == "" {
assert.Empty(t, pathMappings)
return
Expand Down
Loading