diff --git a/buildtools/cli.go b/buildtools/cli.go index 8607ae159..af52b14fa 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -1098,7 +1098,7 @@ func terraformCmd(c *cli.Context) error { case "publish", "p": return terraformPublishCmd(configFilePath, filteredArgs, c) default: - return errorutils.CheckErrorf("Terraform command:\"" + cmdName + "\" is not supported. " + cliutils.GetDocumentationMessage()) + return errorutils.CheckError(errors.New("Terraform command:\"" + cmdName + "\" is not supported. " + cliutils.GetDocumentationMessage())) } } @@ -1128,7 +1128,7 @@ func getProjectConfigPathOrThrow(projectType project.ProjectType, cmdName, confi return } if !exists { - return "", errorutils.CheckErrorf(getMissingConfigErrMsg(cmdName, configCmdName)) + return "", errorutils.CheckError(errors.New(getMissingConfigErrMsg(cmdName, configCmdName))) } return } @@ -1179,5 +1179,5 @@ func getTwineConfigPath() (configFilePath string, err error) { return } } - return "", errorutils.CheckErrorf(getMissingConfigErrMsg("twine", "pip-config OR pipenv-config")) + return "", errorutils.CheckError(errors.New(getMissingConfigErrMsg("twine", "pip-config OR pipenv-config"))) } diff --git a/config/cli.go b/config/cli.go index ef30aa869..128965ae3 100644 --- a/config/cli.go +++ b/config/cli.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "github.com/jfrog/jfrog-cli-core/v2/general/token" "os" @@ -300,7 +301,7 @@ func validatePathsExist(paths ...string) error { return err } if !exists { - return errorutils.CheckErrorf("file does not exit at " + path) + return errorutils.CheckError(errors.New("file does not exit at " + path)) } } } diff --git a/general/ai/cli.go b/general/ai/cli.go index 8ef6c0e71..fd4ca6c3b 100644 --- a/general/ai/cli.go +++ b/general/ai/cli.go @@ -175,12 +175,12 @@ func sendRestAPI(apiType ApiType, content interface{}) (response string, err err if err = errorutils.CheckResponseStatus(resp, http.StatusOK); err != nil { switch resp.StatusCode { case http.StatusInternalServerError: - err = errorutils.CheckErrorf("JFrog CLI-AI model endpoint is not available. Please try again later.") + err = errorutils.CheckError(errors.New("JFrog CLI-AI model endpoint is not available. Please try again later")) case http.StatusNotAcceptable: - err = errorutils.CheckErrorf("The system is currently handling multiple requests from other users\n" + - "Please try submitting your question again in a few minutes. Thank you for your patience!") + err = errorutils.CheckError(errors.New("The system is currently handling multiple requests from other users\n" + + "Please try submitting your question again in a few minutes. Thank you for your patience!")) default: - err = errorutils.CheckErrorf("JFrog CLI-AI server is not available. Please check your network or try again later:\n" + err.Error()) + err = errorutils.CheckError(errors.New("JFrog CLI-AI server is not available. Please check your network or try again later:\n" + err.Error())) } return } diff --git a/lifecycle_test.go b/lifecycle_test.go index 14153264b..1e90511ee 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -26,6 +26,8 @@ import ( "os" "path" "path/filepath" + "strconv" + "strings" "testing" "time" ) @@ -45,6 +47,7 @@ const ( artifactoryLifecycleSetTagMinVersion = "7.111.0" rbManifestName = "release-bundle.json.evd" releaseBundlesV2 = "release-bundles-v2" + minMultiSourcesArtifactoryVersion = "7.114.0" ) var ( @@ -94,6 +97,88 @@ func compareRbArtifacts(t *testing.T, actual services.ReleaseBundleSpecResponse, assert.ElementsMatch(t, actualArtifactsPaths, expected) } +func TestReleaseBundleCreationFromMultiBuildsUsingCommandFlag(t *testing.T) { + + cleanCallback := initLifecycleTest(t, minMultiSourcesArtifactoryVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + createRbFromMultiSourcesUsingCommandFlags(t, lcManager, createBuildsSource(), "", tests.LcRbName1, number1, "default", true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + assertStatusCompleted(t, lcManager, tests.LcRbName1, number1, "") +} + +func TestReleaseBundleCreationFromMultiBundlesUsingCommandFlag(t *testing.T) { + + cleanCallback := initLifecycleTest(t, minMultiSourcesArtifactoryVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + createRbFromSpec(t, tests.LifecycleBuilds12, tests.LcRbName1, number1, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + + createRbFromSpec(t, tests.LifecycleBuilds3, tests.LcRbName2, number2, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) + + createRbFromMultiSourcesUsingCommandFlags(t, lcManager, "", createReleaseBundlesSource(), tests.LcRbName3, number3, "default", true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName3, number3) + assertStatusCompleted(t, lcManager, tests.LcRbName3, number3, "") +} + +func TestReleaseBundleCreationFromMultipleBuildsAndBundlesUsingCommandFlags(t *testing.T) { + + cleanCallback := initLifecycleTest(t, minMultiSourcesArtifactoryVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + createRbFromSpec(t, tests.LifecycleBuilds12, tests.LcRbName1, number1, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + + createRbFromSpec(t, tests.LifecycleBuilds3, tests.LcRbName2, number2, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) + + createRbFromMultiSourcesUsingCommandFlags(t, lcManager, createBuildsSource(), createReleaseBundlesSource(), tests.LcRbName3, number3, "default", true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName3, number3) + assertStatusCompleted(t, lcManager, tests.LcRbName3, number3, "") +} + +func TestReleaseBundleCreationFromMultipleSourcesUsingSpec(t *testing.T) { + + cleanCallback := initLifecycleTest(t, minMultiSourcesArtifactoryVersion) + defer cleanCallback() + lcManager := getLcServiceManager(t) + + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + createRbFromSpec(t, tests.LifecycleBuilds12, tests.LcRbName1, number1, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + + createRbFromSpec(t, tests.LifecycleBuilds3, tests.LcRbName2, number2, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) + + createRbFromSpec(t, tests.LifecycleMultipleSources, tests.LcRbName3, number3, true, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName3, number3) + assertStatusCompleted(t, lcManager, tests.LcRbName3, number3, "") +} + +func createBuildsSource() string { + return fmt.Sprintf("name=%s, id=%s, include-deps=%s; name=%s, id=%s", tests.LcBuildName1, number1, "true", tests.LcBuildName2, number2) +} + +func createReleaseBundlesSource() string { + return fmt.Sprintf("name=%s, version=%s; name=%s, version=%s", tests.LcRbName1, number1, tests.LcRbName2, number2) +} + func TestReleaseBundleCreationFromAql(t *testing.T) { testReleaseBundleCreation(t, tests.UploadDevSpecA, tests.LifecycleAql, tests.GetExpectedLifecycleCreationByAql(), false) } @@ -157,12 +242,12 @@ func TestLifecycleFullFlow(t *testing.T) { assertExpectedArtifacts(t, tests.SearchAllProdRepo2, []string{}) // Export release lifecycle bundle archive - - tempDir, cleanUp := coreTests.CreateTempDirWithCallbackAndAssert(t) + // TODO Temporarily disabling till distribution on testing suite is stable. + /*tempDir, cleanUp := coreTests.CreateTempDirWithCallbackAndAssert(t) defer cleanUp() exportRb(t, tests.LcRbName2, number2, tempDir) - defer deleteExportedReleaseBundle(t, tests.LcRbName2) + defer deleteExportedReleaseBundle(t, tests.LcRbName2)*/ // TODO Temporarily disabling till distribution on testing suite is stable. /* @@ -202,9 +287,9 @@ func TestPromoteReleaseBundleWithPromotionTypeFlag(t *testing.T) { assertStatusCompleted(t, lcManager, tests.LcRbName1, number1, "") } -func deleteExportedReleaseBundle(t *testing.T, rbName string) { +/*func deleteExportedReleaseBundle(t *testing.T, rbName string) { assert.NoError(t, os.RemoveAll(rbName)) -} +}*/ func assertExpectedArtifacts(t *testing.T, specFileName string, expected []string) { searchProdSpec, err := tests.CreateSpec(specFileName) @@ -282,6 +367,119 @@ func TestCreateBundleWithoutSpecAndWithProject(t *testing.T) { defer deleteReleaseBundleWithProject(t, lcManager, tests.LcRbName1, number1, tests.ProjectKey) } +func createRbFromMultiSourcesUsingCommandFlags(t *testing.T, lcManager *lifecycle.LifecycleServicesManager, buildsSourcesOption, bundlesSourcesOption, + rbName, rbVersion, project string, sync bool) { + var sources []services.RbSource + sources = buildMultiSources(sources, buildsSourcesOption, bundlesSourcesOption, project) + + rbDetails := services.ReleaseBundleDetails{ + ReleaseBundleName: rbName, + ReleaseBundleVersion: rbVersion, + } + queryParams := services.CommonOptionalQueryParams{ + Async: !sync, + ProjectKey: project, + } + + _, err := lcManager.CreateReleaseBundlesFromMultipleSources(rbDetails, queryParams, gpgKeyPairName, sources) + assert.NoError(t, err) +} + +func buildMultiSources(sources []services.RbSource, buildsSourcesStr, bundlesSourcesStr, projectKey string) []services.RbSource { + // Process Builds + if buildsSourcesStr != "" { + sources = buildMultiBuildSources(sources, buildsSourcesStr) + } + + // Process Release Bundles + if bundlesSourcesStr != "" { + sources = buildMultiBundleSources(sources, bundlesSourcesStr, projectKey) + } + + return sources +} + +func buildMultiBundleSources(sources []services.RbSource, bundlesSourcesStr, projectKey string) []services.RbSource { + var releaseBundleSources []services.ReleaseBundleSource + bundleEntries := strings.Split(bundlesSourcesStr, ";") + for _, entry := range bundleEntries { + entry = strings.TrimSpace(entry) + if entry == "" { + continue + } + // Assuming the format "name=xxx, version=xxx" + components := strings.Split(entry, ",") + if len(components) != 2 { + continue + } + name := strings.TrimSpace(strings.Split(components[0], "=")[1]) + version := strings.TrimSpace(strings.Split(components[1], "=")[1]) + + releaseBundleSources = append(releaseBundleSources, services.ReleaseBundleSource{ + ProjectKey: projectKey, + ReleaseBundleName: name, + ReleaseBundleVersion: version, + }) + } + if len(releaseBundleSources) > 0 { + sources = append(sources, services.RbSource{ + SourceType: "release_bundles", + ReleaseBundles: releaseBundleSources, + }) + } + return sources +} + +func buildMultiBuildSources(sources []services.RbSource, sourcesStr string) []services.RbSource { + var buildSources []services.BuildSource + buildEntries := strings.Split(sourcesStr, ";") + for _, entry := range buildEntries { + entry = strings.TrimSpace(entry) + if entry == "" { + continue + } + // Assuming the format "name=xxx, number=xxx, include-dep=true" + components := strings.Split(entry, ",") + if len(components) < 2 { + continue + } + + name := strings.TrimSpace(strings.Split(components[0], "=")[1]) + number := strings.TrimSpace(strings.Split(components[1], "=")[1]) + + includeDepStr := "false" + if len(components) >= 3 { + parts := strings.Split(components[2], "=") + if len(parts) > 1 { + includeDepStr = strings.TrimSpace(parts[1]) + } + } + + includeDep, _ := strconv.ParseBool(includeDepStr) + + buildSources = append(buildSources, services.BuildSource{ + BuildRepository: getBuildInfoRepositoryByProject("default"), + BuildName: name, + BuildNumber: number, + IncludeDependencies: includeDep, + }) + } + if len(buildSources) > 0 { + sources = append(sources, services.RbSource{ + SourceType: "builds", + Builds: buildSources, + }) + } + return sources +} + +func getBuildInfoRepositoryByProject(projectKey string) string { + buildRepo := "artifactory" + if projectKey != "" && projectKey != "default" { + buildRepo = projectKey + } + return buildRepo + "-build-info" +} func createRbWithFlags(t *testing.T, specFilePath, sourceOption, buildName, buildNumber, rbName, rbVersion, project string, sync, withoutSigningKey bool) { argsAndOptions := []string{ @@ -314,12 +512,12 @@ func createRbWithFlags(t *testing.T, specFilePath, sourceOption, buildName, buil assert.NoError(t, lcCli.Exec(argsAndOptions...)) } -func exportRb(t *testing.T, rbName, rbVersion, targetPath string) { +/*func exportRb(t *testing.T, rbName, rbVersion, targetPath string) { lcCli.RunCliCmdWithOutput(t, "rbe", rbName, rbVersion, targetPath+"/") exists, err := fileutils.IsDirExists(path.Join(targetPath, rbName), false) assert.NoError(t, err) assert.Equal(t, true, exists) -} +}*/ /* func distributeRb(t *testing.T) { diff --git a/main.go b/main.go index 3aec5037a..a3c0f8b4d 100644 --- a/main.go +++ b/main.go @@ -95,7 +95,7 @@ func execMain() error { cli.CommandHelpTemplate = commandHelpTemplate cli.AppHelpTemplate = getAppHelpTemplate() app.CommandNotFound = func(c *cli.Context, command string) { - _, err = fmt.Fprintf(c.App.Writer, "'"+c.App.Name+" "+command+"' is not a jf command. See --help\n") + _, err = fmt.Fprintf(c.App.Writer, "%s", `'`+c.App.Name+" "+command+"' is not a jf command. See --help\n") if err != nil { clientlog.Debug(err) os.Exit(1) diff --git a/plugins/commands/uninstall.go b/plugins/commands/uninstall.go index c99cb50cd..5e19ad4e4 100644 --- a/plugins/commands/uninstall.go +++ b/plugins/commands/uninstall.go @@ -1,6 +1,7 @@ package commands import ( + "errors" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/plugins" "github.com/jfrog/jfrog-cli/utils/cliutils" @@ -50,5 +51,5 @@ func runUninstallCmd(requestedPlugin string) error { } func generateNoPluginFoundError(pluginName string) error { - return errorutils.CheckErrorf("plugin '" + pluginName + "' could not be found") + return errorutils.CheckError(errors.New("plugin '" + pluginName + "' could not be found")) } diff --git a/plugins/commands/utils/utils.go b/plugins/commands/utils/utils.go index efa871b51..c125749db 100644 --- a/plugins/commands/utils/utils.go +++ b/plugins/commands/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" @@ -119,11 +120,11 @@ func AssertPluginVersion(versionCmdOut string, expectedPluginVersion string) err // Get the actual version which is after the last space. (expected output to -v for example: "plugin-name version v1.0.0") split := strings.Split(strings.TrimSpace(versionCmdOut), " ") if len(split) != 3 { - return errorutils.CheckErrorf("failed verifying plugin version. Unexpected plugin output for version command: '" + versionCmdOut + "'") + return errorutils.CheckError(errors.New("failed verifying plugin version. Unexpected plugin output for version command: '" + versionCmdOut + "'")) } if split[2] != expectedPluginVersion { - return errorutils.CheckErrorf("provided version does not match the plugin's actual version. " + - "Provided: '" + expectedPluginVersion + "', Actual: '" + split[2] + "'") + return errorutils.CheckError(errors.New("provided version does not match the plugin's actual version. " + + "Provided: '" + expectedPluginVersion + "', Actual: '" + split[2] + "'")) } return nil } diff --git a/testdata/filespecs/lifecycle-multiple-sources.json b/testdata/filespecs/lifecycle-multiple-sources.json new file mode 100644 index 000000000..4a043ad05 --- /dev/null +++ b/testdata/filespecs/lifecycle-multiple-sources.json @@ -0,0 +1,26 @@ +{ + "files": [ + { + "build": "${LC_BUILD_NAME3}", + "includeDeps": "true" + }, + { + "bundle": "${RB_NAME1}/111" + }, + { + "bundle": "${RB_NAME2}/222" + }, + { + "pattern": "${DEV_REPO}/b*" + }, + { + "aql": { + "items.find": { + "repo": "${DEV_REPO}", + "path": ".", + "name": "a2.in" + } + } + } + ] +} \ No newline at end of file diff --git a/utils/cliutils/persistence.go b/utils/cliutils/persistence.go index ef7747c97..a36f897c5 100644 --- a/utils/cliutils/persistence.go +++ b/utils/cliutils/persistence.go @@ -2,6 +2,7 @@ package cliutils import ( "encoding/json" + "errors" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" @@ -28,7 +29,7 @@ func getPersistenceFilePath() error { if persistenceFilePath == "" { homeDir, err := coreutils.GetJfrogHomeDir() if err != nil { - return errorutils.CheckErrorf("failed to get JFrog home directory: " + err.Error()) + return errorutils.CheckError(errors.New("failed to get JFrog home directory: " + err.Error())) } persistenceFilePath = filepath.Join(homeDir, persistenceFileName) } @@ -101,19 +102,19 @@ func getPersistenceInfo() (*PersistenceFile, error) { // Create an empty persistence file if it doesn't exist pFile := &PersistenceFile{} if err = setPersistenceInfo(pFile); err != nil { - return nil, errorutils.CheckErrorf("failed while attempting to initialize persistence file: " + err.Error()) + return nil, errorutils.CheckError(errors.New("failed while attempting to initialize persistence file: " + err.Error())) } return pFile, nil } data, err := os.ReadFile(persistenceFilePath) if err != nil { - return nil, errorutils.CheckErrorf("failed while attempting to read persistence file: " + err.Error()) + return nil, errorutils.CheckError(errors.New("failed while attempting to read persistence file: " + err.Error())) } var info PersistenceFile if err = json.Unmarshal(data, &info); err != nil { - return nil, errorutils.CheckErrorf("failed while attempting to parse persistence file: " + err.Error()) + return nil, errorutils.CheckError(errors.New("failed while attempting to parse persistence file: " + err.Error())) } return &info, nil @@ -126,11 +127,11 @@ func setPersistenceInfo(info *PersistenceFile) error { } data, err := json.MarshalIndent(info, "", " ") if err != nil { - return errorutils.CheckErrorf("failed while attempting to create persistence file: " + err.Error()) + return errorutils.CheckError(errors.New("failed while attempting to create persistence file: " + err.Error())) } if err = os.WriteFile(persistenceFilePath, data, 0644); err != nil { - return errorutils.CheckErrorf("failed while attempting to write persistence file: " + err.Error()) + return errorutils.CheckError(errors.New("failed while attempting to write persistence file: " + err.Error())) } return nil } diff --git a/utils/tests/consts.go b/utils/tests/consts.go index 5fae5710b..f1e344cff 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -80,6 +80,7 @@ const ( LifecycleBuilds12 = "lifecycle-builds-1-2.json" LifecycleBuilds3 = "lifecycle-builds-3.json" LifecycleReleaseBundles = "lifecycle-release-bundles.json" + LifecycleMultipleSources = "lifecycle-multiple-sources.json" MavenConfig = "maven.yaml" MavenIncludeExcludePatternsConfig = "maven_include_exclude_patterns.yaml" MavenRemoteRepositoryConfig = "maven_remote_repository_config.json" diff --git a/utils/tests/utils.go b/utils/tests/utils.go index 392945692..fef14496a 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -105,7 +105,7 @@ func init() { TestXray = flag.Bool("test.xray", false, "Test Xray") TestAccess = flag.Bool("test.access", false, "Test Access") TestTransfer = flag.Bool("test.transfer", false, "Test files transfer") - TestLifecycle = flag.Bool("test.lifecycle", false, "Test lifecycle") + TestLifecycle = flag.Bool("test.lifecycle", true, "Test lifecycle") ContainerRegistry = flag.String("test.containerRegistry", "localhost:8082", "Container registry") HideUnitTestLog = flag.Bool("test.hideUnitTestLog", false, "Hide unit tests logs and print it in a file") InstallDataTransferPlugin = flag.Bool("test.installDataTransferPlugin", false, "Install data-transfer plugin on the source Artifactory server")