2023-05-05 22:33:37 +02:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package rpm
import (
"bytes"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/xml"
"errors"
"fmt"
"io"
2026-04-07 12:39:53 -04:00
"slices"
2023-05-05 22:33:37 +02:00
"strings"
"time"
packages_model "code.gitea.io/gitea/models/packages"
2024-01-19 12:37:10 +01:00
rpm_model "code.gitea.io/gitea/models/packages/rpm"
2023-05-05 22:33:37 +02:00
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
2024-08-06 21:03:33 +08:00
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/packet"
2023-05-05 22:33:37 +02:00
)
// GetOrCreateRepositoryVersion gets or creates the internal repository package
// The RPM registry needs multiple metadata files which are stored in this package.
2023-09-25 15:17:37 +02:00
func GetOrCreateRepositoryVersion ( ctx context . Context , ownerID int64 ) ( * packages_model . PackageVersion , error ) {
return packages_service . GetOrCreateInternalPackageVersion ( ctx , ownerID , packages_model . TypeRpm , rpm_module . RepositoryPackage , rpm_module . RepositoryVersion )
2023-05-05 22:33:37 +02:00
}
// GetOrCreateKeyPair gets or creates the PGP keys used to sign repository metadata files
2023-09-15 08:13:19 +02:00
func GetOrCreateKeyPair ( ctx context . Context , ownerID int64 ) ( string , string , error ) {
priv , err := user_model . GetSetting ( ctx , ownerID , rpm_module . SettingKeyPrivate )
2023-05-05 22:33:37 +02:00
if err != nil && ! errors . Is ( err , util . ErrNotExist ) {
return "" , "" , err
}
2023-09-15 08:13:19 +02:00
pub , err := user_model . GetSetting ( ctx , ownerID , rpm_module . SettingKeyPublic )
2023-05-05 22:33:37 +02:00
if err != nil && ! errors . Is ( err , util . ErrNotExist ) {
return "" , "" , err
}
if priv == "" || pub == "" {
priv , pub , err = generateKeypair ( )
if err != nil {
return "" , "" , err
}
2023-09-15 08:13:19 +02:00
if err := user_model . SetUserSetting ( ctx , ownerID , rpm_module . SettingKeyPrivate , priv ) ; err != nil {
2023-05-05 22:33:37 +02:00
return "" , "" , err
}
2023-09-15 08:13:19 +02:00
if err := user_model . SetUserSetting ( ctx , ownerID , rpm_module . SettingKeyPublic , pub ) ; err != nil {
2023-05-05 22:33:37 +02:00
return "" , "" , err
}
}
return priv , pub , nil
}
func generateKeypair ( ) ( string , string , error ) {
2023-12-05 01:48:01 +01:00
e , err := openpgp . NewEntity ( "" , "RPM Registry" , "" , nil )
2023-05-05 22:33:37 +02:00
if err != nil {
return "" , "" , err
}
var priv strings . Builder
var pub strings . Builder
w , err := armor . Encode ( & priv , openpgp . PrivateKeyType , nil )
if err != nil {
return "" , "" , err
}
if err := e . SerializePrivate ( w , nil ) ; err != nil {
return "" , "" , err
}
w . Close ( )
w , err = armor . Encode ( & pub , openpgp . PublicKeyType , nil )
if err != nil {
return "" , "" , err
}
if err := e . Serialize ( w ) ; err != nil {
return "" , "" , err
}
w . Close ( )
return priv . String ( ) , pub . String ( ) , nil
}
2024-01-19 12:37:10 +01:00
// BuildAllRepositoryFiles (re)builds all repository files for every available group
func BuildAllRepositoryFiles ( ctx context . Context , ownerID int64 ) error {
pv , err := GetOrCreateRepositoryVersion ( ctx , ownerID )
if err != nil {
return err
}
// 1. Delete all existing repository files
pfs , err := packages_model . GetFilesByVersionID ( ctx , pv . ID )
if err != nil {
return err
}
for _ , pf := range pfs {
if err := packages_service . DeletePackageFile ( ctx , pf ) ; err != nil {
return err
}
}
// 2. (Re)Build repository files for existing packages
groups , err := rpm_model . GetGroups ( ctx , ownerID )
if err != nil {
return err
}
for _ , group := range groups {
if err := BuildSpecificRepositoryFiles ( ctx , ownerID , group ) ; err != nil {
return fmt . Errorf ( "failed to build repository files [%s]: %w" , group , err )
}
}
return nil
}
2023-05-05 22:33:37 +02:00
type repoChecksum struct {
Value string ` xml:",chardata" `
Type string ` xml:"type,attr" `
}
type repoLocation struct {
Href string ` xml:"href,attr" `
}
type repoData struct {
Type string ` xml:"type,attr" `
Checksum repoChecksum ` xml:"checksum" `
OpenChecksum repoChecksum ` xml:"open-checksum" `
Location repoLocation ` xml:"location" `
Timestamp int64 ` xml:"timestamp" `
Size int64 ` xml:"size" `
OpenSize int64 ` xml:"open-size" `
}
type packageData struct {
Package * packages_model . Package
Version * packages_model . PackageVersion
Blob * packages_model . PackageBlob
VersionMetadata * rpm_module . VersionMetadata
FileMetadata * rpm_module . FileMetadata
}
type packageCache = map [ * packages_model . PackageFile ] * packageData
2024-01-12 11:16:05 +08:00
// BuildSpecificRepositoryFiles builds metadata files for the repository
2024-01-19 12:37:10 +01:00
func BuildSpecificRepositoryFiles ( ctx context . Context , ownerID int64 , group string ) error {
2023-09-25 15:17:37 +02:00
pv , err := GetOrCreateRepositoryVersion ( ctx , ownerID )
2023-05-05 22:33:37 +02:00
if err != nil {
return err
}
pfs , _ , err := packages_model . SearchFiles ( ctx , & packages_model . PackageFileSearchOptions {
2024-01-12 11:16:05 +08:00
OwnerID : ownerID ,
PackageType : packages_model . TypeRpm ,
Query : "%.rpm" ,
2024-01-19 12:37:10 +01:00
CompositeKey : group ,
2023-05-05 22:33:37 +02:00
} )
if err != nil {
return err
}
// Delete the repository files if there are no packages
if len ( pfs ) == 0 {
pfs , err := packages_model . GetFilesByVersionID ( ctx , pv . ID )
if err != nil {
return err
}
for _ , pf := range pfs {
2023-12-12 14:51:33 +01:00
if err := packages_service . DeletePackageFile ( ctx , pf ) ; err != nil {
2023-05-05 22:33:37 +02:00
return err
}
}
return nil
}
// Cache data needed for all repository files
cache := make ( packageCache )
for _ , pf := range pfs {
pv , err := packages_model . GetVersionByID ( ctx , pf . VersionID )
if err != nil {
return err
}
p , err := packages_model . GetPackageByID ( ctx , pv . PackageID )
if err != nil {
return err
}
pb , err := packages_model . GetBlobByID ( ctx , pf . BlobID )
if err != nil {
return err
}
pps , err := packages_model . GetPropertiesByName ( ctx , packages_model . PropertyTypeFile , pf . ID , rpm_module . PropertyMetadata )
if err != nil {
return err
}
pd := & packageData {
Package : p ,
Version : pv ,
Blob : pb ,
}
if err := json . Unmarshal ( [ ] byte ( pv . MetadataJSON ) , & pd . VersionMetadata ) ; err != nil {
return err
}
if len ( pps ) > 0 {
if err := json . Unmarshal ( [ ] byte ( pps [ 0 ] . Value ) , & pd . FileMetadata ) ; err != nil {
return err
}
}
cache [ pf ] = pd
}
2024-01-19 12:37:10 +01:00
primary , err := buildPrimary ( ctx , pv , pfs , cache , group )
2023-05-05 22:33:37 +02:00
if err != nil {
return err
}
2024-01-19 12:37:10 +01:00
filelists , err := buildFilelists ( ctx , pv , pfs , cache , group )
2023-05-05 22:33:37 +02:00
if err != nil {
return err
}
2024-01-19 12:37:10 +01:00
other , err := buildOther ( ctx , pv , pfs , cache , group )
2023-05-05 22:33:37 +02:00
if err != nil {
return err
}
2026-04-07 12:39:53 -04:00
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 )
}
2023-05-05 22:33:37 +02:00
return buildRepomd (
2023-09-15 08:13:19 +02:00
ctx ,
2023-05-05 22:33:37 +02:00
pv ,
ownerID ,
2026-04-07 12:39:53 -04:00
data ,
2024-01-19 12:37:10 +01:00
group ,
2023-05-05 22:33:37 +02:00
)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml
2024-01-19 12:37:10 +01:00
func buildRepomd ( ctx context . Context , pv * packages_model . PackageVersion , ownerID int64 , data [ ] * repoData , group string ) error {
2023-05-05 22:33:37 +02:00
type Repomd struct {
XMLName xml . Name ` xml:"repomd" `
Xmlns string ` xml:"xmlns,attr" `
XmlnsRpm string ` xml:"xmlns:rpm,attr" `
Data [ ] * repoData ` xml:"data" `
}
var buf bytes . Buffer
2023-08-28 12:43:16 -04:00
buf . WriteString ( xml . Header )
2023-05-05 22:33:37 +02:00
if err := xml . NewEncoder ( & buf ) . Encode ( & Repomd {
Xmlns : "http://linux.duke.edu/metadata/repo" ,
XmlnsRpm : "http://linux.duke.edu/metadata/rpm" ,
Data : data ,
} ) ; err != nil {
return err
}
2023-09-15 08:13:19 +02:00
priv , _ , err := GetOrCreateKeyPair ( ctx , ownerID )
2023-05-05 22:33:37 +02:00
if err != nil {
return err
}
block , err := armor . Decode ( strings . NewReader ( priv ) )
if err != nil {
return err
}
e , err := openpgp . ReadEntity ( packet . NewReader ( block . Body ) )
if err != nil {
return err
}
repomdAscContent , _ := packages_module . NewHashedBuffer ( )
2023-10-25 21:24:24 +02:00
defer repomdAscContent . Close ( )
2023-05-05 22:33:37 +02:00
if err := openpgp . ArmoredDetachSign ( repomdAscContent , e , bytes . NewReader ( buf . Bytes ( ) ) , nil ) ; err != nil {
return err
}
repomdContent , _ := packages_module . CreateHashedBufferFromReader ( & buf )
2023-10-25 21:24:24 +02:00
defer repomdContent . Close ( )
2023-05-05 22:33:37 +02:00
for _ , file := range [ ] struct {
Name string
Data packages_module . HashedSizeReader
} {
{ "repomd.xml" , repomdContent } ,
{ "repomd.xml.asc" , repomdAscContent } ,
} {
_ , err = packages_service . AddFileToPackageVersionInternal (
2023-09-25 15:17:37 +02:00
ctx ,
2023-05-05 22:33:37 +02:00
pv ,
& packages_service . PackageFileCreationInfo {
PackageFileInfo : packages_service . PackageFileInfo {
2024-01-12 11:16:05 +08:00
Filename : file . Name ,
2024-01-19 12:37:10 +01:00
CompositeKey : group ,
2023-05-05 22:33:37 +02:00
} ,
Creator : user_model . NewGhostUser ( ) ,
Data : file . Data ,
IsLead : false ,
OverwriteExisting : true ,
} ,
)
if err != nil {
return err
}
}
return nil
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml
2024-01-19 12:37:10 +01:00
func buildPrimary ( ctx context . Context , pv * packages_model . PackageVersion , pfs [ ] * packages_model . PackageFile , c packageCache , group string ) ( * repoData , error ) {
2023-05-05 22:33:37 +02:00
type Version struct {
Epoch string ` xml:"epoch,attr" `
Version string ` xml:"ver,attr" `
Release string ` xml:"rel,attr" `
}
type Checksum struct {
Checksum string ` xml:",chardata" `
Type string ` xml:"type,attr" `
Pkgid string ` xml:"pkgid,attr" `
}
type Times struct {
File uint64 ` xml:"file,attr" `
Build uint64 ` xml:"build,attr" `
}
type Sizes struct {
Package int64 ` xml:"package,attr" `
Installed uint64 ` xml:"installed,attr" `
Archive uint64 ` xml:"archive,attr" `
}
type Location struct {
Href string ` xml:"href,attr" `
}
type EntryList struct {
Entries [ ] * rpm_module . Entry ` xml:"rpm:entry" `
}
type Format struct {
License string ` xml:"rpm:license" `
Vendor string ` xml:"rpm:vendor" `
Group string ` xml:"rpm:group" `
Buildhost string ` xml:"rpm:buildhost" `
Sourcerpm string ` xml:"rpm:sourcerpm" `
Provides EntryList ` xml:"rpm:provides" `
Requires EntryList ` xml:"rpm:requires" `
Conflicts EntryList ` xml:"rpm:conflicts" `
Obsoletes EntryList ` xml:"rpm:obsoletes" `
Files [ ] * rpm_module . File ` xml:"file" `
}
type Package struct {
XMLName xml . Name ` xml:"package" `
Type string ` xml:"type,attr" `
Name string ` xml:"name" `
Architecture string ` xml:"arch" `
Version Version ` xml:"version" `
Checksum Checksum ` xml:"checksum" `
Summary string ` xml:"summary" `
Description string ` xml:"description" `
Packager string ` xml:"packager" `
URL string ` xml:"url" `
Time Times ` xml:"time" `
Size Sizes ` xml:"size" `
Location Location ` xml:"location" `
Format Format ` xml:"format" `
}
type Metadata struct {
XMLName xml . Name ` xml:"metadata" `
Xmlns string ` xml:"xmlns,attr" `
XmlnsRpm string ` xml:"xmlns:rpm,attr" `
PackageCount int ` xml:"packages,attr" `
Packages [ ] * Package ` xml:"package" `
}
packages := make ( [ ] * Package , 0 , len ( pfs ) )
for _ , pf := range pfs {
pd := c [ pf ]
files := make ( [ ] * rpm_module . File , 0 , 3 )
for _ , f := range pd . FileMetadata . Files {
if f . IsExecutable {
files = append ( files , f )
}
}
packages = append ( packages , & Package {
Type : "rpm" ,
Name : pd . Package . Name ,
Architecture : pd . FileMetadata . Architecture ,
Version : Version {
Epoch : pd . FileMetadata . Epoch ,
2023-07-21 21:41:38 +02:00
Version : pd . FileMetadata . Version ,
2023-05-05 22:33:37 +02:00
Release : pd . FileMetadata . Release ,
} ,
Checksum : Checksum {
Type : "sha256" ,
Checksum : pd . Blob . HashSHA256 ,
Pkgid : "YES" ,
} ,
Summary : pd . VersionMetadata . Summary ,
Description : pd . VersionMetadata . Description ,
Packager : pd . FileMetadata . Packager ,
URL : pd . VersionMetadata . ProjectURL ,
Time : Times {
File : pd . FileMetadata . FileTime ,
Build : pd . FileMetadata . BuildTime ,
} ,
Size : Sizes {
Package : pd . Blob . Size ,
Installed : pd . FileMetadata . InstalledSize ,
Archive : pd . FileMetadata . ArchiveSize ,
} ,
Location : Location {
2025-04-03 21:00:00 +02:00
Href : fmt . Sprintf ( "package/%s/%s/%s/%s-%s.%s.rpm" , pd . Package . Name , pd . Version . Version , pd . FileMetadata . Architecture , pd . Package . Name , pd . Version . Version , pd . FileMetadata . Architecture ) ,
2023-05-05 22:33:37 +02:00
} ,
Format : Format {
License : pd . VersionMetadata . License ,
Vendor : pd . FileMetadata . Vendor ,
Group : pd . FileMetadata . Group ,
Buildhost : pd . FileMetadata . BuildHost ,
Sourcerpm : pd . FileMetadata . SourceRpm ,
Provides : EntryList {
Entries : pd . FileMetadata . Provides ,
} ,
Requires : EntryList {
Entries : pd . FileMetadata . Requires ,
} ,
Conflicts : EntryList {
Entries : pd . FileMetadata . Conflicts ,
} ,
Obsoletes : EntryList {
Entries : pd . FileMetadata . Obsoletes ,
} ,
Files : files ,
} ,
} )
}
2023-09-25 15:17:37 +02:00
return addDataAsFileToRepo ( ctx , pv , "primary" , & Metadata {
2023-05-05 22:33:37 +02:00
Xmlns : "http://linux.duke.edu/metadata/common" ,
XmlnsRpm : "http://linux.duke.edu/metadata/rpm" ,
PackageCount : len ( pfs ) ,
Packages : packages ,
2024-01-19 12:37:10 +01:00
} , group )
2023-05-05 22:33:37 +02:00
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
2025-06-27 15:48:03 +02:00
func buildFilelists ( ctx context . Context , pv * packages_model . PackageVersion , pfs [ ] * packages_model . PackageFile , c packageCache , group string ) ( * repoData , error ) { //nolint:dupl // duplicates with buildOther
2023-05-05 22:33:37 +02:00
type Version struct {
Epoch string ` xml:"epoch,attr" `
Version string ` xml:"ver,attr" `
Release string ` xml:"rel,attr" `
}
type Package struct {
Pkgid string ` xml:"pkgid,attr" `
Name string ` xml:"name,attr" `
Architecture string ` xml:"arch,attr" `
Version Version ` xml:"version" `
Files [ ] * rpm_module . File ` xml:"file" `
}
type Filelists struct {
XMLName xml . Name ` xml:"filelists" `
Xmlns string ` xml:"xmlns,attr" `
PackageCount int ` xml:"packages,attr" `
Packages [ ] * Package ` xml:"package" `
}
packages := make ( [ ] * Package , 0 , len ( pfs ) )
for _ , pf := range pfs {
pd := c [ pf ]
packages = append ( packages , & Package {
Pkgid : pd . Blob . HashSHA256 ,
Name : pd . Package . Name ,
Architecture : pd . FileMetadata . Architecture ,
Version : Version {
Epoch : pd . FileMetadata . Epoch ,
2023-07-21 21:41:38 +02:00
Version : pd . FileMetadata . Version ,
2023-05-05 22:33:37 +02:00
Release : pd . FileMetadata . Release ,
} ,
Files : pd . FileMetadata . Files ,
} )
}
2023-09-25 15:17:37 +02:00
return addDataAsFileToRepo ( ctx , pv , "filelists" , & Filelists {
2023-05-05 22:33:37 +02:00
Xmlns : "http://linux.duke.edu/metadata/other" ,
PackageCount : len ( pfs ) ,
Packages : packages ,
2024-01-19 12:37:10 +01:00
} , group )
2023-05-05 22:33:37 +02:00
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
2025-06-27 15:48:03 +02:00
func buildOther ( ctx context . Context , pv * packages_model . PackageVersion , pfs [ ] * packages_model . PackageFile , c packageCache , group string ) ( * repoData , error ) { //nolint:dupl // duplicates with buildFilelists
2023-05-05 22:33:37 +02:00
type Version struct {
Epoch string ` xml:"epoch,attr" `
Version string ` xml:"ver,attr" `
Release string ` xml:"rel,attr" `
}
type Package struct {
Pkgid string ` xml:"pkgid,attr" `
Name string ` xml:"name,attr" `
Architecture string ` xml:"arch,attr" `
Version Version ` xml:"version" `
Changelogs [ ] * rpm_module . Changelog ` xml:"changelog" `
}
type Otherdata struct {
XMLName xml . Name ` xml:"otherdata" `
Xmlns string ` xml:"xmlns,attr" `
PackageCount int ` xml:"packages,attr" `
Packages [ ] * Package ` xml:"package" `
}
packages := make ( [ ] * Package , 0 , len ( pfs ) )
for _ , pf := range pfs {
pd := c [ pf ]
packages = append ( packages , & Package {
Pkgid : pd . Blob . HashSHA256 ,
Name : pd . Package . Name ,
Architecture : pd . FileMetadata . Architecture ,
Version : Version {
Epoch : pd . FileMetadata . Epoch ,
2023-07-21 21:41:38 +02:00
Version : pd . FileMetadata . Version ,
2023-05-05 22:33:37 +02:00
Release : pd . FileMetadata . Release ,
} ,
Changelogs : pd . FileMetadata . Changelogs ,
} )
}
2023-09-25 15:17:37 +02:00
return addDataAsFileToRepo ( ctx , pv , "other" , & Otherdata {
2023-05-05 22:33:37 +02:00
Xmlns : "http://linux.duke.edu/metadata/other" ,
PackageCount : len ( pfs ) ,
Packages : packages ,
2024-01-19 12:37:10 +01:00
} , group )
2023-05-05 22:33:37 +02:00
}
2026-04-07 12:39:53 -04:00
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 )
}
2023-05-05 22:33:37 +02:00
// writtenCounter counts all written bytes
type writtenCounter struct {
written int64
}
func ( wc * writtenCounter ) Write ( buf [ ] byte ) ( int , error ) {
n := len ( buf )
wc . written += int64 ( n )
return n , nil
}
func ( wc * writtenCounter ) Written ( ) int64 {
return wc . written
}
2024-01-19 12:37:10 +01:00
func addDataAsFileToRepo ( ctx context . Context , pv * packages_model . PackageVersion , filetype string , obj any , group string ) ( * repoData , error ) {
2023-05-05 22:33:37 +02:00
content , _ := packages_module . NewHashedBuffer ( )
2024-01-19 12:37:10 +01:00
defer content . Close ( )
2023-05-05 22:33:37 +02:00
gzw := gzip . NewWriter ( content )
wc := & writtenCounter { }
h := sha256 . New ( )
w := io . MultiWriter ( gzw , wc , h )
_ , _ = w . Write ( [ ] byte ( xml . Header ) )
if err := xml . NewEncoder ( w ) . Encode ( obj ) ; err != nil {
return nil , err
}
if err := gzw . Close ( ) ; err != nil {
return nil , err
}
filename := filetype + ".xml.gz"
_ , err := packages_service . AddFileToPackageVersionInternal (
2023-09-25 15:17:37 +02:00
ctx ,
2023-05-05 22:33:37 +02:00
pv ,
& packages_service . PackageFileCreationInfo {
PackageFileInfo : packages_service . PackageFileInfo {
2024-01-12 11:16:05 +08:00
Filename : filename ,
2024-01-19 12:37:10 +01:00
CompositeKey : group ,
2023-05-05 22:33:37 +02:00
} ,
Creator : user_model . NewGhostUser ( ) ,
Data : content ,
IsLead : false ,
OverwriteExisting : true ,
} ,
)
if err != nil {
return nil , err
}
_ , _ , hashSHA256 , _ := content . Sums ( )
return & repoData {
Type : filetype ,
Checksum : repoChecksum {
Type : "sha256" ,
Value : hex . EncodeToString ( hashSHA256 ) ,
} ,
OpenChecksum : repoChecksum {
Type : "sha256" ,
Value : hex . EncodeToString ( h . Sum ( nil ) ) ,
} ,
Location : repoLocation {
Href : "repodata/" + filename ,
} ,
Timestamp : time . Now ( ) . Unix ( ) ,
Size : content . Size ( ) ,
OpenSize : wc . Written ( ) ,
} , nil
}