forgejo/routers/web/shared/packages/packages.go
KN4CK3R 32db62515f
Add package registry cleanup rules (#21658)
Fixes #20514
Fixes #20766
Fixes #20631

This PR adds Cleanup Rules for the package registry. This allows to
delete unneeded packages automatically. Cleanup rules can be set up from
the user or org settings.
Please have a look at the documentation because I'm not a native english
speaker.

Rule Form

![grafik](https://user-images.githubusercontent.com/1666336/199330792-c13918a6-e196-4e71-9f53-18554515edca.png)

Rule List

![grafik](https://user-images.githubusercontent.com/1666336/199331261-5f6878e8-a80c-4985-800d-ebb3524b1a8d.png)

Rule Preview

![grafik](https://user-images.githubusercontent.com/1666336/199330917-c95e4017-cf64-4142-a3e4-af18c4f127c3.png)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-20 16:08:38 +02:00

226 lines
5.9 KiB
Go

// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package packages
import (
"fmt"
"net/http"
"time"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
container_service "code.gitea.io/gitea/services/packages/container"
)
func SetPackagesContext(ctx *context.Context, owner *user_model.User) {
pcrs, err := packages_model.GetCleanupRulesByOwner(ctx, owner.ID)
if err != nil {
ctx.ServerError("GetCleanupRulesByOwner", err)
return
}
ctx.Data["CleanupRules"] = pcrs
}
func SetRuleAddContext(ctx *context.Context) {
setRuleEditContext(ctx, nil)
}
func SetRuleEditContext(ctx *context.Context, owner *user_model.User) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
}
setRuleEditContext(ctx, pcr)
}
func setRuleEditContext(ctx *context.Context, pcr *packages_model.PackageCleanupRule) {
ctx.Data["IsEditRule"] = pcr != nil
if pcr == nil {
pcr = &packages_model.PackageCleanupRule{}
}
ctx.Data["CleanupRule"] = pcr
ctx.Data["AvailableTypes"] = packages_model.TypeList
}
func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
performRuleEditPost(ctx, owner, nil, redirectURL, template)
}
func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
}
form := web.GetForm(ctx).(*forms.PackageCleanupRuleForm)
if form.Action == "remove" {
if err := packages_model.DeleteCleanupRuleByID(ctx, pcr.ID); err != nil {
ctx.ServerError("DeleteCleanupRuleByID", err)
return
}
ctx.Flash.Success(ctx.Tr("packages.owner.settings.cleanuprules.success.delete"))
ctx.Redirect(redirectURL)
} else {
performRuleEditPost(ctx, owner, pcr, redirectURL, template)
}
}
func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template base.TplName) {
isEditRule := pcr != nil
if pcr == nil {
pcr = &packages_model.PackageCleanupRule{}
}
form := web.GetForm(ctx).(*forms.PackageCleanupRuleForm)
pcr.Enabled = form.Enabled
pcr.OwnerID = owner.ID
pcr.KeepCount = form.KeepCount
pcr.KeepPattern = form.KeepPattern
pcr.RemoveDays = form.RemoveDays
pcr.RemovePattern = form.RemovePattern
pcr.MatchFullName = form.MatchFullName
ctx.Data["IsEditRule"] = isEditRule
ctx.Data["CleanupRule"] = pcr
ctx.Data["AvailableTypes"] = packages_model.TypeList
if ctx.HasError() {
ctx.HTML(http.StatusOK, template)
return
}
if isEditRule {
if err := packages_model.UpdateCleanupRule(ctx, pcr); err != nil {
ctx.ServerError("UpdateCleanupRule", err)
return
}
} else {
pcr.Type = packages_model.Type(form.Type)
if has, err := packages_model.HasOwnerCleanupRuleForPackageType(ctx, owner.ID, pcr.Type); err != nil {
ctx.ServerError("HasOwnerCleanupRuleForPackageType", err)
return
} else if has {
ctx.Data["Err_Type"] = true
ctx.HTML(http.StatusOK, template)
return
}
var err error
if pcr, err = packages_model.InsertCleanupRule(ctx, pcr); err != nil {
ctx.ServerError("InsertCleanupRule", err)
return
}
}
ctx.Flash.Success(ctx.Tr("packages.owner.settings.cleanuprules.success.update"))
ctx.Redirect(fmt.Sprintf("%s/rules/%d", redirectURL, pcr.ID))
}
func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
}
if err := pcr.CompiledPattern(); err != nil {
ctx.ServerError("CompiledPattern", err)
return
}
olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays)
packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type)
if err != nil {
ctx.ServerError("GetPackagesByType", err)
return
}
versionsToRemove := make([]*packages_model.PackageDescriptor, 0, 10)
for _, p := range packages {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
IsInternal: util.OptionalBoolFalse,
Sort: packages_model.SortCreatedDesc,
Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
if err != nil {
ctx.ServerError("SearchVersions", err)
return
}
for _, pv := range pvs {
if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil {
ctx.ServerError("ShouldBeSkipped", err)
return
} else if skip {
continue
}
toMatch := pv.LowerVersion
if pcr.MatchFullName {
toMatch = p.LowerName + "/" + pv.LowerVersion
}
if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) {
continue
}
if pv.CreatedUnix.AsLocalTime().After(olderThan) {
continue
}
if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) {
continue
}
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
if err != nil {
ctx.ServerError("GetPackageDescriptor", err)
return
}
versionsToRemove = append(versionsToRemove, pd)
}
}
ctx.Data["CleanupRule"] = pcr
ctx.Data["VersionsToRemove"] = versionsToRemove
}
func getCleanupRuleByContext(ctx *context.Context, owner *user_model.User) *packages_model.PackageCleanupRule {
id := ctx.FormInt64("id")
if id == 0 {
id = ctx.ParamsInt64("id")
}
pcr, err := packages_model.GetCleanupRuleByID(ctx, id)
if err != nil {
if err == packages_model.ErrPackageCleanupRuleNotExist {
ctx.NotFound("", err)
} else {
ctx.ServerError("GetCleanupRuleByID", err)
}
return nil
}
if pcr != nil && pcr.OwnerID == owner.ID {
return pcr
}
ctx.NotFound("", fmt.Errorf("PackageCleanupRule[%v] not associated to owner %v", id, owner))
return nil
}