diff --git a/artifactory/commands/testdata/npm/npm-example-0.0.4.tgz b/artifactory/commands/testdata/npm/npm-example-0.0.4.tgz deleted file mode 100644 index 5d3f4db17..000000000 Binary files a/artifactory/commands/testdata/npm/npm-example-0.0.4.tgz and /dev/null differ diff --git a/go.mod b/go.mod index 0cc3899cd..4e010a20e 100644 --- a/go.mod +++ b/go.mod @@ -96,7 +96,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240228121257-3414cc0ffcb6 +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240317160615-e419c2a9e723 replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c diff --git a/go.sum b/go.sum index 172a51532..b61111a8f 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c h1:M1QiuCYGC github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c/go.mod h1:QHcKuesY4MrBVBuEwwBz4uIsX6mwYuMEDV09ng4AvAU= github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc= github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240228121257-3414cc0ffcb6 h1:W+79g2W3ARRhIZtBfG0t73fi4IlyiIRWwdm1tajOkkc= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240228121257-3414cc0ffcb6/go.mod h1:WhVrqiqhSNFwj58/RQIrJEd28PHH1LTD4eWE0vBXv1o= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240317160615-e419c2a9e723 h1:0N/fdI2PXLjdWZieh7ib+6gb87yw3x22V7t1YZJvWOA= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240317160615-e419c2a9e723/go.mod h1:NB8tYFgkWtn+wHsKC+aYC75aLnS6yW81d8JAFTBxsi0= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= diff --git a/lifecycle/common.go b/lifecycle/common.go index e185cb578..b5cf6de7d 100644 --- a/lifecycle/common.go +++ b/lifecycle/common.go @@ -14,7 +14,6 @@ type releaseBundleCmd struct { serverDetails *config.ServerDetails releaseBundleName string releaseBundleVersion string - signingKeyName string sync bool rbProjectKey string } diff --git a/lifecycle/createcommon.go b/lifecycle/createcommon.go index 4d5fba599..b5a83ee07 100644 --- a/lifecycle/createcommon.go +++ b/lifecycle/createcommon.go @@ -6,6 +6,7 @@ import ( type ReleaseBundleCreateCommand struct { releaseBundleCmd + signingKeyName string buildsSpecPath string releaseBundlesSpecPath string } diff --git a/lifecycle/deletelocal.go b/lifecycle/deletelocal.go new file mode 100644 index 000000000..d1e434aa8 --- /dev/null +++ b/lifecycle/deletelocal.go @@ -0,0 +1,132 @@ +package lifecycle + +import ( + "errors" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + "github.com/jfrog/jfrog-client-go/utils/log" + "strings" +) + +type ReleaseBundleDeleteCommand struct { + releaseBundleCmd + environment string + quiet bool +} + +func NewReleaseBundleDeleteCommand() *ReleaseBundleDeleteCommand { + return &ReleaseBundleDeleteCommand{} +} + +func (rbd *ReleaseBundleDeleteCommand) SetServerDetails(serverDetails *config.ServerDetails) *ReleaseBundleDeleteCommand { + rbd.serverDetails = serverDetails + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetReleaseBundleName(releaseBundleName string) *ReleaseBundleDeleteCommand { + rbd.releaseBundleName = releaseBundleName + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetReleaseBundleVersion(releaseBundleVersion string) *ReleaseBundleDeleteCommand { + rbd.releaseBundleVersion = releaseBundleVersion + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetSync(sync bool) *ReleaseBundleDeleteCommand { + rbd.sync = sync + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetReleaseBundleProject(rbProjectKey string) *ReleaseBundleDeleteCommand { + rbd.rbProjectKey = rbProjectKey + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetEnvironment(environment string) *ReleaseBundleDeleteCommand { + rbd.environment = environment + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetQuiet(quiet bool) *ReleaseBundleDeleteCommand { + rbd.quiet = quiet + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) CommandName() string { + return "rb_delete" +} + +func (rbd *ReleaseBundleDeleteCommand) ServerDetails() (*config.ServerDetails, error) { + return rbd.serverDetails, nil +} + +func (rbd *ReleaseBundleDeleteCommand) Run() error { + if err := validateArtifactoryVersionSupported(rbd.serverDetails); err != nil { + return err + } + + servicesManager, rbDetails, queryParams, err := rbd.getPrerequisites() + if err != nil { + return err + } + + if rbd.environment != "" { + return rbd.deletePromotionsOnly(servicesManager, rbDetails, queryParams) + } + return rbd.deleteLocalReleaseBundle(servicesManager, rbDetails, queryParams) +} + +func (rbd *ReleaseBundleDeleteCommand) deletePromotionsOnly(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, commonQueryParams services.CommonOptionalQueryParams) error { + + deletionSubject := fmt.Sprintf("all promotions to environment '%s' of release bundle '%s/%s'", rbd.environment, rbd.releaseBundleName, rbd.releaseBundleVersion) + if !rbd.confirmDelete(deletionSubject) { + return nil + } + + optionalQueryParams := services.GetPromotionsOptionalQueryParams{ProjectKey: commonQueryParams.ProjectKey} + response, err := servicesManager.GetReleaseBundleVersionPromotions(rbDetails, optionalQueryParams) + if err != nil { + return err + } + success := 0 + fail := 0 + for _, promotion := range response.Promotions { + if strings.EqualFold(promotion.Environment, rbd.environment) { + if curErr := servicesManager.DeleteReleaseBundleVersionPromotion(rbDetails, commonQueryParams, promotion.CreatedMillis.String()); curErr != nil { + err = errors.Join(err, curErr) + fail++ + } else { + success++ + } + } + } + if success == 0 && fail == 0 { + log.Info(fmt.Sprintf("No promotions were found for environment '%s'", rbd.environment)) + } else { + log.Info(fmt.Sprintf("Promotions deleted successfully: %d, failed: %d", success, fail)) + } + + return err +} + +func (rbd *ReleaseBundleDeleteCommand) deleteLocalReleaseBundle(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { + deletionSubject := fmt.Sprintf("release bundle '%s/%s' locally with all its promotions", rbd.releaseBundleName, rbd.releaseBundleVersion) + if !rbd.confirmDelete(deletionSubject) { + return nil + } + return servicesManager.DeleteReleaseBundleVersion(rbDetails, queryParams) +} + +func (rbd *ReleaseBundleDeleteCommand) confirmDelete(deletionSubject string) bool { + if rbd.quiet { + return true + } + return coreutils.AskYesNo( + fmt.Sprintf("Are you sure you want to delete %s?\n"+avoidConfirmationMsg, deletionSubject), false) +} diff --git a/lifecycle/deleteremote.go b/lifecycle/deleteremote.go new file mode 100644 index 000000000..6b1665131 --- /dev/null +++ b/lifecycle/deleteremote.go @@ -0,0 +1,159 @@ +package lifecycle + +import ( + "encoding/json" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/distribution" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +const avoidConfirmationMsg = "You can avoid this confirmation message by adding --quiet to the command." + +type ReleaseBundleRemoteDeleteCommand struct { + releaseBundleCmd + distributionRules *spec.DistributionRules + dryRun bool + quiet bool + maxWaitMinutes int +} + +func NewReleaseBundleRemoteDeleteCommand() *ReleaseBundleRemoteDeleteCommand { + return &ReleaseBundleRemoteDeleteCommand{} +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetServerDetails(serverDetails *config.ServerDetails) *ReleaseBundleRemoteDeleteCommand { + rbd.serverDetails = serverDetails + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetReleaseBundleName(releaseBundleName string) *ReleaseBundleRemoteDeleteCommand { + rbd.releaseBundleName = releaseBundleName + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetReleaseBundleVersion(releaseBundleVersion string) *ReleaseBundleRemoteDeleteCommand { + rbd.releaseBundleVersion = releaseBundleVersion + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetSync(sync bool) *ReleaseBundleRemoteDeleteCommand { + rbd.sync = sync + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetReleaseBundleProject(rbProjectKey string) *ReleaseBundleRemoteDeleteCommand { + rbd.rbProjectKey = rbProjectKey + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetDistributionRules(distributionRules *spec.DistributionRules) *ReleaseBundleRemoteDeleteCommand { + rbd.distributionRules = distributionRules + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetDryRun(dryRun bool) *ReleaseBundleRemoteDeleteCommand { + rbd.dryRun = dryRun + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetQuiet(quiet bool) *ReleaseBundleRemoteDeleteCommand { + rbd.quiet = quiet + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetMaxWaitMinutes(maxWaitMinutes int) *ReleaseBundleRemoteDeleteCommand { + rbd.maxWaitMinutes = maxWaitMinutes + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) CommandName() string { + return "rb_remote_delete" +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) ServerDetails() (*config.ServerDetails, error) { + return rbd.serverDetails, nil +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) Run() error { + if err := validateArtifactoryVersionSupported(rbd.serverDetails); err != nil { + return err + } + + servicesManager, rbDetails, queryParams, err := rbd.getPrerequisites() + if err != nil { + return err + } + + return rbd.deleteRemote(servicesManager, rbDetails, queryParams) +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) deleteRemote(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { + + confirm, err := rbd.confirmDelete() + if err != nil || !confirm { + return err + } + + aggregatedRules := rbd.getAggregatedDistRules() + + return servicesManager.RemoteDeleteReleaseBundle(rbDetails, services.ReleaseBundleRemoteDeleteParams{ + DistributionRules: aggregatedRules, + DryRun: rbd.dryRun, + MaxWaitMinutes: rbd.maxWaitMinutes, + CommonOptionalQueryParams: queryParams, + }) +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) distributionRulesEmpty() bool { + return rbd.distributionRules == nil || + len(rbd.distributionRules.DistributionRules) == 0 || + len(rbd.distributionRules.DistributionRules) == 1 && rbd.distributionRules.DistributionRules[0].IsEmpty() +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) confirmDelete() (bool, error) { + if rbd.quiet { + return true, nil + } + + message := fmt.Sprintf("Are you sure you want to delete the release bundle '%s/%s' remotely ", rbd.releaseBundleName, rbd.releaseBundleVersion) + if rbd.distributionRulesEmpty() { + message += "from all edges?" + } else { + var distributionRulesBodies []distribution.DistributionRulesBody + for _, rule := range rbd.distributionRules.DistributionRules { + distributionRulesBodies = append(distributionRulesBodies, distribution.DistributionRulesBody{ + SiteName: rule.SiteName, + CityName: rule.CityName, + CountryCodes: rule.CountryCodes, + }) + } + bytes, err := json.Marshal(distributionRulesBodies) + if err != nil { + return false, errorutils.CheckError(err) + } + + log.Output(clientutils.IndentJson(bytes)) + message += "from all edges with the above distribution rules?" + } + + return coreutils.AskYesNo(message+"\n"+avoidConfirmationMsg, false), nil +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) getAggregatedDistRules() (aggregatedRules []*distribution.DistributionCommonParams) { + if rbd.distributionRulesEmpty() { + aggregatedRules = append(aggregatedRules, &distribution.DistributionCommonParams{SiteName: "*"}) + } else { + for _, rules := range rbd.distributionRules.DistributionRules { + aggregatedRules = append(aggregatedRules, rules.ToDistributionCommonParams()) + } + } + return +} diff --git a/lifecycle/promote.go b/lifecycle/promote.go index 0cf8b64d0..fb078ff36 100644 --- a/lifecycle/promote.go +++ b/lifecycle/promote.go @@ -10,6 +10,7 @@ import ( type ReleaseBundlePromoteCommand struct { releaseBundleCmd + signingKeyName string environment string includeReposPatterns []string excludeReposPatterns []string