Skip to content

Commit cd5e9df

Browse files
diana.strebkova@t-systems.comdianaStr7
diana.strebkova@t-systems.com
authored andcommitted
Added handling for artifacts with classifiers and added debug mode which will list files for deletion
1 parent e23b1fe commit cd5e9df

File tree

5 files changed

+126
-76
lines changed

5 files changed

+126
-76
lines changed

custom/conf/app.example.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,6 +2633,8 @@ LEVEL = Info
26332633
;; Cleanup expired packages/data then targets the files within all maven snapshots versions
26342634
;RETAIN_MAVEN_SNAPSHOT_BUILDS = -1
26352635
;; Maximum size of a npm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
2636+
; Enable debug logging for Maven cleanup. Enabling debug will stop snapshot version artifacts from being deleted but will log the files which were meant for deletion.
2637+
; DEBUG_MAVEN_CLEANUP = true
26362638
;LIMIT_SIZE_NPM = -1
26372639
;; Maximum size of a NuGet upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
26382640
;LIMIT_SIZE_NUGET = -1

models/packages/package_file.go

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"time"
1313

1414
"code.gitea.io/gitea/models/db"
15-
"code.gitea.io/gitea/modules/log"
1615
"code.gitea.io/gitea/modules/timeutil"
1716
"code.gitea.io/gitea/modules/util"
1817

@@ -24,6 +23,8 @@ func init() {
2423
}
2524

2625
var (
26+
// ErrMetadataFile indicated a metadata file
27+
ErrMetadataFile = errors.New("metadata file")
2728
// ErrDuplicatePackageFile indicates a duplicated package file error
2829
ErrDuplicatePackageFile = util.NewAlreadyExistErrorf("package file already exists")
2930
// ErrPackageFileNotExist indicates a package file not exist error
@@ -229,57 +230,69 @@ func HasFiles(ctx context.Context, opts *PackageFileSearchOptions) (bool, error)
229230
return db.Exist[PackageFile](ctx, opts.toConds())
230231
}
231232

232-
// GetFilesByBuildNumber retrieves all files for a package version with build numbers <= maxBuildNumber.
233-
func GetFilesByBuildNumber(ctx context.Context, versionID int64, maxBuildNumber int) ([]*PackageFile, error) {
234-
if maxBuildNumber < 0 {
235-
return nil, errors.New("maxBuildNumber must be a non-negative integer")
233+
// GetFilesBelowBuildNumber retrieves all files for maven snapshot version where the build number is <= maxBuildNumber.
234+
// Returns two slices: one for filtered files and one for skipped files.
235+
func GetFilesBelowBuildNumber(ctx context.Context, versionID int64, maxBuildNumber int, classifiers ...string) ([]*PackageFile, []*PackageFile, error) {
236+
if maxBuildNumber <= 0 {
237+
return nil, nil, errors.New("maxBuildNumber must be a positive integer")
236238
}
237239

238240
files, err := GetFilesByVersionID(ctx, versionID)
239241
if err != nil {
240-
return nil, fmt.Errorf("failed to retrieve files: %w", err)
242+
return nil, nil, fmt.Errorf("failed to retrieve files: %w", err)
241243
}
242244

243-
var filteredFiles []*PackageFile
245+
var filteredFiles, skippedFiles []*PackageFile
244246
for _, file := range files {
245-
buildNumber, err := extractBuildNumberFromFileName(file.Name)
247+
buildNumber, err := extractBuildNumberFromFileName(file.Name, classifiers...)
246248
if err != nil {
247-
if err.Error() == "metadata file" {
248-
continue
249+
if !errors.Is(err, ErrMetadataFile) {
250+
skippedFiles = append(skippedFiles, file)
249251
}
250-
log.Warn("Failed to extract build number from file name '%s': %v", file.Name, err)
251252
continue
252253
}
253-
254254
if buildNumber <= maxBuildNumber {
255255
filteredFiles = append(filteredFiles, file)
256256
}
257257
}
258258

259-
log.Info("Filtered %d files out of %d total files for version ID %d with maxBuildNumber %d", len(filteredFiles), len(files), versionID, maxBuildNumber)
260-
return filteredFiles, nil
259+
return filteredFiles, skippedFiles, nil
261260
}
262261

263-
// extractBuildNumberFromFileName extracts the build number from the file name.
264-
func extractBuildNumberFromFileName(filename string) (int, error) {
265-
// Skip metadata files
262+
// extractBuildNumberFromFileName extracts the build number from a Maven snapshot file name.
263+
// Expected formats:
264+
//
265+
// "artifact-1.0.0-20250311.083409-9.tgz" returns 9
266+
// "artifact-to-test-2.0.0-20250311.083409-10-sources.tgz" returns 10
267+
func extractBuildNumberFromFileName(filename string, classifiers ...string) (int, error) {
266268
if strings.Contains(filename, "maven-metadata.xml") {
267-
return 0, errors.New("metadata file")
269+
return 0, ErrMetadataFile
268270
}
269271

270-
// Split filename by hyphens to extract the build number
271-
parts := strings.Split(filename, "-")
272-
if len(parts) < 3 {
273-
return 0, fmt.Errorf("invalid file name format: '%s'", filename)
272+
dotIdx := strings.LastIndex(filename, ".")
273+
if dotIdx == -1 {
274+
return 0, fmt.Errorf("extract build number from filename: no file extension found in '%s'", filename)
275+
}
276+
base := filename[:dotIdx]
277+
278+
// Remove classifier suffix if present.
279+
for _, classifier := range classifiers {
280+
suffix := "-" + classifier
281+
if strings.HasSuffix(base, suffix) {
282+
base = base[:len(base)-len(suffix)]
283+
break
284+
}
274285
}
275286

276-
// Extract the last part before the extension
277-
buildNumberWithExt := parts[len(parts)-1]
278-
buildNumberStr := strings.Split(buildNumberWithExt, ".")[0]
279-
287+
// The build number should be the token after the last dash.
288+
lastDash := strings.LastIndex(base, "-")
289+
if lastDash == -1 {
290+
return 0, fmt.Errorf("extract build number from filename: invalid file name format in '%s'", filename)
291+
}
292+
buildNumberStr := base[lastDash+1:]
280293
buildNumber, err := strconv.Atoi(buildNumberStr)
281294
if err != nil {
282-
return 0, fmt.Errorf("failed to convert maven package build number to integer: '%s'", buildNumberStr)
295+
return 0, fmt.Errorf("extract build number from filename: failed to convert build number '%s' to integer in '%s': %v", buildNumberStr, filename, err)
283296
}
284297

285298
return buildNumber, nil

modules/packages/maven/metadata.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/xml"
88
"errors"
99
"io"
10+
"strconv"
1011

1112
"code.gitea.io/gitea/modules/util"
1213
"code.gitea.io/gitea/modules/validation"
@@ -32,6 +33,12 @@ type Dependency struct {
3233
Version string `json:"version,omitempty"`
3334
}
3435

36+
// SnapshotMetadata struct holds the build number and the list of classifiers for a snapshot version
37+
type SnapshotMetadata struct {
38+
BuildNumber int `json:"build_number,omitempty"`
39+
Classifiers []string `json:"classifiers,omitempty"`
40+
}
41+
3542
type pomStruct struct {
3643
XMLName xml.Name `xml:"project"`
3744

@@ -62,7 +69,7 @@ type pomStruct struct {
6269
} `xml:"dependencies>dependency"`
6370
}
6471

65-
type MavenMetadata struct {
72+
type snapshotMetadataStruct struct {
6673
XMLName xml.Name `xml:"metadata"`
6774
GroupID string `xml:"groupId"`
6875
ArtifactID string `xml:"artifactId"`
@@ -74,11 +81,10 @@ type MavenMetadata struct {
7481
BuildNumber string `xml:"buildNumber"`
7582
} `xml:"snapshot"`
7683
SnapshotVersions []struct {
77-
SnapshotVersion struct {
78-
Extension string `xml:"extension"`
79-
Value string `xml:"value"`
80-
Updated string `xml:"updated"`
81-
} `xml:"snapshotVersion"`
84+
Extension string `xml:"extension"`
85+
Classifier string `xml:"classifier"`
86+
Value string `xml:"value"`
87+
Updated string `xml:"updated"`
8288
} `xml:"snapshotVersions>snapshotVersion"`
8389
} `xml:"versioning"`
8490
}
@@ -132,19 +138,30 @@ func ParsePackageMetaData(r io.Reader) (*Metadata, error) {
132138
}, nil
133139
}
134140

135-
// ParseMavenMetadata parses the Maven metadata XML to extract the build number.
136-
func ParseMavenMetaData(r io.Reader) (string, error) {
137-
var metadata MavenMetadata
141+
// ParseSnapshotVersionMetadata parses the Maven Snapshot Version metadata to extract the build number and list of available classifiers.
142+
func ParseSnapshotVersionMetaData(r io.Reader) (*SnapshotMetadata, error) {
143+
var metadata snapshotMetadataStruct
138144

139145
dec := xml.NewDecoder(r)
140-
dec.CharsetReader = charset.NewReaderLabel // Assuming charset.NewReaderLabel is a function you've set up to handle character encoding.
146+
dec.CharsetReader = charset.NewReaderLabel
141147
if err := dec.Decode(&metadata); err != nil {
142-
return "", err
148+
return nil, err
143149
}
144150

145-
if metadata.Versioning.Snapshot.BuildNumber == "" {
146-
return "", errors.New("no build number in snapshot metadata found")
151+
buildNumber, err := strconv.Atoi(metadata.Versioning.Snapshot.BuildNumber)
152+
if err != nil {
153+
return nil, errors.New("invalid or missing build number in snapshot metadata")
147154
}
148155

149-
return metadata.Versioning.Snapshot.BuildNumber, nil
156+
var classifiers []string
157+
for _, snapshotVersion := range metadata.Versioning.SnapshotVersions {
158+
if snapshotVersion.Classifier != "" {
159+
classifiers = append(classifiers, snapshotVersion.Classifier)
160+
}
161+
}
162+
163+
return &SnapshotMetadata{
164+
BuildNumber: buildNumber,
165+
Classifiers: classifiers,
166+
}, nil
150167
}

modules/setting/packages.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var (
4343

4444
DefaultRPMSignEnabled bool
4545
RetainMavenSnapshotBuilds int
46+
DebugMavenCleanup bool
4647
}{
4748
Enabled: true,
4849
LimitTotalOwnerCount: -1,
@@ -91,6 +92,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
9192
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
9293
Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false)
9394
Packages.RetainMavenSnapshotBuilds = sec.Key("RETAIN_MAVEN_SNAPSHOT_BUILDS").MustInt(Packages.RetainMavenSnapshotBuilds)
95+
Packages.DebugMavenCleanup = sec.Key("DEBUG_MAVEN_CLEANUP").MustBool(true)
9496
return nil
9597
}
9698

services/packages/maven/cleanup.go

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package maven
33
import (
44
"context"
55
"fmt"
6-
"strconv"
76
"strings"
87

98
"code.gitea.io/gitea/models/packages"
@@ -13,105 +12,122 @@ import (
1312
packages_service "code.gitea.io/gitea/services/packages"
1413
)
1514

16-
// CleanupSnapshotVersion removes outdated files for SNAPHOT versions for all Maven packages.
15+
// CleanupSnapshotVersions removes outdated files for SNAPHOT versions for all Maven packages.
1716
func CleanupSnapshotVersions(ctx context.Context) error {
1817
retainBuilds := setting.Packages.RetainMavenSnapshotBuilds
19-
log.Info("Starting CleanupSnapshotVersion with retainBuilds: %d", retainBuilds)
18+
debugSession := setting.Packages.DebugMavenCleanup
19+
log.Debug("Starting Maven CleanupSnapshotVersions with retainBuilds: %d, debugSession: %t", retainBuilds, debugSession)
2020

2121
if retainBuilds == -1 {
22-
log.Info("CleanupSnapshotVersion skipped because retainBuilds is set to -1")
22+
log.Info("Maven CleanupSnapshotVersions skipped because retainBuilds is set to -1")
2323
return nil
2424
}
2525

2626
if retainBuilds < 1 {
27-
return fmt.Errorf("forbidden value for retainBuilds: %d. Minimum 1 build should be retained", retainBuilds)
27+
return fmt.Errorf("Maven CleanupSnapshotVersions: forbidden value for retainBuilds: %d. Minimum 1 build should be retained", retainBuilds)
2828
}
2929

3030
versions, err := packages.GetVersionsByPackageType(ctx, 0, packages.TypeMaven)
3131
if err != nil {
32-
return fmt.Errorf("failed to retrieve Maven package versions: %w", err)
32+
return fmt.Errorf("Maven CleanupSnapshotVersions: failed to retrieve Maven package versions: %w", err)
3333
}
3434

35-
for _, version := range versions {
36-
log.Info("Processing version: %s (ID: %d)", version.Version, version.ID)
35+
var errors []error
3736

37+
for _, version := range versions {
3838
if !isSnapshotVersion(version.Version) {
39-
log.Info("Skipping non-SNAPSHOT version: %s (ID: %d)", version.Version, version.ID)
4039
continue
4140
}
4241

43-
if err := cleanSnapshotFiles(ctx, version.ID, retainBuilds); err != nil {
44-
log.Error("Failed to clean up snapshot files for version '%s' (ID: %d): %v", version.Version, version.ID, err)
45-
return err
42+
if err := cleanSnapshotFiles(ctx, version.ID, retainBuilds, debugSession); err != nil {
43+
errors = append(errors, fmt.Errorf("Maven CleanupSnapshotVersions: version '%s' (ID: %d): %w", version.Version, version.ID, err))
44+
}
45+
}
46+
47+
if len(errors) > 0 {
48+
for _, err := range errors {
49+
log.Warn("Maven CleanupSnapshotVersions: Error during cleanup: %v", err)
4650
}
51+
return fmt.Errorf("Maven CleanupSnapshotVersions: cleanup completed with errors: %v", errors)
4752
}
4853

49-
log.Info("Completed CleanupSnapshotVersion")
54+
log.Debug("Completed Maven CleanupSnapshotVersions")
5055
return nil
5156
}
5257

5358
func isSnapshotVersion(version string) bool {
5459
return strings.HasSuffix(version, "-SNAPSHOT")
5560
}
5661

57-
func cleanSnapshotFiles(ctx context.Context, versionID int64, retainBuilds int) error {
58-
log.Info("Starting cleanSnapshotFiles for versionID: %d with retainBuilds: %d", versionID, retainBuilds)
62+
func cleanSnapshotFiles(ctx context.Context, versionID int64, retainBuilds int, debugSession bool) error {
63+
log.Debug("Starting Maven cleanSnapshotFiles for versionID: %d with retainBuilds: %d, debugSession: %t", versionID, retainBuilds, debugSession)
5964

6065
metadataFile, err := packages.GetFileForVersionByName(ctx, versionID, "maven-metadata.xml", packages.EmptyFileKey)
6166
if err != nil {
62-
return fmt.Errorf("failed to retrieve Maven metadata file for version ID %d: %w", versionID, err)
67+
return fmt.Errorf("cleanSnapshotFiles: failed to retrieve Maven metadata file for version ID %d: %w", versionID, err)
6368
}
6469

65-
maxBuildNumber, err := extractMaxBuildNumberFromMetadata(ctx, metadataFile)
70+
maxBuildNumber, classifiers, err := extractMaxBuildNumber(ctx, metadataFile)
6671
if err != nil {
67-
return fmt.Errorf("failed to extract max build number from maven-metadata.xml for version ID %d: %w", versionID, err)
72+
return fmt.Errorf("cleanSnapshotFiles: failed to extract max build number from maven-metadata.xml for version ID %d: %w", versionID, err)
6873
}
6974

70-
log.Info("Max build number for versionID %d: %d", versionID, maxBuildNumber)
71-
7275
thresholdBuildNumber := maxBuildNumber - retainBuilds
7376
if thresholdBuildNumber <= 0 {
74-
log.Info("No files to clean up, as the threshold build number is less than or equal to zero for versionID %d", versionID)
77+
log.Debug("cleanSnapshotFiles: No files to clean up, as the threshold build number is less than or equal to zero for versionID %d", versionID)
7578
return nil
7679
}
7780

78-
filesToRemove, err := packages.GetFilesByBuildNumber(ctx, versionID, thresholdBuildNumber)
81+
filesToRemove, skippedFiles, err := packages.GetFilesBelowBuildNumber(ctx, versionID, thresholdBuildNumber, classifiers...)
7982
if err != nil {
80-
return fmt.Errorf("failed to retrieve files for version ID %d: %w", versionID, err)
83+
return fmt.Errorf("cleanSnapshotFiles: failed to retrieve files for version ID %d: %w", versionID, err)
84+
}
85+
86+
if debugSession {
87+
var fileNamesToRemove, skippedFileNames []string
88+
89+
for _, file := range filesToRemove {
90+
fileNamesToRemove = append(fileNamesToRemove, file.Name)
91+
}
92+
93+
for _, file := range skippedFiles {
94+
skippedFileNames = append(skippedFileNames, file.Name)
95+
}
96+
97+
log.Info("cleanSnapshotFiles: Debug session active. Files to remove: %v, Skipped files: %v", fileNamesToRemove, skippedFileNames)
98+
return nil
8199
}
82100

83101
for _, file := range filesToRemove {
84102
log.Debug("Removing file '%s' below threshold %d", file.Name, thresholdBuildNumber)
85103
if err := packages_service.DeletePackageFile(ctx, file); err != nil {
86-
return fmt.Errorf("failed to delete file '%s': %w", file.Name, err)
104+
return fmt.Errorf("Maven cleanSnapshotFiles: failed to delete file '%s': %w", file.Name, err)
87105
}
88106
}
89107

90-
log.Info("Completed cleanSnapshotFiles for versionID: %d", versionID)
108+
log.Debug("Completed Maven cleanSnapshotFiles for versionID: %d", versionID)
91109
return nil
92110
}
93111

94-
func extractMaxBuildNumberFromMetadata(ctx context.Context, metadataFile *packages.PackageFile) (int, error) {
112+
func extractMaxBuildNumber(ctx context.Context, metadataFile *packages.PackageFile) (int, []string, error) {
95113
pb, err := packages.GetBlobByID(ctx, metadataFile.BlobID)
96114
if err != nil {
97-
return 0, fmt.Errorf("failed to get package blob: %w", err)
115+
return 0, nil, fmt.Errorf("extractMaxBuildNumber: failed to get package blob: %w", err)
98116
}
99117

100118
content, _, _, err := packages_service.GetPackageBlobStream(ctx, metadataFile, pb, nil, true)
101119
if err != nil {
102-
return 0, fmt.Errorf("failed to get package file stream: %w", err)
120+
return 0, nil, fmt.Errorf("extractMaxBuildNumber: failed to get package file stream: %w", err)
103121
}
104122
defer content.Close()
105123

106-
buildNumberStr, err := maven.ParseMavenMetaData(content)
124+
snapshotMetadata, err := maven.ParseSnapshotVersionMetaData(content)
107125
if err != nil {
108-
return 0, fmt.Errorf("failed to parse maven-metadata.xml: %w", err)
126+
return 0, nil, fmt.Errorf("extractMaxBuildNumber: failed to parse maven-metadata.xml: %w", err)
109127
}
110128

111-
buildNumber, err := strconv.Atoi(buildNumberStr)
112-
if err != nil {
113-
return 0, fmt.Errorf("invalid build number format: %w", err)
114-
}
129+
buildNumber := snapshotMetadata.BuildNumber
130+
classifiers := snapshotMetadata.Classifiers
115131

116-
return buildNumber, nil
132+
return buildNumber, classifiers, nil
117133
}

0 commit comments

Comments
 (0)