feat: vendor gitea 1.16.2
This commit is contained in:
@@ -36,7 +36,7 @@ const (
|
||||
IndexArchiveFilename = "packages.db"
|
||||
)
|
||||
|
||||
func AquireRegistryLock(ctx context.Context, ownerID int64) (globallock.ReleaseFunc, error) {
|
||||
func AcquireRegistryLock(ctx context.Context, ownerID int64) (globallock.ReleaseFunc, error) {
|
||||
return globallock.Lock(ctx, fmt.Sprintf("packages_arch_%d", ownerID))
|
||||
}
|
||||
|
||||
|
||||
@@ -23,21 +23,24 @@ type packageClaims struct {
|
||||
PackageMeta
|
||||
}
|
||||
type PackageMeta struct {
|
||||
UserID int64
|
||||
Scope auth_model.AccessTokenScope
|
||||
UserID int64
|
||||
Scope auth_model.AccessTokenScope
|
||||
ActionsUserTaskID int64
|
||||
}
|
||||
|
||||
func CreateAuthorizationToken(u *user_model.User, packageScope auth_model.AccessTokenScope) (string, error) {
|
||||
now := time.Now()
|
||||
|
||||
actionsUserTaskID, _ := user_model.GetActionsUserTaskID(u)
|
||||
claims := packageClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
},
|
||||
PackageMeta: PackageMeta{
|
||||
UserID: u.ID,
|
||||
Scope: packageScope,
|
||||
UserID: u.ID,
|
||||
Scope: packageScope,
|
||||
ActionsUserTaskID: actionsUserTaskID,
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
@@ -53,7 +56,7 @@ func CreateAuthorizationToken(u *user_model.User, packageScope auth_model.Access
|
||||
func ParseAuthorizationRequest(req *http.Request) (*PackageMeta, error) {
|
||||
h := req.Header.Get("Authorization")
|
||||
if h == "" {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // the auth method is not applicable
|
||||
}
|
||||
|
||||
parts := strings.SplitN(h, " ", 2)
|
||||
|
||||
@@ -152,7 +152,7 @@ func BuildPackageIndex(ctx context.Context, p *packages_model.Package) (*bytes.B
|
||||
return nil, fmt.Errorf("SearchVersions[%s]: %w", p.Name, err)
|
||||
}
|
||||
if len(pvs) == 0 {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the package has no versions
|
||||
}
|
||||
|
||||
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
|
||||
@@ -306,7 +306,7 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re
|
||||
return err
|
||||
}
|
||||
|
||||
return t.Push(ctx, doer, commitHash, repo.DefaultBranch)
|
||||
return t.Push(ctx, doer, commitHash, repo.DefaultBranch, false)
|
||||
}
|
||||
|
||||
func writeObjectToIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, path string, r io.Reader) error {
|
||||
|
||||
@@ -134,7 +134,7 @@ func executeCleanupOneRule(ctx context.Context, pcr *packages_model.PackageClean
|
||||
return fmt.Errorf("CleanupRule [%d]: rpm.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
|
||||
}
|
||||
case packages_model.TypeArch:
|
||||
release, err := arch_service.AquireRegistryLock(ctx, pcr.OwnerID)
|
||||
release, err := arch_service.AcquireRegistryLock(ctx, pcr.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,6 +184,7 @@ func CleanupExpiredData(ctx context.Context, olderThan time.Duration) error {
|
||||
}
|
||||
}
|
||||
|
||||
// HINT: PACKAGE-DEFER-STORAGE-DELETE: Handle blob deletion for package storage
|
||||
pbs, err = packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
var (
|
||||
// errWriteAfterRead occurs if Write is called after a read operation
|
||||
errWriteAfterRead = errors.New("write is unsupported after a read operation")
|
||||
// errOffsetMissmatch occurs if the file offset is different than the model
|
||||
errOffsetMissmatch = errors.New("offset mismatch between file and model")
|
||||
// errOffsetMismatch occurs if the file offset is different than the model
|
||||
errOffsetMismatch = errors.New("offset mismatch between file and model")
|
||||
)
|
||||
|
||||
// BlobUploader handles chunked blob uploads
|
||||
@@ -63,10 +63,10 @@ func NewBlobUploader(ctx context.Context, id string) (*BlobUploader, error) {
|
||||
}
|
||||
|
||||
return &BlobUploader{
|
||||
model,
|
||||
hash,
|
||||
f,
|
||||
false,
|
||||
PackageBlobUpload: model,
|
||||
MultiHasher: hash,
|
||||
file: f,
|
||||
reading: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func (u *BlobUploader) Append(ctx context.Context, r io.Reader) error {
|
||||
return err
|
||||
}
|
||||
if offset != u.BytesReceived {
|
||||
return errOffsetMissmatch
|
||||
return errOffsetMismatch
|
||||
}
|
||||
|
||||
n, err := io.Copy(io.MultiWriter(u.file, u.MultiHasher), r)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -67,7 +68,14 @@ func GetOrCreateKeyPair(ctx context.Context, ownerID int64) (string, string, err
|
||||
}
|
||||
|
||||
func generateKeypair() (string, string, error) {
|
||||
e, err := openpgp.NewEntity("", "Debian Registry", "", nil)
|
||||
// Repository signing keys are long-lived and there is currently no rotation mechanism, choose stronger algorithms
|
||||
cfg := &packet.Config{
|
||||
RSABits: 4096,
|
||||
DefaultHash: crypto.SHA256,
|
||||
DefaultCipher: packet.CipherAES256,
|
||||
}
|
||||
|
||||
e, err := openpgp.NewEntity("", "Automatically generated Debian Registry Key; created "+time.Now().UTC().Format(time.RFC3339), "", cfg)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func LinkToRepository(ctx context.Context, pkg *packages_model.Package, repo *re
|
||||
return util.ErrInvalidArgument
|
||||
}
|
||||
|
||||
perms, err := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||
perms, err := access_model.GetDoerRepoPermission(ctx, repo, doer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err)
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer
|
||||
return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err)
|
||||
}
|
||||
if err == nil {
|
||||
perms, err := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||
perms, err := access_model.GetDoerRepoPermission(ctx, repo, doer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/globallock"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
@@ -32,6 +33,12 @@ var (
|
||||
ErrQuotaTotalCount = errors.New("maximum allowed package count exceeded")
|
||||
)
|
||||
|
||||
type Specialization interface {
|
||||
OnBeforeRemovePackageAll(ctx context.Context, doer *user_model.User, pkg *packages_model.Package, pds []*packages_model.PackageDescriptor) error
|
||||
OnBeforeRemovePackageVersion(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) error
|
||||
GetViewPackageVersionData(ctx context.Context, pd *packages_model.PackageDescriptor) (any, error)
|
||||
}
|
||||
|
||||
// PackageInfo describes a package
|
||||
type PackageInfo struct {
|
||||
Owner *user_model.User
|
||||
@@ -72,8 +79,13 @@ func CreatePackageAndAddFile(ctx context.Context, pvci *PackageCreationInfo, pfc
|
||||
}
|
||||
|
||||
// CreatePackageOrAddFileToExisting creates a package with a file or adds the file if the package exists already
|
||||
func CreatePackageOrAddFileToExisting(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
|
||||
return createPackageAndAddFile(ctx, pvci, pfci, true)
|
||||
func CreatePackageOrAddFileToExisting(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo) (pv *packages_model.PackageVersion, pf *packages_model.PackageFile, err error) {
|
||||
lockKey := fmt.Sprintf("pkg-upsert-%v-%v-%v", pvci.PackageType, pvci.Name, pvci.Version)
|
||||
err = globallock.LockAndDo(ctx, lockKey, func(ctx context.Context) error {
|
||||
pv, pf, err = createPackageAndAddFile(ctx, pvci, pfci, true)
|
||||
return err
|
||||
})
|
||||
return pv, pf, err
|
||||
}
|
||||
|
||||
func createPackageAndAddFile(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
|
||||
@@ -394,6 +406,8 @@ func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p
|
||||
typeSpecificSize = setting.Packages.LimitSizeRubyGems
|
||||
case packages_model.TypeSwift:
|
||||
typeSpecificSize = setting.Packages.LimitSizeSwift
|
||||
case packages_model.TypeTerraformState:
|
||||
typeSpecificSize = setting.Packages.LimitSizeTerraformState
|
||||
case packages_model.TypeVagrant:
|
||||
typeSpecificSize = setting.Packages.LimitSizeVagrant
|
||||
}
|
||||
@@ -473,7 +487,11 @@ func RemovePackageVersion(ctx context.Context, doer *user_model.User, pv *packag
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := GetSpecManager().Get(pd.Package.Type).OnBeforeRemovePackageVersion(ctx, doer, pd); err != nil {
|
||||
return err
|
||||
}
|
||||
// HINT: PACKAGE-DEFER-STORAGE-DELETE: Blobs are not deleted immediately, instead they are deleted by the cleanup_packages cron task.
|
||||
// If there are no more versions for the package, the same task removes that as well.
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
log.Trace("Deleting package: %v", pv.ID)
|
||||
return DeletePackageVersionAndReferences(ctx, pv)
|
||||
@@ -532,16 +550,11 @@ func DeletePackageVersionAndReferences(ctx context.Context, pv *packages_model.P
|
||||
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, pv.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID)
|
||||
if err != nil {
|
||||
if err := packages_model.DeleteFilePropertiesByVersionID(ctx, pv.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pf := range pfs {
|
||||
if err := DeletePackageFile(ctx, pf); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := packages_model.DeleteFilesByVersionID(ctx, pv.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return packages_model.DeleteVersionByID(ctx, pv.ID)
|
||||
@@ -599,7 +612,7 @@ func OpenBlobStream(pb *packages_model.PackageBlob) (io.ReadSeekCloser, error) {
|
||||
|
||||
// OpenBlobForDownload returns the content of the specific package blob and increases the download counter.
|
||||
// If the storage supports direct serving and it's enabled, only the direct serving url is returned.
|
||||
func OpenBlobForDownload(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, method string, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
func OpenBlobForDownload(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, method string, serveDirectReqParams *storage.ServeDirectOptions) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
key := packages_module.BlobHash256Key(pb.HashSHA256)
|
||||
|
||||
cs := packages_module.NewContentStore()
|
||||
@@ -629,6 +642,50 @@ func OpenBlobForDownload(ctx context.Context, pf *packages_model.PackageFile, pb
|
||||
return s, u, pf, nil
|
||||
}
|
||||
|
||||
// RemovePackage deletes the package and all its versions
|
||||
func RemovePackage(ctx context.Context, doer *user_model.User, p *packages_model.Package) error {
|
||||
pds, err := packages_model.GetAllPackageDescriptors(ctx, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := GetSpecManager().Get(p.Type).OnBeforeRemovePackageAll(ctx, doer, p, pds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// HINT: PACKAGE-DEFER-STORAGE-DELETE: Blobs are not deleted immediately, instead they are deleted by cleanup_packages cron task.
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
err := packages_model.DeletePropertiesByPackageID(ctx, packages_model.PropertyTypePackage, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeletePropertiesByPackageID(ctx, packages_model.PropertyTypeFile, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeletePropertiesByPackageID(ctx, packages_model.PropertyTypeVersion, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeleteFilesByPackageID(ctx, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeleteVersionsByPackageID(ctx, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return packages_model.DeletePackageByID(ctx, p.ID)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pd := range pds {
|
||||
notify_service.PackageDelete(ctx, doer, pd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllPackages for User
|
||||
func RemoveAllPackages(ctx context.Context, userID int64) (int, error) {
|
||||
count := 0
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package pkgspec
|
||||
|
||||
import (
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
"code.gitea.io/gitea/services/packages/terraform"
|
||||
)
|
||||
|
||||
func InitManager() error {
|
||||
mgr := packages_service.GetSpecManager()
|
||||
mgr.Add(packages_model.TypeTerraformState, &terraform.Specialization{})
|
||||
// TODO: add more in the future, refactor the existing code to use this approach
|
||||
return nil
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -241,15 +242,22 @@ func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, group stri
|
||||
return err
|
||||
}
|
||||
|
||||
data := []*repoData{primary, filelists, other}
|
||||
|
||||
updates := collectUpdateInfoUpdates(pfs, cache)
|
||||
if len(updates) > 0 {
|
||||
updateInfo, err := buildUpdateInfo(ctx, pv, updates, group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, updateInfo)
|
||||
}
|
||||
|
||||
return buildRepomd(
|
||||
ctx,
|
||||
pv,
|
||||
ownerID,
|
||||
[]*repoData{
|
||||
primary,
|
||||
filelists,
|
||||
other,
|
||||
},
|
||||
data,
|
||||
group,
|
||||
)
|
||||
}
|
||||
@@ -563,6 +571,93 @@ func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*p
|
||||
}, group)
|
||||
}
|
||||
|
||||
func collectUpdateInfoUpdates(pfs []*packages_model.PackageFile, c packageCache) (updates []*rpm_module.Update) {
|
||||
seenVersions := make(map[int64]bool)
|
||||
for _, pf := range pfs {
|
||||
pd := c[pf]
|
||||
if pd.Version != nil && !seenVersions[pd.Version.ID] && pd.VersionMetadata.Updates != nil {
|
||||
updates = append(updates, pd.VersionMetadata.Updates...)
|
||||
seenVersions[pd.Version.ID] = true
|
||||
}
|
||||
}
|
||||
return updates
|
||||
}
|
||||
|
||||
// buildUpdateInfo builds the updateinfo.xml file
|
||||
func buildUpdateInfo(ctx context.Context, pv *packages_model.PackageVersion, updates []*rpm_module.Update, group string) (*repoData, error) {
|
||||
// Group updates by ID to merge package lists
|
||||
type updateKey struct {
|
||||
ID string
|
||||
}
|
||||
updateMap := make(map[updateKey]*rpm_module.Update)
|
||||
|
||||
for _, u := range updates {
|
||||
key := updateKey{ID: u.ID}
|
||||
if existing, ok := updateMap[key]; ok {
|
||||
for _, newColl := range u.PkgList {
|
||||
collFound := false
|
||||
for j, existingColl := range existing.PkgList {
|
||||
if existingColl.Short == newColl.Short {
|
||||
for _, newPkg := range newColl.Packages {
|
||||
pkgFound := false
|
||||
for _, existingPkg := range existingColl.Packages {
|
||||
if existingPkg.Name == newPkg.Name &&
|
||||
existingPkg.Version == newPkg.Version &&
|
||||
existingPkg.Release == newPkg.Release &&
|
||||
existingPkg.Arch == newPkg.Arch {
|
||||
pkgFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !pkgFound {
|
||||
existing.PkgList[j].Packages = append(existing.PkgList[j].Packages, newPkg)
|
||||
}
|
||||
}
|
||||
collFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !collFound {
|
||||
collCopy := *newColl
|
||||
collCopy.Packages = append([]*rpm_module.UpdatePackage(nil), newColl.Packages...)
|
||||
existing.PkgList = append(existing.PkgList, &collCopy)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create a shallow copy so we don't mutate the original cached pointer
|
||||
uCopy := *u
|
||||
// Deep copy PkgList and Collections to avoid mutating cache
|
||||
// Note: References is shallow-copied, but safe as long as it remains immutable
|
||||
uCopy.PkgList = make([]*rpm_module.Collection, len(u.PkgList))
|
||||
for i, coll := range u.PkgList {
|
||||
collCopy := *coll
|
||||
collCopy.Packages = append([]*rpm_module.UpdatePackage(nil), coll.Packages...)
|
||||
uCopy.PkgList[i] = &collCopy
|
||||
}
|
||||
updateMap[key] = &uCopy
|
||||
}
|
||||
}
|
||||
|
||||
var mergedUpdates []*rpm_module.Update
|
||||
for _, u := range updateMap {
|
||||
mergedUpdates = append(mergedUpdates, u)
|
||||
}
|
||||
slices.SortFunc(mergedUpdates, func(a, b *rpm_module.Update) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
type updateInfo struct {
|
||||
XMLName xml.Name `xml:"updates"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Updates []*rpm_module.Update `xml:"update"`
|
||||
}
|
||||
|
||||
return addDataAsFileToRepo(ctx, pv, "updateinfo", &updateInfo{
|
||||
Xmlns: "http://linux.duke.edu/metadata/updateinfo",
|
||||
Updates: mergedUpdates,
|
||||
}, group)
|
||||
}
|
||||
|
||||
// writtenCounter counts all written bytes
|
||||
type writtenCounter struct {
|
||||
written int64
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
type nop struct{}
|
||||
|
||||
func (n *nop) GetViewPackageVersionData(ctx context.Context, pd *packages_model.PackageDescriptor) (any, error) {
|
||||
return nil, nil //nolint:nilnil // no data, no error
|
||||
}
|
||||
|
||||
func (n *nop) OnBeforeRemovePackageAll(ctx context.Context, doer *user_model.User, pkg *packages_model.Package, pds []*packages_model.PackageDescriptor) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nop) OnBeforeRemovePackageVersion(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Specialization = (*nop)(nil)
|
||||
|
||||
type SpecManagerType struct {
|
||||
specMap map[packages_model.Type]Specialization
|
||||
}
|
||||
|
||||
func (m *SpecManagerType) Add(t packages_model.Type, spec Specialization) {
|
||||
m.specMap[t] = spec
|
||||
}
|
||||
|
||||
func (m *SpecManagerType) Get(t packages_model.Type) Specialization {
|
||||
if len(m.specMap) == 0 {
|
||||
panic("specialization not initialized")
|
||||
}
|
||||
spec := m.specMap[t]
|
||||
if spec == nil {
|
||||
return &nop{}
|
||||
}
|
||||
return spec
|
||||
}
|
||||
|
||||
var GetSpecManager = sync.OnceValue(func() *SpecManagerType {
|
||||
return &SpecManagerType{specMap: make(map[packages_model.Type]Specialization)}
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
terraform_module "code.gitea.io/gitea/modules/packages/terraform"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
type Specialization struct{}
|
||||
|
||||
var _ packages_service.Specialization = (*Specialization)(nil)
|
||||
|
||||
func (s Specialization) GetViewPackageVersionData(ctx context.Context, pd *packages_model.PackageDescriptor) (any, error) {
|
||||
var ret struct {
|
||||
IsLatestVersion bool
|
||||
TerraformLock *terraform_module.LockInfo
|
||||
}
|
||||
latestPvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
PackageID: pd.Package.ID,
|
||||
IsInternal: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
isLatest := len(latestPvs) > 0 && latestPvs[0].ID == pd.Version.ID
|
||||
ret.IsLatestVersion = isLatest
|
||||
|
||||
if isLatest {
|
||||
lockInfo, err := terraform_module.GetLock(ctx, pd.Package.ID)
|
||||
if err != nil {
|
||||
return ret, nil
|
||||
}
|
||||
if lockInfo.IsLocked() {
|
||||
ret.TerraformLock = &lockInfo
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s Specialization) OnBeforeRemovePackageAll(ctx context.Context, doer *user_model.User, pkg *packages_model.Package, pds []*packages_model.PackageDescriptor) error {
|
||||
locked, err := IsLocked(ctx, pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if locked {
|
||||
return util.ErrorWrapTranslatable(
|
||||
util.ErrorWrap(util.ErrUnprocessableContent, "terraform state is locked and cannot be deleted"),
|
||||
"packages.terraform.delete.locked",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Specialization) OnBeforeRemovePackageVersion(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) error {
|
||||
locked, err := IsLocked(ctx, pd.Package)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if locked {
|
||||
return util.ErrorWrapTranslatable(
|
||||
util.ErrorWrap(util.ErrUnprocessableContent, "terraform state is locked and cannot be deleted"),
|
||||
"packages.terraform.delete.locked",
|
||||
)
|
||||
}
|
||||
|
||||
latest, err := IsLatest(ctx, pd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if latest {
|
||||
return util.ErrorWrapTranslatable(
|
||||
util.ErrorWrap(util.ErrUnprocessableContent, "the latest version of a Terraform state cannot be deleted"),
|
||||
"packages.terraform.delete.latest",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
terraform_module "code.gitea.io/gitea/modules/packages/terraform"
|
||||
)
|
||||
|
||||
// IsLocked is a helper function to check if the terraform state is locked
|
||||
func IsLocked(ctx context.Context, pkg *packages_model.Package) (bool, error) {
|
||||
// Non terraform state packages aren't handled here
|
||||
if pkg.Type == packages_model.TypeTerraformState {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
lock, err := terraform_module.GetLock(ctx, pkg.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return lock.IsLocked(), nil
|
||||
}
|
||||
|
||||
// IsLatest is a helper function to check if the terraform state is the latest version
|
||||
func IsLatest(ctx context.Context, pd *packages_model.PackageDescriptor) (bool, error) {
|
||||
if pd.Package.Type == packages_model.TypeTerraformState {
|
||||
return false, nil
|
||||
}
|
||||
latestPvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
PackageID: pd.Package.ID,
|
||||
IsInternal: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(latestPvs) > 0 && latestPvs[0].ID == pd.Version.ID {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
Reference in New Issue
Block a user