diff --git a/go.mod b/go.mod index 8249d9934..4ab4625e5 100644 --- a/go.mod +++ b/go.mod @@ -19,12 +19,12 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260130140656-2d0d5593fccf github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260127112223-c5078af84b5a - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260129054930-035e3ed7e30d + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260203090305-f5f8aed71eaf github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260128134755-624b659a398e github.com/jfrog/jfrog-cli-evidence v0.8.3-0.20260125120022-1c6f4f382dbb github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20251205121610-171eb9b0000e github.com/jfrog/jfrog-cli-security v1.26.0 - github.com/jfrog/jfrog-client-go v1.55.1-0.20260128141727-bd34d78a3e48 + github.com/jfrog/jfrog-client-go v1.55.1-0.20260202100450-1b20bddfdb3f github.com/jszwec/csvutil v1.10.0 github.com/manifoldco/promptui v0.9.0 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index 01e1826fb..e92dc9886 100644 --- a/go.sum +++ b/go.sum @@ -1218,8 +1218,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260127112223-c5078af84b5a h1:Yvl7XuMoI1cXk3jzB1oWcZvFy5K3aUAIin9o5GRaqzE= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260127112223-c5078af84b5a/go.mod h1:xum2HquWO5uExa/A7MQs3TgJJVEeoqTR+6Z4mfBr1Xw= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260129054930-035e3ed7e30d h1:jyBD9kqAL8eHerZvGmxiTZECA+sYuH3TpodGnnpnPSs= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260129054930-035e3ed7e30d/go.mod h1:ANFZOB4AX+Voo24l1BO8bVvN76m3ZViR1QaK5u3QDgE= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260203090305-f5f8aed71eaf h1:3W7qIxrkJkC1K0vB/By3fVyCkU3qgY9SdHQs7o5RNRo= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260203090305-f5f8aed71eaf/go.mod h1:DkLAN+AvZ4v6jcuUStufi9DuhAS1dvSRJhILJyRa9kg= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260128134755-624b659a398e h1:jGtYjWMMfCINDl1JbRtvAijtVwD+j+yvR5BuYMZvRrA= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260128134755-624b659a398e/go.mod h1:+Hnaikp/xCSPD/q7txxRy4Zc0wzjW/usrCSf+6uONSQ= github.com/jfrog/jfrog-cli-evidence v0.8.3-0.20260125120022-1c6f4f382dbb h1:4nGzxRxVcXxkFg95jr+zMjESisZGqSYecXfHsA37rpA= @@ -1228,8 +1228,8 @@ github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20251205121610-171eb9b000 github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20251205121610-171eb9b0000e/go.mod h1:qbu4iqBST9x8LgD8HhzUm91iOB3vHqtoGmaxOnmw0ok= github.com/jfrog/jfrog-cli-security v1.26.0 h1:FcLshS1Ahm0++nV5q7UluFTCVRxH2wEIbqO7ZBag++I= github.com/jfrog/jfrog-cli-security v1.26.0/go.mod h1:r9E0BdlNy6mq6gkRGslZRZaYe6WeGhLkpUm8+oEUOvU= -github.com/jfrog/jfrog-client-go v1.55.1-0.20260128141727-bd34d78a3e48 h1:HY4b9DsUe0Lox1rScYHKmfIZY9PQvy0kfkOefqc/QdQ= -github.com/jfrog/jfrog-client-go v1.55.1-0.20260128141727-bd34d78a3e48/go.mod h1:sCE06+GngPoyrGO0c+vmhgMoVSP83UMNiZnIuNPzU8U= +github.com/jfrog/jfrog-client-go v1.55.1-0.20260202100450-1b20bddfdb3f h1:A/5DenqKB4zv9jWhG777/UwhoGL8hCzIndKj0sJ7QSM= +github.com/jfrog/jfrog-client-go v1.55.1-0.20260202100450-1b20bddfdb3f/go.mod h1:sCE06+GngPoyrGO0c+vmhgMoVSP83UMNiZnIuNPzU8U= github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= diff --git a/lifecycle_test.go b/lifecycle_test.go index 3319736b4..e61e76aa2 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -38,6 +38,7 @@ const ( artifactoryLifecycleMinVersion = "7.68.3" signingKeyOptionalArtifactoryMinVersion = "7.104.1" promotionTypeFlagArtifactoryMinVersion = "7.106.1" + draftBundleArtifactoryMinVersion = "7.136.0" gpgKeyPairName = "lc-tests-key-pair" lcTestdataPath = "lifecycle" releaseBundlesSpec = "release-bundles-spec.json" @@ -167,7 +168,7 @@ func TestReleaseBundleCreationFromMultiBundlesUsingCommandFlagWithProject(t *tes defer deleteBuilds() // Create first release bundle from builds with project - createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, tests.ProjectKey, true, true) + createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, tests.ProjectKey, true, true, false) defer deleteReleaseBundleWithProject(t, lcManager, tests.LcRbName1, number1, tests.ProjectKey) assertStatusCompletedWithProject(t, lcManager, tests.LcRbName1, number1, "", tests.ProjectKey) @@ -177,7 +178,7 @@ func TestReleaseBundleCreationFromMultiBundlesUsingCommandFlagWithProject(t *tes assert.True(t, isExist, "Release bundle %s/%s should exist in project %s", tests.LcRbName1, number1, tests.ProjectKey) // Create second release bundle from builds with project - createRbWithFlags(t, "", "", tests.LcBuildName2, number2, tests.LcRbName2, number2, tests.ProjectKey, true, true) + createRbWithFlags(t, "", "", tests.LcBuildName2, number2, tests.LcRbName2, number2, tests.ProjectKey, true, true, false) defer deleteReleaseBundleWithProject(t, lcManager, tests.LcRbName2, number2, tests.ProjectKey) assertStatusCompletedWithProject(t, lcManager, tests.LcRbName2, number2, "", tests.ProjectKey) @@ -489,6 +490,85 @@ func TestPromoteReleaseBundleWithPromotionTypeFlag(t *testing.T) { assertStatusCompleted(t, lcManager, tests.LcRbName1, number1, "") } +func TestReleaseBundleCreationWithDraftFlagFromSpec(t *testing.T) { + cleanCallback := initLifecycleTest(t, draftBundleArtifactoryMinVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + // Create draft bundle from spec + createRbFromSpecWithDraft(t, tests.LifecycleBuilds12, tests.LcRbName1, number1, true, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + assertStatusDraft(t, lcManager, tests.LcRbName1, number1) +} + +func TestReleaseBundleCreationWithDraftFlagFromFlags(t *testing.T) { + cleanCallback := initLifecycleTest(t, draftBundleArtifactoryMinVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + // Create draft bundle using build-name/build-number flags + createRbWithFlags(t, "", "", tests.LcBuildName2, number2, tests.LcRbName2, number2, "default", true, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) + assertStatusDraft(t, lcManager, tests.LcRbName2, number2) +} + +func TestReleaseBundleUpdateWithSpec(t *testing.T) { + cleanCallback := initLifecycleTest(t, draftBundleArtifactoryMinVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + // Create a draft bundle from build 1 + createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, "default", true, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + assertStatusDraft(t, lcManager, tests.LcRbName1, number1) + assertRbArtifacts(t, lcManager, tests.LcRbName1, number1, tests.GetExpectedLifecycleBuild1Artifacts()) + + // Update the draft bundle by adding build 3 (which includes dependencies) using spec file + updateSpecFile, err := tests.CreateSpec(tests.LifecycleBuilds3) + assert.NoError(t, err) + updateRbWithFlags(t, updateSpecFile, tests.LcRbName1, number1, "default", "", true) + + // Verify the bundle is still in DRAFT status after update + assertStatusDraft(t, lcManager, tests.LcRbName1, number1) + + // Verify the bundle now contains artifacts from both build 1 and build 3 + assertRbArtifacts(t, lcManager, tests.LcRbName1, number1, tests.GetExpectedLifecycleUpdateArtifacts()) +} + +func TestReleaseBundleUpdateWithFlags(t *testing.T) { + cleanCallback := initLifecycleTest(t, draftBundleArtifactoryMinVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + // Create a draft bundle from build 2 + createRbWithFlags(t, "", "", tests.LcBuildName2, number2, tests.LcRbName2, number2, "default", true, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) + assertStatusDraft(t, lcManager, tests.LcRbName2, number2) + assertRbArtifacts(t, lcManager, tests.LcRbName2, number2, tests.GetExpectedLifecycleBuild2Artifacts()) + + // Update draft using --source-type-builds flag to add build 3 + sourceBuildsFlag := fmt.Sprintf("name=%s,id=%s,include-deps=true", tests.LcBuildName3, number3) + updateRbWithFlags(t, "", tests.LcRbName2, number2, "default", sourceBuildsFlag, true) + + // Verify the bundle is still in DRAFT status after update + assertStatusDraft(t, lcManager, tests.LcRbName2, number2) + + // Verify the bundle now contains artifacts from both build 2 and build 3 + assertRbArtifacts(t, lcManager, tests.LcRbName2, number2, tests.GetExpectedLifecycleUpdateBuild2Artifacts()) +} + /*func deleteExportedReleaseBundle(t *testing.T, rbName string) { assert.NoError(t, os.RemoveAll(rbName)) }*/ @@ -524,13 +604,19 @@ func uploadBuildsWithProject(t *testing.T) func() { func createRbBackwardCompatible(t *testing.T, specName, sourceOption, rbName, rbVersion string, sync bool) { specFile, err := getSpecFile(specName) assert.NoError(t, err) - createRbWithFlags(t, specFile, sourceOption, "", "", rbName, rbVersion, "", sync, false) + createRbWithFlags(t, specFile, sourceOption, "", "", rbName, rbVersion, "", sync, false, false) } func createRbFromSpec(t *testing.T, specName, rbName, rbVersion string, sync bool, withoutSigningKey bool) { specFile, err := tests.CreateSpec(specName) assert.NoError(t, err) - createRbWithFlags(t, specFile, "spec", "", "", rbName, rbVersion, "", sync, withoutSigningKey) + createRbWithFlags(t, specFile, "spec", "", "", rbName, rbVersion, "", sync, withoutSigningKey, false) +} + +func createRbFromSpecWithDraft(t *testing.T, specName, rbName, rbVersion string, sync, withoutSigningKey, draft bool) { + specFile, err := tests.CreateSpec(specName) + assert.NoError(t, err) + createRbWithFlags(t, specFile, "spec", "", "", rbName, rbVersion, "", sync, withoutSigningKey, draft) } func TestCreateBundleWithoutSpec(t *testing.T) { @@ -542,11 +628,11 @@ func TestCreateBundleWithoutSpec(t *testing.T) { deleteBuilds := uploadBuilds(t) defer deleteBuilds() - createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, "default", false, false) + createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, "default", false, false, false) assertStatusCompleted(t, lcManager, tests.LcRbName1, number1, "") defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) - createRbWithFlags(t, "", "", tests.LcBuildName2, number2, tests.LcRbName2, number2, "default", false, true) + createRbWithFlags(t, "", "", tests.LcBuildName2, number2, tests.LcRbName2, number2, "default", false, true, false) assertStatusCompleted(t, lcManager, tests.LcRbName2, number2, "") defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) } @@ -566,13 +652,13 @@ func TestCreateBundleWithoutSpecAndWithProject(t *testing.T) { deleteBuilds := uploadBuildsWithProject(t) defer deleteBuilds() - createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, tests.ProjectKey, false, false) + createRbWithFlags(t, "", "", tests.LcBuildName1, number1, tests.LcRbName1, number1, tests.ProjectKey, false, false, false) assertStatusCompletedWithProject(t, lcManager, tests.LcRbName1, number1, "", tests.ProjectKey) defer deleteReleaseBundleWithProject(t, lcManager, tests.LcRbName1, number1, tests.ProjectKey) } func createRbWithFlags(t *testing.T, specFilePath, sourceOption, buildName, buildNumber, rbName, rbVersion, project string, - sync, withoutSigningKey bool, + sync, withoutSigningKey, draft bool, ) { argsAndOptions := []string{ "rbc", @@ -601,6 +687,35 @@ func createRbWithFlags(t *testing.T, specFilePath, sourceOption, buildName, buil argsAndOptions = append(argsAndOptions, getOption(cliutils.Project, project)) } + if draft { + argsAndOptions = append(argsAndOptions, getOption(cliutils.Draft, "true")) + } + + assert.NoError(t, lcCli.Exec(argsAndOptions...)) +} + +func updateRbWithFlags(t *testing.T, specFilePath, rbName, rbVersion, project, sourceTypeBuilds string, sync bool) { + argsAndOptions := []string{ + "rbu", + rbName, + rbVersion, + "--add", // Mandatory flag for rbu + } + + if specFilePath != "" { + argsAndOptions = append(argsAndOptions, getOption("spec", specFilePath)) + } + + if sourceTypeBuilds != "" { + argsAndOptions = append(argsAndOptions, getOption("source-type-builds", sourceTypeBuilds)) + } + + if project != "" { + argsAndOptions = append(argsAndOptions, getOption(cliutils.Project, project)) + } + + argsAndOptions = append(argsAndOptions, getOption(cliutils.Sync, strconv.FormatBool(sync))) + assert.NoError(t, lcCli.Exec(argsAndOptions...)) } @@ -665,6 +780,14 @@ func assertStatusCompleted(t *testing.T, lcManager *lifecycle.LifecycleServicesM assert.Equal(t, services.Completed, resp.Status) } +func assertStatusDraft(t *testing.T, lcManager *lifecycle.LifecycleServicesManager, rbName, rbVersion string) { + resp, err := getStatus(lcManager, rbName, rbVersion, "") + if !assert.NoError(t, err) { + return + } + assert.Equal(t, services.Draft, resp.Status) +} + // If createdMillis is provided, assert status for promotion. If blank, assert for creation. // //nolint:unparam // createdMillis parameter is kept for API consistency with existing tests @@ -1204,13 +1327,13 @@ func TestReleaseBundlesSearchVersions(t *testing.T) { } // Create release bundles with project - createRbWithFlags(t, "", "", tests.LcBuildName1, number1, projectRbName, projectVersionA, tests.ProjectKey, true, false) + createRbWithFlags(t, "", "", tests.LcBuildName1, number1, projectRbName, projectVersionA, tests.ProjectKey, true, false, false) defer deleteReleaseBundleWithProject(t, lcManager, projectRbName, projectVersionA, tests.ProjectKey) assertStatusCompletedWithProject(t, lcManager, projectRbName, projectVersionA, "", tests.ProjectKey) time.Sleep(1 * time.Second) - createRbWithFlags(t, "", "", tests.LcBuildName2, number2, projectRbName, projectVersionB, tests.ProjectKey, true, false) + createRbWithFlags(t, "", "", tests.LcBuildName2, number2, projectRbName, projectVersionB, tests.ProjectKey, true, false, false) defer deleteReleaseBundleWithProject(t, lcManager, projectRbName, projectVersionB, tests.ProjectKey) assertStatusCompletedWithProject(t, lcManager, projectRbName, projectVersionB, "", tests.ProjectKey) diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index 7c3e1ebc2..8d7768a0d 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -592,6 +592,7 @@ const ( SigningKey = "signing-key" setupRepo = repo PromotionType = "promotion-type" + Draft = "draft" ) var flagsMap = map[string]cli.Flag{ diff --git a/utils/tests/consts.go b/utils/tests/consts.go index ff9a25953..ea40bab8a 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -2157,6 +2157,46 @@ func GetExpectedLifecycleCreationByAql() []string { } } +// GetExpectedLifecycleBuild1Artifacts returns expected artifacts from build 1 only. +func GetExpectedLifecycleBuild1Artifacts() []string { + return []string{ + RtDevRepo + "/a1.in", + RtDevRepo + "/a2.in", + RtDevRepo + "/a3.in", + } +} + +// GetExpectedLifecycleUpdateArtifacts returns expected artifacts after updating a draft bundle +// that was created from build 1 (a1.in, a2.in, a3.in) with build 3 (dep-file as dependency). +func GetExpectedLifecycleUpdateArtifacts() []string { + return []string{ + RtDevRepo + "/a1.in", + RtDevRepo + "/a2.in", + RtDevRepo + "/a3.in", + RtDevRepo + "/dep-file", + } +} + +// GetExpectedLifecycleBuild2Artifacts returns expected artifacts from build 2 only. +func GetExpectedLifecycleBuild2Artifacts() []string { + return []string{ + RtDevRepo + "/b1.in", + RtDevRepo + "/b2.in", + RtDevRepo + "/b3.in", + } +} + +// GetExpectedLifecycleUpdateBuild2Artifacts returns expected artifacts after updating a draft bundle +// that was created from build 2 (b1.in, b2.in, b3.in) with build 3 (dep-file as dependency). +func GetExpectedLifecycleUpdateBuild2Artifacts() []string { + return []string{ + RtDevRepo + "/b1.in", + RtDevRepo + "/b2.in", + RtDevRepo + "/b3.in", + RtDevRepo + "/dep-file", + } +} + func GetExpectedLifecycleArtifacts() []string { return []string{ RtProdRepo1 + "/a1.in",