Skip to content

Commit

Permalink
Artifactory Release Lifecycle Management - Support release bundles de…
Browse files Browse the repository at this point in the history
…letion (#1136)
  • Loading branch information
RobiNino authored Mar 17, 2024
1 parent 5979ebb commit 662abef
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 4 deletions.
Binary file not shown.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
1 change: 0 additions & 1 deletion lifecycle/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type releaseBundleCmd struct {
serverDetails *config.ServerDetails
releaseBundleName string
releaseBundleVersion string
signingKeyName string
sync bool
rbProjectKey string
}
Expand Down
1 change: 1 addition & 0 deletions lifecycle/createcommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

type ReleaseBundleCreateCommand struct {
releaseBundleCmd
signingKeyName string
buildsSpecPath string
releaseBundlesSpecPath string
}
Expand Down
132 changes: 132 additions & 0 deletions lifecycle/deletelocal.go
Original file line number Diff line number Diff line change
@@ -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)
}
159 changes: 159 additions & 0 deletions lifecycle/deleteremote.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions lifecycle/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

type ReleaseBundlePromoteCommand struct {
releaseBundleCmd
signingKeyName string
environment string
includeReposPatterns []string
excludeReposPatterns []string
Expand Down

0 comments on commit 662abef

Please sign in to comment.