|  | 
|  | 1 | +package maven | 
|  | 2 | + | 
|  | 3 | +import ( | 
|  | 4 | +	"context" | 
|  | 5 | +	"fmt" | 
|  | 6 | +	"strconv" | 
|  | 7 | +	"strings" | 
|  | 8 | + | 
|  | 9 | +	"code.gitea.io/gitea/models/packages" | 
|  | 10 | +	"code.gitea.io/gitea/modules/log" | 
|  | 11 | +	"code.gitea.io/gitea/modules/packages/maven" | 
|  | 12 | +	"code.gitea.io/gitea/modules/setting" | 
|  | 13 | +	packages_service "code.gitea.io/gitea/services/packages" | 
|  | 14 | +) | 
|  | 15 | + | 
|  | 16 | +// CleanupSnapshotVersion removes outdated files for SNAPHOT versions for all Maven packages. | 
|  | 17 | +func CleanupSnapshotVersions(ctx context.Context) error { | 
|  | 18 | +	retainBuilds := setting.Packages.RetainMavenSnapshotBuilds | 
|  | 19 | +	log.Info("Starting CleanupSnapshotVersion with retainBuilds: %d", retainBuilds) | 
|  | 20 | + | 
|  | 21 | +	if retainBuilds == -1 { | 
|  | 22 | +		log.Info("CleanupSnapshotVersion skipped because retainBuilds is set to -1") | 
|  | 23 | +		return nil | 
|  | 24 | +	} | 
|  | 25 | + | 
|  | 26 | +	if retainBuilds < 1 { | 
|  | 27 | +		return fmt.Errorf("forbidden value for retainBuilds: %d. Minimum 1 build should be retained", retainBuilds) | 
|  | 28 | +	} | 
|  | 29 | + | 
|  | 30 | +	versions, err := packages.GetVersionsByPackageType(ctx, 0, packages.TypeMaven) | 
|  | 31 | +	if err != nil { | 
|  | 32 | +		return fmt.Errorf("failed to retrieve Maven package versions: %w", err) | 
|  | 33 | +	} | 
|  | 34 | + | 
|  | 35 | +	for _, version := range versions { | 
|  | 36 | +		log.Info("Processing version: %s (ID: %d)", version.Version, version.ID) | 
|  | 37 | + | 
|  | 38 | +		if !isSnapshotVersion(version.Version) { | 
|  | 39 | +			log.Info("Skipping non-SNAPSHOT version: %s (ID: %d)", version.Version, version.ID) | 
|  | 40 | +			continue | 
|  | 41 | +		} | 
|  | 42 | + | 
|  | 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 | 
|  | 46 | +		} | 
|  | 47 | +	} | 
|  | 48 | + | 
|  | 49 | +	log.Info("Completed CleanupSnapshotVersion") | 
|  | 50 | +	return nil | 
|  | 51 | +} | 
|  | 52 | + | 
|  | 53 | +func isSnapshotVersion(version string) bool { | 
|  | 54 | +	return strings.Contains(version, "-SNAPSHOT") | 
|  | 55 | +} | 
|  | 56 | + | 
|  | 57 | +func cleanSnapshotFiles(ctx context.Context, versionID int64, retainBuilds int) error { | 
|  | 58 | +	log.Info("Starting cleanSnapshotFiles for versionID: %d with retainBuilds: %d", versionID, retainBuilds) | 
|  | 59 | + | 
|  | 60 | +	metadataFile, err := packages.GetFileForVersionByName(ctx, versionID, "maven-metadata.xml", packages.EmptyFileKey) | 
|  | 61 | +	if err != nil { | 
|  | 62 | +		return fmt.Errorf("failed to retrieve Maven metadata file for version ID %d: %w", versionID, err) | 
|  | 63 | +	} | 
|  | 64 | + | 
|  | 65 | +	maxBuildNumber, err := extractMaxBuildNumberFromMetadata(ctx, metadataFile) | 
|  | 66 | +	if err != nil { | 
|  | 67 | +		return fmt.Errorf("failed to extract max build number from maven-metadata.xml for version ID %d: %w", versionID, err) | 
|  | 68 | +	} | 
|  | 69 | + | 
|  | 70 | +	log.Info("Max build number for versionID %d: %d", versionID, maxBuildNumber) | 
|  | 71 | + | 
|  | 72 | +	thresholdBuildNumber := maxBuildNumber - retainBuilds | 
|  | 73 | +	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) | 
|  | 75 | +		return nil | 
|  | 76 | +	} | 
|  | 77 | + | 
|  | 78 | +	filesToRemove, err := packages.GetFilesByBuildNumber(ctx, versionID, thresholdBuildNumber) | 
|  | 79 | +	if err != nil { | 
|  | 80 | +		return fmt.Errorf("failed to retrieve files for version ID %d: %w", versionID, err) | 
|  | 81 | +	} | 
|  | 82 | + | 
|  | 83 | +	for _, file := range filesToRemove { | 
|  | 84 | +		log.Debug("Removing file '%s' below threshold %d", file.Name, thresholdBuildNumber) | 
|  | 85 | +		if err := packages_service.DeletePackageFile(ctx, file); err != nil { | 
|  | 86 | +			return fmt.Errorf("failed to delete file '%s': %w", file.Name, err) | 
|  | 87 | +		} | 
|  | 88 | +	} | 
|  | 89 | + | 
|  | 90 | +	log.Info("Completed cleanSnapshotFiles for versionID: %d", versionID) | 
|  | 91 | +	return nil | 
|  | 92 | +} | 
|  | 93 | + | 
|  | 94 | +func extractMaxBuildNumberFromMetadata(ctx context.Context, metadataFile *packages.PackageFile) (int, error) { | 
|  | 95 | +	content, _, _, err := packages_service.GetPackageFileStream(ctx, metadataFile) | 
|  | 96 | +	if err != nil { | 
|  | 97 | +		return 0, fmt.Errorf("failed to get package file stream: %w", err) | 
|  | 98 | +	} | 
|  | 99 | +	defer content.Close() | 
|  | 100 | + | 
|  | 101 | +	buildNumberStr, err := maven.ParseMavenMetaData(content) | 
|  | 102 | +	if err != nil { | 
|  | 103 | +		return 0, fmt.Errorf("failed to parse maven-metadata.xml: %w", err) | 
|  | 104 | +	} | 
|  | 105 | + | 
|  | 106 | +	buildNumber, err := strconv.Atoi(buildNumberStr) | 
|  | 107 | +	if err != nil { | 
|  | 108 | +		return 0, fmt.Errorf("invalid build number format: %w", err) | 
|  | 109 | +	} | 
|  | 110 | + | 
|  | 111 | +	return buildNumber, nil | 
|  | 112 | +} | 
0 commit comments