diff --git a/.github/workflows/frogbot-scan-and-fix.yml b/.github/workflows/frogbot-scan-and-fix.yml new file mode 100644 index 000000000..875e275ca --- /dev/null +++ b/.github/workflows/frogbot-scan-and-fix.yml @@ -0,0 +1,42 @@ +name: "Frogbot Scan and Fix" +on: + schedule: + # The repository will be scanned once a day at 00:00 GMT. + - cron: "0 0 * * *" +permissions: + contents: write + pull-requests: write + security-events: write +jobs: + create-fix-pull-requests: + runs-on: ubuntu-latest + strategy: + matrix: + # The repository scanning will be triggered periodically on the following branches. + branch: [ "dev" ] + steps: + # Install prerequisites + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: 1.20.x + + - uses: jfrog/frogbot@v2 + env: + # [Mandatory] + # JFrog platform URL + JF_URL: ${{ secrets.FROGBOT_URL }} + + # [Mandatory if JF_USER and JF_PASSWORD are not provided] + # JFrog access token with 'read' permissions on Xray service + JF_ACCESS_TOKEN: ${{ secrets.FROGBOT_ACCESS_TOKEN }} + + # [Mandatory] + # The GitHub token automatically generated for the job + JF_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JFROG_CLI_LOG_LEVEL: "DEBUG" + + # [Mandatory] + # The name of the branch on which Frogbot will perform the scan + JF_GIT_BASE_BRANCH: ${{ matrix.branch }} + diff --git a/.github/workflows/frogbot-scan-pr.yml b/.github/workflows/frogbot-scan-pr.yml new file mode 100644 index 000000000..06147e3cd --- /dev/null +++ b/.github/workflows/frogbot-scan-pr.yml @@ -0,0 +1,53 @@ +name: "Frogbot Scan Pull Request" +on: + pull_request_target: + types: [opened, synchronize] +permissions: + pull-requests: write + contents: read +jobs: + scan-pull-request: + runs-on: ubuntu-latest + # A pull request needs to be approved, before Frogbot scans it. Any GitHub user who is associated with the + # "frogbot" GitHub environment can approve the pull request to be scanned. + environment: frogbot + steps: + # Install prerequisites + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: 1.20.x + + - uses: jfrog/frogbot@v2 + env: + # [Mandatory] + # JFrog platform URL + JF_URL: ${{ secrets.FROGBOT_URL }} + + # [Mandatory if JF_USER and JF_PASSWORD are not provided] + # JFrog access token with 'read' permissions on Xray service + JF_ACCESS_TOKEN: ${{ secrets.FROGBOT_ACCESS_TOKEN }} + + # [Mandatory] + # The GitHub token automatically generated for the job + JF_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JFROG_CLI_LOG_LEVEL: "DEBUG" + + # [Optional] + # Configure the SMTP server to enable Frogbot to send emails with detected secrets in pull request scans. + # SMTP server URL including should the relevant port: (Example: smtp.server.com:8080) + JF_SMTP_SERVER: ${{ secrets.JF_SMTP_SERVER }} + + # [Mandatory if JF_SMTP_SERVER is set] + # The username required for authenticating with the SMTP server. + JF_SMTP_USER: ${{ secrets.JF_SMTP_USER }} + + # [Mandatory if JF_SMTP_SERVER is set] + # The password associated with the username required for authentication with the SMTP server. + JF_SMTP_PASSWORD: ${{ secrets.JF_SMTP_PASSWORD }} + + # [Optional] + # List of comma separated email addresses to receive email notifications about secrets + # detected during pull request scanning. The notification is also sent to the email set + # in the committer git profile regardless of whether this variable is set or not. + JF_EMAIL_RECEIVERS: "eco-system@jfrog.com" diff --git a/.github/workflows/gradleTests.yml b/.github/workflows/gradleTests.yml index d361d9e9b..6feac8097 100644 --- a/.github/workflows/gradleTests.yml +++ b/.github/workflows/gradleTests.yml @@ -15,11 +15,12 @@ concurrency: jobs: Gradle-Tests: if: contains(github.event.pull_request.labels.*.name, 'safe to test') || github.event_name == 'push' - name: ${{ matrix.os }} + name: ${{ matrix.os }}-gradle-${{ matrix.gradle-version }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] + gradle-version: [5.6.4, 8.3] runs-on: ${{ matrix.os }} env: GRADLE_OPTS: -Dorg.gradle.daemon=false @@ -31,7 +32,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 7.6 + gradle-version: ${{ matrix.gradle-version }} - name: Checkout code uses: actions/checkout@v3 with: diff --git a/.github/workflows/lifecycleTests.yml b/.github/workflows/lifecycleTests.yml new file mode 100644 index 000000000..01613e0af --- /dev/null +++ b/.github/workflows/lifecycleTests.yml @@ -0,0 +1,44 @@ +name: Lifecycle Tests +on: + push: + branches: + - '**' + tags-ignore: + - '**' + # Triggers the workflow on labeled PRs only. + pull_request_target: + types: [labeled] +# Ensures that only the latest commit is running for each PR at a time. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.ref }} + cancel-in-progress: true +jobs: + Lifecycle-Tests: + if: contains(github.event.pull_request.labels.*.name, 'safe to test') || github.event_name == 'push' + name: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: 1.20.x + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Go Cache + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: ${{ runner.os }}-go- + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Run Lifecycle tests + run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.lifecycle --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.user=${{ secrets.PLATFORM_USER }} --ci.runId=${{ runner.os }}-lifecycle diff --git a/distribution/cli.go b/distribution/cli.go index 682156767..0a0664a29 100644 --- a/distribution/cli.go +++ b/distribution/cli.go @@ -2,10 +2,6 @@ package distribution import ( "errors" - "os" - "path/filepath" - "strings" - "github.com/jfrog/jfrog-cli-core/v2/common/commands" "github.com/jfrog/jfrog-cli-core/v2/common/spec" distributionCommands "github.com/jfrog/jfrog-cli-core/v2/distribution/commands" @@ -18,10 +14,14 @@ import ( "github.com/jfrog/jfrog-cli/docs/artifactory/releasebundleupdate" "github.com/jfrog/jfrog-cli/docs/common" "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-cli/utils/distribution" distributionServices "github.com/jfrog/jfrog-client-go/distribution/services" distributionServicesUtils "github.com/jfrog/jfrog-client-go/distribution/services/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/urfave/cli" + "os" + "path/filepath" + "strings" ) func GetCommands() []cli.Command { @@ -111,11 +111,11 @@ func releaseBundleCreateCmd(c *cli.Context) error { return err } releaseBundleCreateCmd := distributionCommands.NewReleaseBundleCreateCommand() - rtDetails, err := createArtifactoryDetailsByFlags(c) + dsDetails, err := createDistributionDetailsByFlags(c) if err != nil { return err } - releaseBundleCreateCmd.SetServerDetails(rtDetails).SetReleaseBundleCreateParams(params).SetSpec(releaseBundleCreateSpec).SetDryRun(c.Bool("dry-run")).SetDetailedSummary(c.Bool("detailed-summary")) + releaseBundleCreateCmd.SetServerDetails(dsDetails).SetReleaseBundleCreateParams(params).SetSpec(releaseBundleCreateSpec).SetDryRun(c.Bool("dry-run")).SetDetailedSummary(c.Bool("detailed-summary")) err = commands.Exec(releaseBundleCreateCmd) if releaseBundleCreateCmd.IsDetailedSummary() { @@ -153,11 +153,11 @@ func releaseBundleUpdateCmd(c *cli.Context) error { return err } releaseBundleUpdateCmd := distributionCommands.NewReleaseBundleUpdateCommand() - rtDetails, err := createArtifactoryDetailsByFlags(c) + dsDetails, err := createDistributionDetailsByFlags(c) if err != nil { return err } - releaseBundleUpdateCmd.SetServerDetails(rtDetails).SetReleaseBundleUpdateParams(params).SetSpec(releaseBundleUpdateSpec).SetDryRun(c.Bool("dry-run")).SetDetailedSummary(c.Bool("detailed-summary")) + releaseBundleUpdateCmd.SetServerDetails(dsDetails).SetReleaseBundleUpdateParams(params).SetSpec(releaseBundleUpdateSpec).SetDryRun(c.Bool("dry-run")).SetDetailedSummary(c.Bool("detailed-summary")) err = commands.Exec(releaseBundleUpdateCmd) if releaseBundleUpdateCmd.IsDetailedSummary() { @@ -177,11 +177,11 @@ func releaseBundleSignCmd(c *cli.Context) error { params.StoringRepository = c.String("repo") params.GpgPassphrase = c.String("passphrase") releaseBundleSignCmd := distributionCommands.NewReleaseBundleSignCommand() - rtDetails, err := createArtifactoryDetailsByFlags(c) + dsDetails, err := createDistributionDetailsByFlags(c) if err != nil { return err } - releaseBundleSignCmd.SetServerDetails(rtDetails).SetReleaseBundleSignParams(params).SetDetailedSummary(c.Bool("detailed-summary")) + releaseBundleSignCmd.SetServerDetails(dsDetails).SetReleaseBundleSignParams(params).SetDetailedSummary(c.Bool("detailed-summary")) err = commands.Exec(releaseBundleSignCmd) if releaseBundleSignCmd.IsDetailedSummary() { if summary := releaseBundleSignCmd.GetSummary(); summary != nil { @@ -192,37 +192,21 @@ func releaseBundleSignCmd(c *cli.Context) error { } func releaseBundleDistributeCmd(c *cli.Context) error { - if c.NArg() != 2 { - return cliutils.WrongNumberOfArgumentsHandler(c) - } - if c.IsSet("max-wait-minutes") && !c.IsSet("sync") { - return cliutils.PrintHelpAndReturnError("The --max-wait-minutes option can't be used without --sync", c) - } - var distributionRules *spec.DistributionRules - if c.IsSet("dist-rules") { - if c.IsSet("site") || c.IsSet("city") || c.IsSet("country-code") { - return cliutils.PrintHelpAndReturnError("The --dist-rules option can't be used with --site, --city or --country-code", c) - } - var err error - distributionRules, err = spec.CreateDistributionRulesFromFile(c.String("dist-rules")) - if err != nil { - return err - } - } else { - distributionRules = createDefaultDistributionRules(c) + if err := distribution.ValidateReleaseBundleDistributeCmd(c); err != nil { + return err } - params := distributionServices.NewDistributeReleaseBundleParams(c.Args().Get(0), c.Args().Get(1)) - releaseBundleDistributeCmd := distributionCommands.NewReleaseBundleDistributeCommand() - rtDetails, err := createArtifactoryDetailsByFlags(c) + dsDetails, err := createDistributionDetailsByFlags(c) if err != nil { return err } - maxWaitMinutes, err := cliutils.GetIntFlagValue(c, "max-wait-minutes", 60) + distributionRules, maxWaitMinutes, params, err := distribution.InitReleaseBundleDistributeCmd(c) if err != nil { return err } - releaseBundleDistributeCmd.SetServerDetails(rtDetails). + + distributeCmd := distributionCommands.NewReleaseBundleDistributeV1Command() + distributeCmd.SetServerDetails(dsDetails). SetDistributeBundleParams(params). SetDistributionRules(distributionRules). SetDryRun(c.Bool("dry-run")). @@ -230,7 +214,7 @@ func releaseBundleDistributeCmd(c *cli.Context) error { SetMaxWaitMinutes(maxWaitMinutes). SetAutoCreateRepo(c.Bool("create-repo")) - return commands.Exec(releaseBundleDistributeCmd) + return commands.Exec(distributeCmd) } func releaseBundleDeleteCmd(c *cli.Context) error { @@ -248,7 +232,7 @@ func releaseBundleDeleteCmd(c *cli.Context) error { return err } } else { - distributionRules = createDefaultDistributionRules(c) + distributionRules = distribution.CreateDefaultDistributionRules(c) } params := distributionServices.NewDeleteReleaseBundleParams(c.Args().Get(0), c.Args().Get(1)) @@ -260,11 +244,11 @@ func releaseBundleDeleteCmd(c *cli.Context) error { } params.MaxWaitMinutes = maxWaitMinutes distributeBundleCmd := distributionCommands.NewReleaseBundleDeleteParams() - rtDetails, err := createArtifactoryDetailsByFlags(c) + dsDetails, err := createDistributionDetailsByFlags(c) if err != nil { return err } - distributeBundleCmd.SetQuiet(cliutils.GetQuietValue(c)).SetServerDetails(rtDetails).SetDistributeBundleParams(params).SetDistributionRules(distributionRules).SetDryRun(c.Bool("dry-run")) + distributeBundleCmd.SetQuiet(cliutils.GetQuietValue(c)).SetServerDetails(dsDetails).SetDistributeBundleParams(params).SetDistributionRules(distributionRules).SetDryRun(c.Bool("dry-run")) return commands.Exec(distributeBundleCmd) } @@ -283,16 +267,6 @@ func createDefaultReleaseBundleSpec(c *cli.Context) *spec.SpecFiles { BuildSpec() } -func createDefaultDistributionRules(c *cli.Context) *spec.DistributionRules { - return &spec.DistributionRules{ - DistributionRules: []spec.DistributionRule{{ - SiteName: c.String("site"), - CityName: c.String("city"), - CountryCodes: cliutils.GetStringsArrFlagValue(c, "country-codes"), - }}, - } -} - func createReleaseBundleCreateUpdateParams(c *cli.Context, bundleName, bundleVersion string) (distributionServicesUtils.ReleaseBundleParams, error) { releaseBundleParams := distributionServicesUtils.NewReleaseBundleParams(bundleName, bundleVersion) releaseBundleParams.SignImmediately = c.Bool("sign") @@ -336,13 +310,13 @@ func populateReleaseNotesSyntax(c *cli.Context) (distributionServicesUtils.Relea return distributionServicesUtils.PlainText, nil } -func createArtifactoryDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, error) { - artDetails, err := cliutils.CreateServerDetailsWithConfigOffer(c, true, cliutils.Ds) +func createDistributionDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, error) { + dsDetails, err := cliutils.CreateServerDetailsWithConfigOffer(c, true, cliutils.Ds) if err != nil { return nil, err } - if artDetails.DistributionUrl == "" { + if dsDetails.DistributionUrl == "" { return nil, errors.New("the --dist-url option is mandatory") } - return artDetails, nil + return dsDetails, nil } diff --git a/distribution_test.go b/distribution_test.go index 9c23f7304..62cbea526 100644 --- a/distribution_test.go +++ b/distribution_test.go @@ -14,6 +14,7 @@ import ( distributionServices "github.com/jfrog/jfrog-client-go/distribution/services" clientDistUtils "github.com/jfrog/jfrog-client-go/distribution/services/utils" clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/distribution" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -566,7 +567,7 @@ func TestDistributeSyncTimeout(t *testing.T) { testServer, mockServerDetails, _ := coreTestUtils.CreateDsRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/api/v1/distribution/"+tests.BundleName+"/"+bundleVersion { w.WriteHeader(http.StatusOK) - content, err := json.Marshal(distributionServices.DistributionResponseBody{TrackerId: json.Number(trackerId)}) + content, err := json.Marshal(distribution.DistributionResponseBody{TrackerId: json.Number(trackerId)}) assert.NoError(t, err) _, err = w.Write(content) assert.NoError(t, err) diff --git a/docs/lifecycle/distribute/help.go b/docs/lifecycle/distribute/help.go new file mode 100644 index 000000000..8a3c2d47f --- /dev/null +++ b/docs/lifecycle/distribute/help.go @@ -0,0 +1,15 @@ +package distribute + +var Usage = []string{"rbd [command options] "} + +func GetDescription() string { + return "Distribute a release bundle." +} + +func GetArguments() string { + return ` release bundle name + Name of the Release Bundle to distribute. + + release bundle version + Version of the Release Bundle to distribute.` +} diff --git a/go.mod b/go.mod index dc83f3523..737d29c7d 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ require ( github.com/buger/jsonparser v1.1.1 github.com/go-git/go-git/v5 v5.8.1 github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d - github.com/jfrog/build-info-go v1.9.8 + github.com/jfrog/build-info-go v1.9.9 github.com/jfrog/gofrog v1.3.0 - github.com/jfrog/jfrog-cli-core/v2 v2.41.2 - github.com/jfrog/jfrog-client-go v1.31.5 + github.com/jfrog/jfrog-cli-core/v2 v2.41.4 + github.com/jfrog/jfrog-client-go v1.31.6 github.com/jszwec/csvutil v1.8.0 github.com/mholt/archiver/v3 v3.5.1 github.com/stretchr/testify v1.8.4 @@ -122,10 +122,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230820165857-52ff32c4d8eb +// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230828134416-f0db33dd9344 -// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/eyalbe4/jfrog-cli-core/v2 v2.22.1-0.20230825095403-f5869e4264d6 +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230830130857-c5a2b11b52be // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.2.6-0.20230418122323-2bf299dd6d27 -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230803140217-0a5f43783ae8 +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230830130057-df2d2a80b555 diff --git a/go.sum b/go.sum index bd0657de8..d00b21ab6 100644 --- a/go.sum +++ b/go.sum @@ -237,14 +237,14 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= -github.com/jfrog/build-info-go v1.9.8 h1:D8/ga+YgQpqp/CJj2zteS4/twmSy8zvm1v9lCd2Kv1M= -github.com/jfrog/build-info-go v1.9.8/go.mod h1:t31QRpH5xUJKw8XkQlAA+Aq7aanyS1rrzpcK8xSNVts= +github.com/jfrog/build-info-go v1.9.9 h1:YMA9okHawBNL8SrCWzqULSf5M4W+YnWyUhmkWSjoXEE= +github.com/jfrog/build-info-go v1.9.9/go.mod h1:t31QRpH5xUJKw8XkQlAA+Aq7aanyS1rrzpcK8xSNVts= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= -github.com/jfrog/jfrog-cli-core/v2 v2.41.2 h1:Gnp93JcDAnHHCN3SHqam2K/S9yJcytS4q+MQd6vv9Ck= -github.com/jfrog/jfrog-cli-core/v2 v2.41.2/go.mod h1:YqB9rEJF1P7uGLIPUvF5qdDDf1zM5f4DneIQNkqyAfs= -github.com/jfrog/jfrog-client-go v1.31.5 h1:dYVgIJzMwX+EU9GEELKPSHFLyfW6UrrjZWMEZtAyx6A= -github.com/jfrog/jfrog-client-go v1.31.5/go.mod h1:icb00ZJN/mMMNkQduHDkzpqsXH9Flwi3f3COYexq3Nc= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230830130857-c5a2b11b52be h1:MjbSKQy937o0WFBKCXtvkX4EUSPCaA1LIhGISJUjYbU= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230830130857-c5a2b11b52be/go.mod h1:kaFzB3X83/jdzMcuGOYOaqnlz5MVTnDYt/asrOsCv18= +github.com/jfrog/jfrog-client-go v1.28.1-0.20230830130057-df2d2a80b555 h1:yaF5J4LNk+ws5+j+BFMJ43NMqpKuCtF7dUfCeGLttl8= +github.com/jfrog/jfrog-client-go v1.28.1-0.20230830130057-df2d2a80b555/go.mod h1:icb00ZJN/mMMNkQduHDkzpqsXH9Flwi3f3COYexq3Nc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jszwec/csvutil v1.8.0 h1:G7vS2LGdpZZDH1HmHeNbxOaJ/ZnJlpwGFvOkTkJzzNk= diff --git a/inttestutils/buildinfo.go b/inttestutils/buildinfo.go index 954ca6987..3003172bf 100644 --- a/inttestutils/buildinfo.go +++ b/inttestutils/buildinfo.go @@ -11,8 +11,8 @@ import ( coreutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/stretchr/testify/assert" @@ -27,7 +27,7 @@ func DeleteBuild(artifactoryUrl, buildName string, artHttpDetails httputils.Http restApi := path.Join("api/build/", buildName) params := map[string]string{"deleteAll": "1"} - requestFullUrl, err := utils.BuildArtifactoryUrl(artifactoryUrl, restApi, params) + requestFullUrl, err := utils.BuildUrl(artifactoryUrl, restApi, params) if err != nil { log.Error(err) return diff --git a/lifecycle/cli.go b/lifecycle/cli.go index 8caa332d2..647963c10 100644 --- a/lifecycle/cli.go +++ b/lifecycle/cli.go @@ -8,8 +8,10 @@ import ( coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli/docs/common" rbCreate "github.com/jfrog/jfrog-cli/docs/lifecycle/create" + rbDistribute "github.com/jfrog/jfrog-cli/docs/lifecycle/distribute" rbPromote "github.com/jfrog/jfrog-cli/docs/lifecycle/promote" "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-cli/utils/distribution" "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/urfave/cli" @@ -43,6 +45,19 @@ func GetCommands() []cli.Command { Category: lcCategory, Action: promote, }, + { + Name: "release-bundle-distribute", + Aliases: []string{"rbd"}, + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleDistribute), + Usage: rbDistribute.GetDescription(), + HelpName: coreCommon.CreateUsage("rbd", rbDistribute.GetDescription(), rbDistribute.Usage), + UsageText: rbDistribute.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + BashComplete: coreCommon.CreateBashCompletionFunc(), + Category: lcCategory, + Hidden: true, + Action: distribute, + }, }) } @@ -77,7 +92,7 @@ func create(c *cli.Context) (err error) { return } - createCmd := lifecycle.NewReleaseBundleCreate().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). + createCmd := lifecycle.NewReleaseBundleCreateCommand().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). SetReleaseBundleVersion(c.Args().Get(1)).SetSigningKeyName(c.String(cliutils.SigningKey)).SetSync(c.Bool(cliutils.Sync)). SetReleaseBundleProject(cliutils.GetProject(c)).SetBuildsSpecPath(c.String(cliutils.Builds)). SetReleaseBundlesSpecPath(c.String(cliutils.ReleaseBundles)) @@ -102,12 +117,51 @@ func promote(c *cli.Context) error { return err } - createCmd := lifecycle.NewReleaseBundlePromote().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). + createCmd := lifecycle.NewReleaseBundlePromoteCommand().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). SetReleaseBundleVersion(c.Args().Get(1)).SetEnvironment(c.Args().Get(2)).SetSigningKeyName(c.String(cliutils.SigningKey)). SetSync(c.Bool(cliutils.Sync)).SetReleaseBundleProject(cliutils.GetProject(c)).SetOverwrite(c.Bool(cliutils.Overwrite)) return commands.Exec(createCmd) } +func distribute(c *cli.Context) error { + if err := validateDistributeCommand(c); err != nil { + return err + } + + lcDetails, err := createLifecycleDetailsByFlags(c) + if err != nil { + return err + } + distributionRules, _, params, err := distribution.InitReleaseBundleDistributeCmd(c) + if err != nil { + return err + } + + distributeCmd := lifecycle.NewReleaseBundleDistributeCommand() + distributeCmd.SetServerDetails(lcDetails). + SetDistributeBundleParams(params). + SetDistributionRules(distributionRules). + SetDryRun(c.Bool("dry-run")). + SetAutoCreateRepo(c.Bool(cliutils.CreateRepo)). + SetPathMappingPattern(c.String(cliutils.PathMappingPattern)). + SetPathMappingTarget(c.String(cliutils.PathMappingTarget)) + return commands.Exec(distributeCmd) +} + +func validateDistributeCommand(c *cli.Context) error { + if err := distribution.ValidateReleaseBundleDistributeCmd(c); err != nil { + return err + } + + mappingPatternProvided := c.IsSet(cliutils.PathMappingPattern) + mappingTargetProvided := c.IsSet(cliutils.PathMappingTarget) + if (mappingPatternProvided && !mappingTargetProvided) || + (!mappingPatternProvided && mappingTargetProvided) { + return errorutils.CheckErrorf("the options --%s and --%s must be provided together", cliutils.PathMappingPattern, cliutils.PathMappingTarget) + } + return nil +} + func assertSigningKeyProvided(c *cli.Context) error { if c.String(cliutils.SigningKey) == "" { return errorutils.CheckErrorf("the --%s option is mandatory", cliutils.SigningKey) diff --git a/lifecycle_test.go b/lifecycle_test.go index 24980c734..5a1516df8 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -23,14 +23,14 @@ import ( ) const ( - rbMinVersion = "7.45.0" - gpgKeyPairName = "lc-tests-key-pair" - lcTestdataPath = "lifecycle" - releaseBundlesSpec = "release-bundles-spec.json" - buildsSpec12 = "builds-spec-1-2.json" - buildsSpec3 = "builds-spec-3.json" - prodEnvironment = "PROD" - number1, number2, number3 = "111", "222", "333" + artifactoryLifecycleMinVersion = "7.68.3" + gpgKeyPairName = "lc-tests-key-pair" + lcTestdataPath = "lifecycle" + releaseBundlesSpec = "release-bundles-spec.json" + buildsSpec12 = "builds-spec-1-2.json" + buildsSpec3 = "builds-spec-3.json" + prodEnvironment = "PROD" + number1, number2, number3 = "111", "222", "333" ) var ( @@ -47,11 +47,11 @@ func TestLifecycle(t *testing.T) { deleteBuilds := uploadBuilds(t) defer deleteBuilds() - // Create release bundles from builds synchronously. + // Create release bundle from builds synchronously. createRb(t, buildsSpec12, cliutils.Builds, tests.LcRbName1, number1, true) defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) - // Create release bundles from builds asynchronously and assert status. + // Create release bundle from builds asynchronously and assert status. createRb(t, buildsSpec3, cliutils.Builds, tests.LcRbName2, number2, false) defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) assertStatusCompleted(t, lcManager, tests.LcRbName2, number2, "") @@ -67,6 +67,11 @@ func TestLifecycle(t *testing.T) { searchSpec, err := tests.CreateSpec(tests.SearchAllProdRepo) assert.NoError(t, err) inttestutils.VerifyExistInArtifactory(tests.GetExpectedLifecycleArtifacts(), searchSpec, serverDetails, t) + + distributeRb(t) + // Verify the artifacts were distributed correctly by the provided path mappings. + expected := append(tests.GetExpectedLifecycleArtifacts(), tests.GetExpectedLifecycleMappingArtifacts()...) + inttestutils.VerifyExistInArtifactory(expected, searchSpec, serverDetails, t) } func uploadBuilds(t *testing.T) func() { @@ -80,13 +85,13 @@ func uploadBuilds(t *testing.T) func() { } } -func createRb(t *testing.T, specName, sourceOption, buildName, buildNumber string, sync bool) { +func createRb(t *testing.T, specName, sourceOption, rbName, rbVersion string, sync bool) { specFile, err := getSpecFile(specName) assert.NoError(t, err) argsAndOptions := []string{ "rbc", - buildName, - buildNumber, + rbName, + rbVersion, getOption(sourceOption, specFile), getOption(cliutils.SigningKey, gpgKeyPairName), } @@ -97,6 +102,16 @@ func createRb(t *testing.T, specName, sourceOption, buildName, buildNumber strin assert.NoError(t, lcCli.Exec(argsAndOptions...)) } +func distributeRb(t *testing.T) { + distributionRulesPath := filepath.Join(tests.GetTestResourcesPath(), "distribution", tests.DistributionRules) + assert.NoError(t, lcCli.Exec( + "rbd", tests.LcRbName3, number3, + getOption(cliutils.DistRules, distributionRulesPath), + getOption(cliutils.PathMappingPattern, tests.RtProdRepo+"/(*)"), + getOption(cliutils.PathMappingTarget, tests.RtProdRepo+"/target/{1}"), + )) +} + func getOption(option, value string) string { return fmt.Sprintf("--%s=%s", option, value) } @@ -183,7 +198,7 @@ func initLifecycleTest(t *testing.T) { if !*tests.TestLifecycle { t.Skip("Skipping lifecycle test. To run release bundle test add the '-test.lc=true' option.") } - validateArtifactoryVersion(t, rbMinVersion) + validateArtifactoryVersion(t, artifactoryLifecycleMinVersion) if !isLifecycleSupported(t) { t.Skip("Skipping lifecycle test because the functionality is not enabled on the provided JPD.") diff --git a/testdata/gradle/projectwithplugin/build.gradle b/testdata/gradle/projectwithplugin/build.gradle index 7664f11cb..d06c9fa99 100644 --- a/testdata/gradle/projectwithplugin/build.gradle +++ b/testdata/gradle/projectwithplugin/build.gradle @@ -9,6 +9,8 @@ buildscript { apply plugin: 'groovy' apply plugin: 'idea' apply plugin: 'com.jfrog.artifactory' +apply plugin: 'maven-publish' + version = 1.0 task initProject(description: 'Initialize project directory structure.') { doLast { @@ -28,6 +30,18 @@ task initProject(description: 'Initialize project directory structure.') { } } +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } +} + +tasks.withType(GenerateModuleMetadata) { + enabled = false +} + artifactory { contextUrl = "${URL}" publish { @@ -40,7 +54,7 @@ artifactory { // Reference to Gradle publications defined in the build script. // This is how we tell the Artifactory Plugin which artifacts should be // published to Artifactory. - publishConfigs('archives', 'published') + publications('mavenJava') publishArtifacts = true // Properties to be attached to the published artifacts. properties = ['qa.level': 'basic', 'dev.team' : 'core'] diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index 625841bc1..24bc36d46 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -131,8 +131,9 @@ const ( TransferInstall = "transfer-plugin-install" // Lifecycle commands keys - ReleaseBundleCreate = "release-bundle-create" - ReleaseBundlePromote = "release-bundle-promote" + ReleaseBundleCreate = "release-bundle-create" + ReleaseBundlePromote = "release-bundle-promote" + ReleaseBundleDistribute = "release-bundle-distribute" // *** Artifactory Commands' flags *** // Base flags @@ -424,14 +425,16 @@ const ( desc = "desc" releaseNotesPath = "release-notes-path" releaseNotesSyntax = "release-notes-syntax" - distRules = "dist-rules" - site = "site" - city = "city" - countryCodes = "country-codes" - sync = "sync" - maxWaitMinutes = "max-wait-minutes" deleteFromDist = "delete-from-dist" - createRepo = "create-repo" + + // Common release-bundle-* v1&v2 flags + DistRules = "dist-rules" + site = "site" + city = "city" + countryCodes = "country-codes" + sync = "sync" + maxWaitMinutes = "max-wait-minutes" + CreateRepo = "create-repo" // *** Xray Commands' flags *** // Base flags @@ -542,17 +545,22 @@ const ( InstallPluginHomeDir = "home-dir" // Unique lifecycle flags - lifecyclePrefix = "lc-" - lcUrl = lifecyclePrefix + url - lcSync = lifecyclePrefix + Sync - lcProject = lifecyclePrefix + project - Builds = "builds" - lcBuilds = lifecyclePrefix + Builds - ReleaseBundles = "release-bundles" - lcReleaseBundles = lifecyclePrefix + ReleaseBundles - SigningKey = "signing-key" - lcSigningKey = lifecyclePrefix + SigningKey - lcOverwrite = lifecyclePrefix + Overwrite + lifecyclePrefix = "lc-" + lcUrl = lifecyclePrefix + url + lcSync = lifecyclePrefix + Sync + lcProject = lifecyclePrefix + project + Builds = "builds" + lcBuilds = lifecyclePrefix + Builds + ReleaseBundles = "release-bundles" + lcReleaseBundles = lifecyclePrefix + ReleaseBundles + SigningKey = "signing-key" + lcSigningKey = lifecyclePrefix + SigningKey + lcOverwrite = lifecyclePrefix + Overwrite + PathMappingPattern = "mapping-pattern" + lcPathMappingPattern = lifecyclePrefix + PathMappingPattern + PathMappingTarget = "mapping-target" + lcPathMappingTarget = lifecyclePrefix + PathMappingTarget + lcDryRun = lifecyclePrefix + dryRun ) var flagsMap = map[string]cli.Flag{ @@ -1237,9 +1245,9 @@ var flagsMap = map[string]cli.Flag{ Name: repo, Usage: "[Optional] A repository name at source Artifactory to store release bundle artifacts in. If not provided, Artifactory will use the default one.` `", }, - distRules: cli.StringFlag{ - Name: distRules, - Usage: "Path to distribution rules.` `", + DistRules: cli.StringFlag{ + Name: DistRules, + Usage: "[Optional] Path to distribution rules.` `", }, site: cli.StringFlag{ Name: site, @@ -1509,8 +1517,8 @@ var flagsMap = map[string]cli.Flag{ Name: "format", Hidden: true, }, - createRepo: cli.BoolFlag{ - Name: createRepo, + CreateRepo: cli.BoolFlag{ + Name: CreateRepo, Usage: "[Default: false] Set to true to create the repository on the edge if it does not exist.` `", }, Filestore: cli.BoolFlag{ @@ -1613,6 +1621,19 @@ var flagsMap = map[string]cli.Flag{ Name: Overwrite, Usage: "[Default: false] Set to true to replace artifacts with the same name but a different checksum if such already exist at the promotion targets. By default, the promotion is stopped in a case of such conflict.` `", }, + lcPathMappingPattern: cli.StringFlag{ + Name: PathMappingPattern, + Usage: "[Optional] Specify along with '" + PathMappingTarget + "' to distribute artifacts to a different path on the edge node. You can use wildcards to specify multiple artifacts.` `", + }, + lcPathMappingTarget: cli.StringFlag{ + Name: PathMappingTarget, + Usage: "[Optional] The target path for distributed artifacts on the edge node. If not specified, the artifacts will have the same path and name on the edge node, as on the source Artifactory server. " + + "For flexibility in specifying the distribution path, you can include placeholders in the form of {1}, {2} which are replaced by corresponding tokens in the pattern path that are enclosed in parenthesis.` `", + }, + lcDryRun: cli.BoolFlag{ + Name: dryRun, + Usage: "[Default: false] Set to true to only simulate the distribution of the release bundle.` `", + }, } var commandFlags = map[string][]string{ @@ -1836,11 +1857,11 @@ var commandFlags = map[string][]string{ InsecureTls, rbDetailedSummary, }, ReleaseBundleV1Distribute: { - distUrl, user, password, accessToken, serverId, rbDryRun, distRules, - site, city, countryCodes, sync, maxWaitMinutes, InsecureTls, createRepo, + distUrl, user, password, accessToken, serverId, rbDryRun, DistRules, + site, city, countryCodes, sync, maxWaitMinutes, InsecureTls, CreateRepo, }, ReleaseBundleV1Delete: { - distUrl, user, password, accessToken, serverId, rbDryRun, distRules, + distUrl, user, password, accessToken, serverId, rbDryRun, DistRules, site, city, countryCodes, sync, maxWaitMinutes, InsecureTls, deleteFromDist, deleteQuiet, }, TemplateConsumer: { @@ -1897,6 +1918,10 @@ var commandFlags = map[string][]string{ ReleaseBundlePromote: { lcUrl, user, password, accessToken, serverId, lcSigningKey, lcSync, lcProject, lcOverwrite, }, + ReleaseBundleDistribute: { + lcUrl, user, password, accessToken, serverId, lcDryRun, DistRules, site, city, countryCodes, + InsecureTls, CreateRepo, lcPathMappingPattern, lcPathMappingTarget, + }, // Xray's commands OfflineUpdate: { licenseId, from, to, Version, target, Stream, Periodic, diff --git a/utils/distribution/distribute.go b/utils/distribution/distribute.go new file mode 100644 index 000000000..bbc82bae9 --- /dev/null +++ b/utils/distribution/distribute.go @@ -0,0 +1,52 @@ +package distribution + +import ( + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-cli/utils/cliutils" + distributionUtils "github.com/jfrog/jfrog-client-go/utils/distribution" + "github.com/urfave/cli" +) + +func CreateDefaultDistributionRules(c *cli.Context) *spec.DistributionRules { + return &spec.DistributionRules{ + DistributionRules: []spec.DistributionRule{{ + SiteName: c.String("site"), + CityName: c.String("city"), + CountryCodes: cliutils.GetStringsArrFlagValue(c, "country-codes"), + }}, + } +} + +func ValidateReleaseBundleDistributeCmd(c *cli.Context) error { + if c.NArg() != 2 { + return cliutils.WrongNumberOfArgumentsHandler(c) + } + if c.IsSet("max-wait-minutes") && !c.IsSet("sync") { + return cliutils.PrintHelpAndReturnError("The --max-wait-minutes option can't be used without --sync", c) + } + + if c.IsSet("dist-rules") && (c.IsSet("site") || c.IsSet("city") || c.IsSet("country-code")) { + return cliutils.PrintHelpAndReturnError("The --dist-rules option can't be used with --site, --city or --country-code", c) + } + + return nil +} + +func InitReleaseBundleDistributeCmd(c *cli.Context) (distributionRules *spec.DistributionRules, maxWaitMinutes int, params distributionUtils.DistributionParams, err error) { + if c.IsSet("dist-rules") { + distributionRules, err = spec.CreateDistributionRulesFromFile(c.String("dist-rules")) + if err != nil { + return + } + } else { + distributionRules = CreateDefaultDistributionRules(c) + } + + maxWaitMinutes, err = cliutils.GetIntFlagValue(c, "max-wait-minutes", 60) + if err != nil { + return + } + + params = distributionUtils.NewDistributeReleaseBundleParams(c.Args().Get(0), c.Args().Get(1)) + return +} diff --git a/utils/tests/consts.go b/utils/tests/consts.go index 8373be6d7..f415aa1f0 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -2099,6 +2099,20 @@ func GetExpectedLifecycleArtifacts() []string { } } +func GetExpectedLifecycleMappingArtifacts() []string { + return []string{ + RtProdRepo + "/target/a1.in", + RtProdRepo + "/target/a2.in", + RtProdRepo + "/target/a3.in", + RtProdRepo + "/target/b1.in", + RtProdRepo + "/target/b2.in", + RtProdRepo + "/target/b3.in", + RtProdRepo + "/target/c1.in", + RtProdRepo + "/target/c2.in", + RtProdRepo + "/target/c3.in", + } +} + func GetGoPublishWithExclusionsExpectedRepoGo() []string { var expected = []string{ GoRepo + "/github.com/jfrog/dependency/@v/v1.1.1.info", diff --git a/utils/tests/utils.go b/utils/tests/utils.go index d5ab82381..19540b144 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -106,7 +106,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.lc", false, "Test lifecycle") + TestLifecycle = flag.Bool("test.lifecycle", false, "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") diff --git a/xray_test.go b/xray_test.go index a59e356b0..74d64c7c9 100644 --- a/xray_test.go +++ b/xray_test.go @@ -485,7 +485,7 @@ func validateXrayVersion(t *testing.T, minVersion string) { assert.NoError(t, err) return } - err = coreutils.ValidateMinimumVersion(coreutils.Xray, xrayVersion.GetVersion(), minVersion) + err = clientUtils.ValidateMinimumVersion(clientUtils.Xray, xrayVersion.GetVersion(), minVersion) if err != nil { t.Skip(err) }