// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package doctor import ( "context" "fmt" "slices" "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/modules/log" packages_module "code.gitea.io/gitea/modules/packages" nuget_module "code.gitea.io/gitea/modules/packages/nuget" packages_service "code.gitea.io/gitea/services/packages" "xorm.io/builder" ) func init() { Register(&Check{ Title: "Extract Nuget Nuspec Files to content store", Name: "packages-nuget-nuspec", IsDefault: false, Run: PackagesNugetNuspecCheck, Priority: 15, InitStorage: true, }) } func PackagesNugetNuspecCheck(ctx context.Context, logger log.Logger, autofix bool) error { found := 0 fixed := 0 errors := 0 err := db.Iterate(ctx, builder.Eq{"package.type": packages.TypeNuGet, "package.is_internal": false}, func(ctx context.Context, pkg *packages.Package) error { logger.Info("Processing package %s", pkg.Name) pvs, _, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{ Type: packages.TypeNuGet, PackageID: pkg.ID, }) if err != nil { // Should never happen logger.Error("Failed to search for versions for package %s: %v", pkg.Name, err) return err } logger.Info("Found %d versions for package %s", len(pvs), pkg.Name) for _, pv := range pvs { pfs, err := packages.GetFilesByVersionID(ctx, pv.ID) if err != nil { logger.Error("Failed to get files for package version %s %s: %v", pkg.Name, pv.Version, err) errors++ continue } if slices.ContainsFunc(pfs, func(pf *packages.PackageFile) bool { return strings.HasSuffix(pf.LowerName, ".nuspec") }) { logger.Debug("Nuspec file already exists for %s %s", pkg.Name, pv.Version) continue } nupkgIdx := slices.IndexFunc(pfs, func(pf *packages.PackageFile) bool { return pf.IsLead }) if nupkgIdx < 0 { logger.Error("Missing nupkg file for %s %s", pkg.Name, pv.Version) errors++ continue } pf := pfs[nupkgIdx] logger.Warn("Missing nuspec file found for %s %s", pkg.Name, pv.Version) found++ if !autofix { continue } s, _, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { logger.Error("Failed to get nupkg file stream for %s %s: %v", pkg.Name, pv.Version, err) errors++ continue } defer s.Close() buf, err := packages_module.CreateHashedBufferFromReader(s) if err != nil { logger.Error("Failed to create hashed buffer for nupkg from reader for %s %s: %v", pkg.Name, pv.Version, err) errors++ continue } defer buf.Close() np, err := nuget_module.ParsePackageMetaData(buf, buf.Size()) if err != nil { logger.Error("Failed to parse package metadata for %s %s: %v", pkg.Name, pv.Version, err) errors++ continue } nuspecBuf, err := packages_module.CreateHashedBufferFromReaderWithSize(np.NuspecContent, np.NuspecContent.Len()) if err != nil { logger.Error("Failed to create hashed buffer for nuspec from reader for %s %s: %v", pkg.Name, pv.Version, err) errors++ continue } defer nuspecBuf.Close() _, err = packages_service.AddFileToPackageVersionInternal( ctx, pv, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ Filename: fmt.Sprintf("%s.nuspec", pkg.LowerName), }, Data: nuspecBuf, IsLead: false, }, ) if err != nil { logger.Error("Failed to add nuspec file for %s %s: %v", pkg.Name, pv.Version, err) errors++ continue } fixed++ } return nil }) if err != nil { logger.Error("Failed to iterate over users: %v", err) return err } if autofix { if fixed > 0 { logger.Info("Fixed %d package versions by extracting nuspec files", fixed) } else { logger.Info("No package versions with missing nuspec files found") } } else { if found > 0 { logger.Info("Found %d package versions with missing nuspec files", found) } else { logger.Info("No package versions with missing nuspec files found") } } if errors > 0 { return fmt.Errorf("failed to fix %d nuspec files", errors) } return nil }