mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 10:44:12 +01:00 
			
		
		
		
	Fix some RPM registry flaws (#28782)
Related #26984 (https://github.com/go-gitea/gitea/pull/26984#issuecomment-1889588912) Fix admin cleanup message. Fix models `Get` not respecting default values. Rebuild RPM repository files after cleanup. Do not add RPM group to package version name. Force stable sorting of Alpine/Debian/RPM repository data. Fix missing deferred `Close`. Add tests for multiple RPM groups. Removed non-cached `ReplaceAllStringRegex`. If there are multiple groups available, it's stated in the package installation screen: 
This commit is contained in:
		
							parent
							
								
									075c4c89ee
								
							
						
					
					
						commit
						461d8b53c2
					
				@ -24,16 +24,26 @@ The following examples use `dnf`.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Configuring the package registry
 | 
					## Configuring the package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To register the RPM registry add the url to the list of known apt sources:
 | 
					To register the RPM registry add the url to the list of known sources:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo
 | 
					dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Placeholder | Description                                        |
 | 
					| Placeholder | Description |
 | 
				
			||||||
| ----------- |----------------------------------------------------|
 | 
					| ----------- | ----------- |
 | 
				
			||||||
| `owner`     | The owner of the package.                          |
 | 
					| `owner`     | The owner of the package. |
 | 
				
			||||||
| `group`     |  Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.|
 | 
					| `group`     | Optional: Everything, e.g. empty, `el7`, `rocky/el9`, `test/fc38`. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					# without a group
 | 
				
			||||||
 | 
					dnf config-manager --add-repo https://gitea.example.com/api/packages/testuser/rpm.repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# with the group 'centos/el7'
 | 
				
			||||||
 | 
					dnf config-manager --add-repo https://gitea.example.com/api/packages/testuser/rpm/centos/el7.repo
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication):
 | 
					If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,7 +51,7 @@ If the registry is private, provide credentials in the url. You can use a passwo
 | 
				
			|||||||
dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo
 | 
					dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.repos.d` too.
 | 
					You have to add the credentials to the urls in the created `.repo` file in `/etc/yum.repos.d` too.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Publish a package
 | 
					## Publish a package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,11 +64,17 @@ PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload
 | 
				
			|||||||
| Parameter | Description |
 | 
					| Parameter | Description |
 | 
				
			||||||
| --------- | ----------- |
 | 
					| --------- | ----------- |
 | 
				
			||||||
| `owner`   | The owner of the package. |
 | 
					| `owner`   | The owner of the package. |
 | 
				
			||||||
| `group`   |  Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.|
 | 
					| `group`   | Optional: Everything, e.g. empty, `el7`, `rocky/el9`, `test/fc38`. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Example request using HTTP Basic authentication:
 | 
					Example request using HTTP Basic authentication:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
 | 
					# without a group
 | 
				
			||||||
 | 
					curl --user your_username:your_password_or_token \
 | 
				
			||||||
 | 
					     --upload-file path/to/file.rpm \
 | 
				
			||||||
 | 
					     https://gitea.example.com/api/packages/testuser/rpm/upload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# with the group 'centos/el7'
 | 
				
			||||||
curl --user your_username:your_password_or_token \
 | 
					curl --user your_username:your_password_or_token \
 | 
				
			||||||
     --upload-file path/to/file.rpm \
 | 
					     --upload-file path/to/file.rpm \
 | 
				
			||||||
     https://gitea.example.com/api/packages/testuser/rpm/centos/el7/upload
 | 
					     https://gitea.example.com/api/packages/testuser/rpm/centos/el7/upload
 | 
				
			||||||
@ -83,17 +99,22 @@ To delete an RPM package perform a HTTP DELETE operation. This will delete the p
 | 
				
			|||||||
DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture}
 | 
					DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Parameter         | Description                |
 | 
					| Parameter         | Description |
 | 
				
			||||||
|-------------------|----------------------------|
 | 
					| ----------------- | ----------- |
 | 
				
			||||||
| `owner`           | The owner of the package.  |
 | 
					| `owner`           | The owner of the package. |
 | 
				
			||||||
| `group`           | The package group       .  |
 | 
					| `group`           | Optional: The package group. |
 | 
				
			||||||
| `package_name`    | The package name.          |
 | 
					| `package_name`    | The package name. |
 | 
				
			||||||
| `package_version` | The package version.       |
 | 
					| `package_version` | The package version. |
 | 
				
			||||||
| `architecture`    | The package architecture.  |
 | 
					| `architecture`    | The package architecture. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Example request using HTTP Basic authentication:
 | 
					Example request using HTTP Basic authentication:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
 | 
					# without a group
 | 
				
			||||||
 | 
					curl --user your_username:your_token_or_password -X DELETE \
 | 
				
			||||||
 | 
					     https://gitea.example.com/api/packages/testuser/rpm/package/test-package/1.0.0/x86_64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# with the group 'centos/el7'
 | 
				
			||||||
curl --user your_username:your_token_or_password -X DELETE \
 | 
					curl --user your_username:your_token_or_password -X DELETE \
 | 
				
			||||||
     https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64
 | 
					     https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
				
			|||||||
@ -191,18 +191,18 @@ type Package struct {
 | 
				
			|||||||
func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
 | 
					func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
 | 
				
			||||||
	e := db.GetEngine(ctx)
 | 
						e := db.GetEngine(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	key := &Package{
 | 
						existing := &Package{}
 | 
				
			||||||
		OwnerID:   p.OwnerID,
 | 
					 | 
				
			||||||
		Type:      p.Type,
 | 
					 | 
				
			||||||
		LowerName: p.LowerName,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	has, err := e.Get(key)
 | 
						has, err := e.Where(builder.Eq{
 | 
				
			||||||
 | 
							"owner_id":   p.OwnerID,
 | 
				
			||||||
 | 
							"type":       p.Type,
 | 
				
			||||||
 | 
							"lower_name": p.LowerName,
 | 
				
			||||||
 | 
						}).Get(existing)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if has {
 | 
						if has {
 | 
				
			||||||
		return key, ErrDuplicatePackage
 | 
							return existing, ErrDuplicatePackage
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if _, err = e.Insert(p); err != nil {
 | 
						if _, err = e.Insert(p); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
 | 
				
			|||||||
@ -41,12 +41,20 @@ type PackageBlob struct {
 | 
				
			|||||||
func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool, error) {
 | 
					func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool, error) {
 | 
				
			||||||
	e := db.GetEngine(ctx)
 | 
						e := db.GetEngine(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	has, err := e.Get(pb)
 | 
						existing := &PackageBlob{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						has, err := e.Where(builder.Eq{
 | 
				
			||||||
 | 
							"size":        pb.Size,
 | 
				
			||||||
 | 
							"hash_md5":    pb.HashMD5,
 | 
				
			||||||
 | 
							"hash_sha1":   pb.HashSHA1,
 | 
				
			||||||
 | 
							"hash_sha256": pb.HashSHA256,
 | 
				
			||||||
 | 
							"hash_sha512": pb.HashSHA512,
 | 
				
			||||||
 | 
						}).Get(existing)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, false, err
 | 
							return nil, false, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if has {
 | 
						if has {
 | 
				
			||||||
		return pb, true, nil
 | 
							return existing, true, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if _, err = e.Insert(pb); err != nil {
 | 
						if _, err = e.Insert(pb); err != nil {
 | 
				
			||||||
		return nil, false, err
 | 
							return nil, false, err
 | 
				
			||||||
 | 
				
			|||||||
@ -46,18 +46,18 @@ type PackageFile struct {
 | 
				
			|||||||
func TryInsertFile(ctx context.Context, pf *PackageFile) (*PackageFile, error) {
 | 
					func TryInsertFile(ctx context.Context, pf *PackageFile) (*PackageFile, error) {
 | 
				
			||||||
	e := db.GetEngine(ctx)
 | 
						e := db.GetEngine(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	key := &PackageFile{
 | 
						existing := &PackageFile{}
 | 
				
			||||||
		VersionID:    pf.VersionID,
 | 
					 | 
				
			||||||
		LowerName:    pf.LowerName,
 | 
					 | 
				
			||||||
		CompositeKey: pf.CompositeKey,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	has, err := e.Get(key)
 | 
						has, err := e.Where(builder.Eq{
 | 
				
			||||||
 | 
							"version_id":    pf.VersionID,
 | 
				
			||||||
 | 
							"lower_name":    pf.LowerName,
 | 
				
			||||||
 | 
							"composite_key": pf.CompositeKey,
 | 
				
			||||||
 | 
						}).Get(existing)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if has {
 | 
						if has {
 | 
				
			||||||
		return pf, ErrDuplicatePackageFile
 | 
							return existing, ErrDuplicatePackageFile
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if _, err = e.Insert(pf); err != nil {
 | 
						if _, err = e.Insert(pf); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -93,13 +93,13 @@ func GetFileForVersionByName(ctx context.Context, versionID int64, name, key str
 | 
				
			|||||||
		return nil, ErrPackageFileNotExist
 | 
							return nil, ErrPackageFileNotExist
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pf := &PackageFile{
 | 
						pf := &PackageFile{}
 | 
				
			||||||
		VersionID:    versionID,
 | 
					 | 
				
			||||||
		LowerName:    strings.ToLower(name),
 | 
					 | 
				
			||||||
		CompositeKey: key,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	has, err := db.GetEngine(ctx).Get(pf)
 | 
						has, err := db.GetEngine(ctx).Where(builder.Eq{
 | 
				
			||||||
 | 
							"version_id":    versionID,
 | 
				
			||||||
 | 
							"lower_name":    strings.ToLower(name),
 | 
				
			||||||
 | 
							"composite_key": key,
 | 
				
			||||||
 | 
						}).Get(pf)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -39,17 +39,17 @@ type PackageVersion struct {
 | 
				
			|||||||
func GetOrInsertVersion(ctx context.Context, pv *PackageVersion) (*PackageVersion, error) {
 | 
					func GetOrInsertVersion(ctx context.Context, pv *PackageVersion) (*PackageVersion, error) {
 | 
				
			||||||
	e := db.GetEngine(ctx)
 | 
						e := db.GetEngine(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	key := &PackageVersion{
 | 
						existing := &PackageVersion{}
 | 
				
			||||||
		PackageID:    pv.PackageID,
 | 
					 | 
				
			||||||
		LowerVersion: pv.LowerVersion,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	has, err := e.Get(key)
 | 
						has, err := e.Where(builder.Eq{
 | 
				
			||||||
 | 
							"package_id":    pv.PackageID,
 | 
				
			||||||
 | 
							"lower_version": pv.LowerVersion,
 | 
				
			||||||
 | 
						}).Get(existing)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if has {
 | 
						if has {
 | 
				
			||||||
		return key, ErrDuplicatePackageVersion
 | 
							return existing, ErrDuplicatePackageVersion
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if _, err = e.Insert(pv); err != nil {
 | 
						if _, err = e.Insert(pv); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								models/packages/rpm/search.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								models/packages/rpm/search.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package rpm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						packages_model "code.gitea.io/gitea/models/packages"
 | 
				
			||||||
 | 
						rpm_module "code.gitea.io/gitea/modules/packages/rpm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetGroups gets all available groups
 | 
				
			||||||
 | 
					func GetGroups(ctx context.Context, ownerID int64) ([]string, error) {
 | 
				
			||||||
 | 
						return packages_model.GetDistinctPropertyValues(
 | 
				
			||||||
 | 
							ctx,
 | 
				
			||||||
 | 
							packages_model.TypeRpm,
 | 
				
			||||||
 | 
							ownerID,
 | 
				
			||||||
 | 
							packages_model.PropertyTypeFile,
 | 
				
			||||||
 | 
							rpm_module.PropertyGroup,
 | 
				
			||||||
 | 
							nil,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -15,7 +15,10 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	PropertyMetadata  = "rpm.metadata"
 | 
						PropertyMetadata     = "rpm.metadata"
 | 
				
			||||||
 | 
						PropertyGroup        = "rpm.group"
 | 
				
			||||||
 | 
						PropertyArchitecture = "rpm.architecture"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	SettingKeyPrivate = "rpm.key.private"
 | 
						SettingKeyPrivate = "rpm.key.private"
 | 
				
			||||||
	SettingKeyPublic  = "rpm.key.public"
 | 
						SettingKeyPublic  = "rpm.key.public"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@
 | 
				
			|||||||
package templates
 | 
					package templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"regexp"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
@ -26,10 +25,6 @@ func (su *StringUtils) Contains(s, substr string) bool {
 | 
				
			|||||||
	return strings.Contains(s, substr)
 | 
						return strings.Contains(s, substr)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (su *StringUtils) ReplaceAllStringRegex(s, regex, new string) string {
 | 
					 | 
				
			||||||
	return regexp.MustCompile(regex).ReplaceAllString(s, new)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (su *StringUtils) Split(s, sep string) []string {
 | 
					func (su *StringUtils) Split(s, sep string) []string {
 | 
				
			||||||
	return strings.Split(s, sep)
 | 
						return strings.Split(s, sep)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@
 | 
				
			|||||||
package util
 | 
					package util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"cmp"
 | 
				
			||||||
	"slices"
 | 
						"slices"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -45,3 +46,10 @@ func SliceSortedEqual[T comparable](s1, s2 []T) bool {
 | 
				
			|||||||
func SliceRemoveAll[T comparable](slice []T, target T) []T {
 | 
					func SliceRemoveAll[T comparable](slice []T, target T) []T {
 | 
				
			||||||
	return slices.DeleteFunc(slice, func(t T) bool { return t == target })
 | 
						return slices.DeleteFunc(slice, func(t T) bool { return t == target })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sorted returns the sorted slice
 | 
				
			||||||
 | 
					// Note: The parameter is sorted inline.
 | 
				
			||||||
 | 
					func Sorted[S ~[]E, E cmp.Ordered](values S) S {
 | 
				
			||||||
 | 
						slices.Sort(values)
 | 
				
			||||||
 | 
						return values
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3414,6 +3414,9 @@ rpm.registry = Setup this registry from the command line:
 | 
				
			|||||||
rpm.distros.redhat = on RedHat based distributions
 | 
					rpm.distros.redhat = on RedHat based distributions
 | 
				
			||||||
rpm.distros.suse = on SUSE based distributions
 | 
					rpm.distros.suse = on SUSE based distributions
 | 
				
			||||||
rpm.install = To install the package, run the following command:
 | 
					rpm.install = To install the package, run the following command:
 | 
				
			||||||
 | 
					rpm.repository = Repository Info
 | 
				
			||||||
 | 
					rpm.repository.architectures = Architectures
 | 
				
			||||||
 | 
					rpm.repository.multiple_groups = This package is available in multiple groups.
 | 
				
			||||||
rubygems.install = To install the package using gem, run the following command:
 | 
					rubygems.install = To install the package using gem, run the following command:
 | 
				
			||||||
rubygems.install2 = or add it to the Gemfile:
 | 
					rubygems.install2 = or add it to the Gemfile:
 | 
				
			||||||
rubygems.dependencies.runtime = Runtime Dependencies
 | 
					rubygems.dependencies.runtime = Runtime Dependencies
 | 
				
			||||||
 | 
				
			|||||||
@ -512,7 +512,77 @@ func CommonRoutes() *web.Route {
 | 
				
			|||||||
			r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
 | 
								r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
 | 
				
			||||||
			r.Get("/simple/{id}", pypi.PackageMetadata)
 | 
								r.Get("/simple/{id}", pypi.PackageMetadata)
 | 
				
			||||||
		}, reqPackageAccess(perm.AccessModeRead))
 | 
							}, reqPackageAccess(perm.AccessModeRead))
 | 
				
			||||||
		r.Group("/rpm", RpmRoutes(r), reqPackageAccess(perm.AccessModeRead))
 | 
							r.Group("/rpm", func() {
 | 
				
			||||||
 | 
								r.Group("/repository.key", func() {
 | 
				
			||||||
 | 
									r.Head("", rpm.GetRepositoryKey)
 | 
				
			||||||
 | 
									r.Get("", rpm.GetRepositoryKey)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var (
 | 
				
			||||||
 | 
									repoPattern     = regexp.MustCompile(`\A(.*?)\.repo\z`)
 | 
				
			||||||
 | 
									uploadPattern   = regexp.MustCompile(`\A(.*?)/upload\z`)
 | 
				
			||||||
 | 
									filePattern     = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
 | 
				
			||||||
 | 
									repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
 | 
				
			||||||
 | 
									path := ctx.Params("*")
 | 
				
			||||||
 | 
									isHead := ctx.Req.Method == "HEAD"
 | 
				
			||||||
 | 
									isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
 | 
				
			||||||
 | 
									isPut := ctx.Req.Method == "PUT"
 | 
				
			||||||
 | 
									isDelete := ctx.Req.Method == "DELETE"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									m := repoPattern.FindStringSubmatch(path)
 | 
				
			||||||
 | 
									if len(m) == 2 && isGetHead {
 | 
				
			||||||
 | 
										ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
				
			||||||
 | 
										rpm.GetRepositoryConfig(ctx)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									m = repoFilePattern.FindStringSubmatch(path)
 | 
				
			||||||
 | 
									if len(m) == 3 && isGetHead {
 | 
				
			||||||
 | 
										ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
				
			||||||
 | 
										ctx.SetParams("filename", m[2])
 | 
				
			||||||
 | 
										if isHead {
 | 
				
			||||||
 | 
											rpm.CheckRepositoryFileExistence(ctx)
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											rpm.GetRepositoryFile(ctx)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									m = uploadPattern.FindStringSubmatch(path)
 | 
				
			||||||
 | 
									if len(m) == 2 && isPut {
 | 
				
			||||||
 | 
										reqPackageAccess(perm.AccessModeWrite)(ctx)
 | 
				
			||||||
 | 
										if ctx.Written() {
 | 
				
			||||||
 | 
											return
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
				
			||||||
 | 
										rpm.UploadPackageFile(ctx)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									m = filePattern.FindStringSubmatch(path)
 | 
				
			||||||
 | 
									if len(m) == 6 && (isGetHead || isDelete) {
 | 
				
			||||||
 | 
										ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
				
			||||||
 | 
										ctx.SetParams("name", m[2])
 | 
				
			||||||
 | 
										ctx.SetParams("version", m[3])
 | 
				
			||||||
 | 
										ctx.SetParams("architecture", m[4])
 | 
				
			||||||
 | 
										if isGetHead {
 | 
				
			||||||
 | 
											rpm.DownloadPackageFile(ctx)
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											reqPackageAccess(perm.AccessModeWrite)(ctx)
 | 
				
			||||||
 | 
											if ctx.Written() {
 | 
				
			||||||
 | 
												return
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											rpm.DeletePackageFile(ctx)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ctx.Status(http.StatusNotFound)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}, reqPackageAccess(perm.AccessModeRead))
 | 
				
			||||||
		r.Group("/rubygems", func() {
 | 
							r.Group("/rubygems", func() {
 | 
				
			||||||
			r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
 | 
								r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
 | 
				
			||||||
			r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
 | 
								r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
 | 
				
			||||||
@ -577,82 +647,6 @@ func CommonRoutes() *web.Route {
 | 
				
			|||||||
	return r
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Support for uploading rpm packages with arbitrary depth paths
 | 
					 | 
				
			||||||
func RpmRoutes(r *web.Route) func() {
 | 
					 | 
				
			||||||
	var (
 | 
					 | 
				
			||||||
		groupRepoInfo = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)\.repo\z`)
 | 
					 | 
				
			||||||
		groupUpload   = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/upload\z`)
 | 
					 | 
				
			||||||
		groupRpm      = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
 | 
					 | 
				
			||||||
		groupMetadata = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/repodata/([^/]+)\z`)
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return func() {
 | 
					 | 
				
			||||||
		r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "*", func(ctx *context.Context) {
 | 
					 | 
				
			||||||
			path := ctx.Params("*")
 | 
					 | 
				
			||||||
			isHead := ctx.Req.Method == "HEAD"
 | 
					 | 
				
			||||||
			isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
 | 
					 | 
				
			||||||
			isPut := ctx.Req.Method == "PUT"
 | 
					 | 
				
			||||||
			isDelete := ctx.Req.Method == "DELETE"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if path == "/repository.key" && isGetHead {
 | 
					 | 
				
			||||||
				rpm.GetRepositoryKey(ctx)
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// get repo
 | 
					 | 
				
			||||||
			m := groupRepoInfo.FindStringSubmatch(path)
 | 
					 | 
				
			||||||
			if len(m) == 2 && isGetHead {
 | 
					 | 
				
			||||||
				ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
					 | 
				
			||||||
				rpm.GetRepositoryConfig(ctx)
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// get meta
 | 
					 | 
				
			||||||
			m = groupMetadata.FindStringSubmatch(path)
 | 
					 | 
				
			||||||
			if len(m) == 3 && isGetHead {
 | 
					 | 
				
			||||||
				ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
					 | 
				
			||||||
				ctx.SetParams("filename", m[2])
 | 
					 | 
				
			||||||
				if isHead {
 | 
					 | 
				
			||||||
					rpm.CheckRepositoryFileExistence(ctx)
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					rpm.GetRepositoryFile(ctx)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// upload
 | 
					 | 
				
			||||||
			m = groupUpload.FindStringSubmatch(path)
 | 
					 | 
				
			||||||
			if len(m) == 2 && isPut {
 | 
					 | 
				
			||||||
				reqPackageAccess(perm.AccessModeWrite)(ctx)
 | 
					 | 
				
			||||||
				if ctx.Written() {
 | 
					 | 
				
			||||||
					return
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
					 | 
				
			||||||
				rpm.UploadPackageFile(ctx)
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// rpm down/delete
 | 
					 | 
				
			||||||
			m = groupRpm.FindStringSubmatch(path)
 | 
					 | 
				
			||||||
			if len(m) == 6 {
 | 
					 | 
				
			||||||
				ctx.SetParams("group", strings.Trim(m[1], "/"))
 | 
					 | 
				
			||||||
				ctx.SetParams("name", m[2])
 | 
					 | 
				
			||||||
				ctx.SetParams("version", m[3])
 | 
					 | 
				
			||||||
				ctx.SetParams("architecture", m[4])
 | 
					 | 
				
			||||||
				if isGetHead {
 | 
					 | 
				
			||||||
					rpm.DownloadPackageFile(ctx)
 | 
					 | 
				
			||||||
					return
 | 
					 | 
				
			||||||
				} else if isDelete {
 | 
					 | 
				
			||||||
					reqPackageAccess(perm.AccessModeWrite)(ctx)
 | 
					 | 
				
			||||||
					if ctx.Written() {
 | 
					 | 
				
			||||||
						return
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					rpm.DeletePackageFile(ctx)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// default
 | 
					 | 
				
			||||||
			ctx.Status(http.StatusNotFound)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ContainerRoutes provides endpoints that implement the OCI API to serve containers
 | 
					// ContainerRoutes provides endpoints that implement the OCI API to serve containers
 | 
				
			||||||
// These have to be mounted on `/v2/...` to comply with the OCI spec:
 | 
					// These have to be mounted on `/v2/...` to comply with the OCI spec:
 | 
				
			||||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
 | 
					// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
 | 
				
			||||||
 | 
				
			|||||||
@ -34,13 +34,17 @@ func apiError(ctx *context.Context, status int, obj any) {
 | 
				
			|||||||
// https://dnf.readthedocs.io/en/latest/conf_ref.html
 | 
					// https://dnf.readthedocs.io/en/latest/conf_ref.html
 | 
				
			||||||
func GetRepositoryConfig(ctx *context.Context) {
 | 
					func GetRepositoryConfig(ctx *context.Context) {
 | 
				
			||||||
	group := ctx.Params("group")
 | 
						group := ctx.Params("group")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var groupParts []string
 | 
				
			||||||
	if group != "" {
 | 
						if group != "" {
 | 
				
			||||||
		group = fmt.Sprintf("/%s", group)
 | 
							groupParts = strings.Split(group, "/")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
 | 
						url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
 | 
				
			||||||
	ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+strings.ReplaceAll(group, "/", "-")+`]
 | 
					
 | 
				
			||||||
name=`+ctx.Package.Owner.Name+` - `+setting.AppName+strings.ReplaceAll(group, "/", " - ")+`
 | 
						ctx.PlainText(http.StatusOK, `[gitea-`+strings.Join(append([]string{ctx.Package.Owner.LowerName}, groupParts...), "-")+`]
 | 
				
			||||||
baseurl=`+url+group+`/
 | 
					name=`+strings.Join(append([]string{ctx.Package.Owner.Name, setting.AppName}, groupParts...), " - ")+`
 | 
				
			||||||
 | 
					baseurl=`+strings.Join(append([]string{url}, groupParts...), "/")+`
 | 
				
			||||||
enabled=1
 | 
					enabled=1
 | 
				
			||||||
gpgcheck=1
 | 
					gpgcheck=1
 | 
				
			||||||
gpgkey=`+url+`/repository.key`)
 | 
					gpgkey=`+url+`/repository.key`)
 | 
				
			||||||
@ -157,7 +161,7 @@ func UploadPackageFile(ctx *context.Context) {
 | 
				
			|||||||
				Owner:       ctx.Package.Owner,
 | 
									Owner:       ctx.Package.Owner,
 | 
				
			||||||
				PackageType: packages_model.TypeRpm,
 | 
									PackageType: packages_model.TypeRpm,
 | 
				
			||||||
				Name:        pck.Name,
 | 
									Name:        pck.Name,
 | 
				
			||||||
				Version:     strings.Trim(fmt.Sprintf("%s/%s", group, pck.Version), "/"),
 | 
									Version:     pck.Version,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Creator:  ctx.Doer,
 | 
								Creator:  ctx.Doer,
 | 
				
			||||||
			Metadata: pck.VersionMetadata,
 | 
								Metadata: pck.VersionMetadata,
 | 
				
			||||||
@ -171,7 +175,9 @@ func UploadPackageFile(ctx *context.Context) {
 | 
				
			|||||||
			Data:    buf,
 | 
								Data:    buf,
 | 
				
			||||||
			IsLead:  true,
 | 
								IsLead:  true,
 | 
				
			||||||
			Properties: map[string]string{
 | 
								Properties: map[string]string{
 | 
				
			||||||
				rpm_module.PropertyMetadata: string(fileMetadataRaw),
 | 
									rpm_module.PropertyGroup:        group,
 | 
				
			||||||
 | 
									rpm_module.PropertyArchitecture: pck.FileMetadata.Architecture,
 | 
				
			||||||
 | 
									rpm_module.PropertyMetadata:     string(fileMetadataRaw),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
@ -187,7 +193,7 @@ func UploadPackageFile(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
 | 
						if err := rpm_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
 | 
				
			||||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
							apiError(ctx, http.StatusInternalServerError, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -196,20 +202,20 @@ func UploadPackageFile(ctx *context.Context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func DownloadPackageFile(ctx *context.Context) {
 | 
					func DownloadPackageFile(ctx *context.Context) {
 | 
				
			||||||
	group := ctx.Params("group")
 | 
					 | 
				
			||||||
	name := ctx.Params("name")
 | 
						name := ctx.Params("name")
 | 
				
			||||||
	version := ctx.Params("version")
 | 
						version := ctx.Params("version")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
 | 
						s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
 | 
				
			||||||
		ctx,
 | 
							ctx,
 | 
				
			||||||
		&packages_service.PackageInfo{
 | 
							&packages_service.PackageInfo{
 | 
				
			||||||
			Owner:       ctx.Package.Owner,
 | 
								Owner:       ctx.Package.Owner,
 | 
				
			||||||
			PackageType: packages_model.TypeRpm,
 | 
								PackageType: packages_model.TypeRpm,
 | 
				
			||||||
			Name:        name,
 | 
								Name:        name,
 | 
				
			||||||
			Version:     strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
 | 
								Version:     version,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		&packages_service.PackageFileInfo{
 | 
							&packages_service.PackageFileInfo{
 | 
				
			||||||
			Filename:     fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
 | 
								Filename:     fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
 | 
				
			||||||
			CompositeKey: group,
 | 
								CompositeKey: ctx.Params("group"),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -229,6 +235,7 @@ func DeletePackageFile(webctx *context.Context) {
 | 
				
			|||||||
	name := webctx.Params("name")
 | 
						name := webctx.Params("name")
 | 
				
			||||||
	version := webctx.Params("version")
 | 
						version := webctx.Params("version")
 | 
				
			||||||
	architecture := webctx.Params("architecture")
 | 
						architecture := webctx.Params("architecture")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var pd *packages_model.PackageDescriptor
 | 
						var pd *packages_model.PackageDescriptor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := db.WithTx(webctx, func(ctx stdctx.Context) error {
 | 
						err := db.WithTx(webctx, func(ctx stdctx.Context) error {
 | 
				
			||||||
@ -236,7 +243,7 @@ func DeletePackageFile(webctx *context.Context) {
 | 
				
			|||||||
			webctx.Package.Owner.ID,
 | 
								webctx.Package.Owner.ID,
 | 
				
			||||||
			packages_model.TypeRpm,
 | 
								packages_model.TypeRpm,
 | 
				
			||||||
			name,
 | 
								name,
 | 
				
			||||||
			strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
 | 
								version,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
@ -286,7 +293,7 @@ func DeletePackageFile(webctx *context.Context) {
 | 
				
			|||||||
		notify_service.PackageDelete(webctx, webctx.Doer, pd)
 | 
							notify_service.PackageDelete(webctx, webctx.Doer, pd)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
 | 
						if err := rpm_service.BuildSpecificRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
 | 
				
			||||||
		apiError(webctx, http.StatusInternalServerError, err)
 | 
							apiError(webctx, http.StatusInternalServerError, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -108,6 +108,6 @@ func CleanupExpiredData(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Flash.Success(ctx.Tr("packages.cleanup.success"))
 | 
						ctx.Flash.Success(ctx.Tr("admin.packages.cleanup.success"))
 | 
				
			||||||
	ctx.Redirect(setting.AppSubURL + "/admin/packages")
 | 
						ctx.Redirect(setting.AppSubURL + "/admin/packages")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	alpine_module "code.gitea.io/gitea/modules/packages/alpine"
 | 
						alpine_module "code.gitea.io/gitea/modules/packages/alpine"
 | 
				
			||||||
	debian_module "code.gitea.io/gitea/modules/packages/debian"
 | 
						debian_module "code.gitea.io/gitea/modules/packages/debian"
 | 
				
			||||||
 | 
						rpm_module "code.gitea.io/gitea/modules/packages/rpm"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
@ -195,9 +196,9 @@ func ViewPackageVersion(ctx *context.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ctx.Data["Branches"] = branches.Values()
 | 
							ctx.Data["Branches"] = util.Sorted(branches.Values())
 | 
				
			||||||
		ctx.Data["Repositories"] = repositories.Values()
 | 
							ctx.Data["Repositories"] = util.Sorted(repositories.Values())
 | 
				
			||||||
		ctx.Data["Architectures"] = architectures.Values()
 | 
							ctx.Data["Architectures"] = util.Sorted(architectures.Values())
 | 
				
			||||||
	case packages_model.TypeDebian:
 | 
						case packages_model.TypeDebian:
 | 
				
			||||||
		distributions := make(container.Set[string])
 | 
							distributions := make(container.Set[string])
 | 
				
			||||||
		components := make(container.Set[string])
 | 
							components := make(container.Set[string])
 | 
				
			||||||
@ -216,9 +217,26 @@ func ViewPackageVersion(ctx *context.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ctx.Data["Distributions"] = distributions.Values()
 | 
							ctx.Data["Distributions"] = util.Sorted(distributions.Values())
 | 
				
			||||||
		ctx.Data["Components"] = components.Values()
 | 
							ctx.Data["Components"] = util.Sorted(components.Values())
 | 
				
			||||||
		ctx.Data["Architectures"] = architectures.Values()
 | 
							ctx.Data["Architectures"] = util.Sorted(architectures.Values())
 | 
				
			||||||
 | 
						case packages_model.TypeRpm:
 | 
				
			||||||
 | 
							groups := make(container.Set[string])
 | 
				
			||||||
 | 
							architectures := make(container.Set[string])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, f := range pd.Files {
 | 
				
			||||||
 | 
								for _, pp := range f.Properties {
 | 
				
			||||||
 | 
									switch pp.Name {
 | 
				
			||||||
 | 
									case rpm_module.PropertyGroup:
 | 
				
			||||||
 | 
										groups.Add(pp.Value)
 | 
				
			||||||
 | 
									case rpm_module.PropertyArchitecture:
 | 
				
			||||||
 | 
										architectures.Add(pp.Value)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ctx.Data["Groups"] = util.Sorted(groups.Values())
 | 
				
			||||||
 | 
							ctx.Data["Architectures"] = util.Sorted(architectures.Values())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@ import (
 | 
				
			|||||||
	cargo_service "code.gitea.io/gitea/services/packages/cargo"
 | 
						cargo_service "code.gitea.io/gitea/services/packages/cargo"
 | 
				
			||||||
	container_service "code.gitea.io/gitea/services/packages/container"
 | 
						container_service "code.gitea.io/gitea/services/packages/container"
 | 
				
			||||||
	debian_service "code.gitea.io/gitea/services/packages/debian"
 | 
						debian_service "code.gitea.io/gitea/services/packages/debian"
 | 
				
			||||||
 | 
						rpm_service "code.gitea.io/gitea/services/packages/rpm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Task method to execute cleanup rules and cleanup expired package data
 | 
					// Task method to execute cleanup rules and cleanup expired package data
 | 
				
			||||||
@ -127,6 +128,10 @@ func ExecuteCleanupRules(outerCtx context.Context) error {
 | 
				
			|||||||
				if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
 | 
									if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
 | 
				
			||||||
					return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
 | 
										return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								} else if pcr.Type == packages_model.TypeRpm {
 | 
				
			||||||
 | 
									if err := rpm_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("CleanupRule [%d]: rpm.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	packages_model "code.gitea.io/gitea/models/packages"
 | 
						packages_model "code.gitea.io/gitea/models/packages"
 | 
				
			||||||
 | 
						rpm_model "code.gitea.io/gitea/models/packages/rpm"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/json"
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
	packages_module "code.gitea.io/gitea/modules/packages"
 | 
						packages_module "code.gitea.io/gitea/modules/packages"
 | 
				
			||||||
@ -96,6 +97,39 @@ func generateKeypair() (string, string, error) {
 | 
				
			|||||||
	return priv.String(), pub.String(), nil
 | 
						return priv.String(), pub.String(), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type repoChecksum struct {
 | 
					type repoChecksum struct {
 | 
				
			||||||
	Value string `xml:",chardata"`
 | 
						Value string `xml:",chardata"`
 | 
				
			||||||
	Type  string `xml:"type,attr"`
 | 
						Type  string `xml:"type,attr"`
 | 
				
			||||||
@ -126,7 +160,7 @@ type packageData struct {
 | 
				
			|||||||
type packageCache = map[*packages_model.PackageFile]*packageData
 | 
					type packageCache = map[*packages_model.PackageFile]*packageData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// BuildSpecificRepositoryFiles builds metadata files for the repository
 | 
					// BuildSpecificRepositoryFiles builds metadata files for the repository
 | 
				
			||||||
func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey string) error {
 | 
					func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, group string) error {
 | 
				
			||||||
	pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
 | 
						pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@ -136,7 +170,7 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey strin
 | 
				
			|||||||
		OwnerID:      ownerID,
 | 
							OwnerID:      ownerID,
 | 
				
			||||||
		PackageType:  packages_model.TypeRpm,
 | 
							PackageType:  packages_model.TypeRpm,
 | 
				
			||||||
		Query:        "%.rpm",
 | 
							Query:        "%.rpm",
 | 
				
			||||||
		CompositeKey: compositeKey,
 | 
							CompositeKey: group,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@ -195,15 +229,15 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey strin
 | 
				
			|||||||
		cache[pf] = pd
 | 
							cache[pf] = pd
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	primary, err := buildPrimary(ctx, pv, pfs, cache, compositeKey)
 | 
						primary, err := buildPrimary(ctx, pv, pfs, cache, group)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	filelists, err := buildFilelists(ctx, pv, pfs, cache, compositeKey)
 | 
						filelists, err := buildFilelists(ctx, pv, pfs, cache, group)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	other, err := buildOther(ctx, pv, pfs, cache, compositeKey)
 | 
						other, err := buildOther(ctx, pv, pfs, cache, group)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -217,12 +251,12 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey strin
 | 
				
			|||||||
			filelists,
 | 
								filelists,
 | 
				
			||||||
			other,
 | 
								other,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		compositeKey,
 | 
							group,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml
 | 
					// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml
 | 
				
			||||||
func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, compositeKey string) error {
 | 
					func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, group string) error {
 | 
				
			||||||
	type Repomd struct {
 | 
						type Repomd struct {
 | 
				
			||||||
		XMLName  xml.Name    `xml:"repomd"`
 | 
							XMLName  xml.Name    `xml:"repomd"`
 | 
				
			||||||
		Xmlns    string      `xml:"xmlns,attr"`
 | 
							Xmlns    string      `xml:"xmlns,attr"`
 | 
				
			||||||
@ -278,7 +312,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
 | 
				
			|||||||
			&packages_service.PackageFileCreationInfo{
 | 
								&packages_service.PackageFileCreationInfo{
 | 
				
			||||||
				PackageFileInfo: packages_service.PackageFileInfo{
 | 
									PackageFileInfo: packages_service.PackageFileInfo{
 | 
				
			||||||
					Filename:     file.Name,
 | 
										Filename:     file.Name,
 | 
				
			||||||
					CompositeKey: compositeKey,
 | 
										CompositeKey: group,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				Creator:           user_model.NewGhostUser(),
 | 
									Creator:           user_model.NewGhostUser(),
 | 
				
			||||||
				Data:              file.Data,
 | 
									Data:              file.Data,
 | 
				
			||||||
@ -295,7 +329,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml
 | 
					// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml
 | 
				
			||||||
func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) {
 | 
					func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) {
 | 
				
			||||||
	type Version struct {
 | 
						type Version struct {
 | 
				
			||||||
		Epoch   string `xml:"epoch,attr"`
 | 
							Epoch   string `xml:"epoch,attr"`
 | 
				
			||||||
		Version string `xml:"ver,attr"`
 | 
							Version string `xml:"ver,attr"`
 | 
				
			||||||
@ -434,11 +468,11 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
 | 
				
			|||||||
		XmlnsRpm:     "http://linux.duke.edu/metadata/rpm",
 | 
							XmlnsRpm:     "http://linux.duke.edu/metadata/rpm",
 | 
				
			||||||
		PackageCount: len(pfs),
 | 
							PackageCount: len(pfs),
 | 
				
			||||||
		Packages:     packages,
 | 
							Packages:     packages,
 | 
				
			||||||
	}, compositeKey)
 | 
						}, group)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
 | 
					// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
 | 
				
			||||||
func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
 | 
					func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl
 | 
				
			||||||
	type Version struct {
 | 
						type Version struct {
 | 
				
			||||||
		Epoch   string `xml:"epoch,attr"`
 | 
							Epoch   string `xml:"epoch,attr"`
 | 
				
			||||||
		Version string `xml:"ver,attr"`
 | 
							Version string `xml:"ver,attr"`
 | 
				
			||||||
@ -481,12 +515,11 @@ func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs
 | 
				
			|||||||
		Xmlns:        "http://linux.duke.edu/metadata/other",
 | 
							Xmlns:        "http://linux.duke.edu/metadata/other",
 | 
				
			||||||
		PackageCount: len(pfs),
 | 
							PackageCount: len(pfs),
 | 
				
			||||||
		Packages:     packages,
 | 
							Packages:     packages,
 | 
				
			||||||
	},
 | 
						}, group)
 | 
				
			||||||
		compositeKey)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
 | 
					// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
 | 
				
			||||||
func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
 | 
					func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl
 | 
				
			||||||
	type Version struct {
 | 
						type Version struct {
 | 
				
			||||||
		Epoch   string `xml:"epoch,attr"`
 | 
							Epoch   string `xml:"epoch,attr"`
 | 
				
			||||||
		Version string `xml:"ver,attr"`
 | 
							Version string `xml:"ver,attr"`
 | 
				
			||||||
@ -529,7 +562,7 @@ func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*p
 | 
				
			|||||||
		Xmlns:        "http://linux.duke.edu/metadata/other",
 | 
							Xmlns:        "http://linux.duke.edu/metadata/other",
 | 
				
			||||||
		PackageCount: len(pfs),
 | 
							PackageCount: len(pfs),
 | 
				
			||||||
		Packages:     packages,
 | 
							Packages:     packages,
 | 
				
			||||||
	}, compositeKey)
 | 
						}, group)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// writtenCounter counts all written bytes
 | 
					// writtenCounter counts all written bytes
 | 
				
			||||||
@ -549,8 +582,10 @@ func (wc *writtenCounter) Written() int64 {
 | 
				
			|||||||
	return wc.written
 | 
						return wc.written
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, compositeKey string) (*repoData, error) {
 | 
					func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, group string) (*repoData, error) {
 | 
				
			||||||
	content, _ := packages_module.NewHashedBuffer()
 | 
						content, _ := packages_module.NewHashedBuffer()
 | 
				
			||||||
 | 
						defer content.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gzw := gzip.NewWriter(content)
 | 
						gzw := gzip.NewWriter(content)
 | 
				
			||||||
	wc := &writtenCounter{}
 | 
						wc := &writtenCounter{}
 | 
				
			||||||
	h := sha256.New()
 | 
						h := sha256.New()
 | 
				
			||||||
@ -574,7 +609,7 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion,
 | 
				
			|||||||
		&packages_service.PackageFileCreationInfo{
 | 
							&packages_service.PackageFileCreationInfo{
 | 
				
			||||||
			PackageFileInfo: packages_service.PackageFileInfo{
 | 
								PackageFileInfo: packages_service.PackageFileInfo{
 | 
				
			||||||
				Filename:     filename,
 | 
									Filename:     filename,
 | 
				
			||||||
				CompositeKey: compositeKey,
 | 
									CompositeKey: group,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Creator:           user_model.NewGhostUser(),
 | 
								Creator:           user_model.NewGhostUser(),
 | 
				
			||||||
			Data:              content,
 | 
								Data:              content,
 | 
				
			||||||
 | 
				
			|||||||
@ -4,15 +4,21 @@
 | 
				
			|||||||
		<div class="ui form">
 | 
							<div class="ui form">
 | 
				
			||||||
			<div class="field">
 | 
								<div class="field">
 | 
				
			||||||
				<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.registry"}}</label>
 | 
									<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.registry"}}</label>
 | 
				
			||||||
				<div class="markup"><pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distros.redhat"}}
 | 
									<div class="markup"><pre class="code-block"><code>{{- if gt (len .Groups) 1 -}}
 | 
				
			||||||
{{$group_name:= StringUtils.ReplaceAllStringRegex .PackageDescriptor.Version.Version "(/[^/]+|[^/]*)\\z" "" -}}
 | 
					# {{ctx.Locale.Tr "packages.rpm.repository.multiple_groups"}}
 | 
				
			||||||
{{- if $group_name -}}
 | 
					
 | 
				
			||||||
{{- $group_name = (print "/" $group_name) -}}
 | 
					{{end -}}
 | 
				
			||||||
{{- end -}}
 | 
					# {{ctx.Locale.Tr "packages.rpm.distros.redhat"}}
 | 
				
			||||||
dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group_name}}.repo"></gitea-origin-url>
 | 
					{{- range $group := .Groups}}
 | 
				
			||||||
 | 
						{{- if $group}}{{$group = print "/" $group}}{{end}}
 | 
				
			||||||
 | 
					dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group}}.repo"></gitea-origin-url>
 | 
				
			||||||
 | 
					{{- end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# {{ctx.Locale.Tr "packages.rpm.distros.suse"}}
 | 
					# {{ctx.Locale.Tr "packages.rpm.distros.suse"}}
 | 
				
			||||||
zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group_name}}.repo"></gitea-origin-url></code></pre></div>
 | 
					{{- range $group := .Groups}}
 | 
				
			||||||
 | 
						{{- if $group}}{{$group = print "/" $group}}{{end}}
 | 
				
			||||||
 | 
					zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group}}.repo"></gitea-origin-url>
 | 
				
			||||||
 | 
					{{- end}}</code></pre></div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="field">
 | 
								<div class="field">
 | 
				
			||||||
				<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.install"}}</label>
 | 
									<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.install"}}</label>
 | 
				
			||||||
@ -30,6 +36,18 @@ zypper install {{$.PackageDescriptor.Package.Name}}</code></pre>
 | 
				
			|||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.rpm.repository"}}</h4>
 | 
				
			||||||
 | 
						<div class="ui attached segment">
 | 
				
			||||||
 | 
							<table class="ui single line very basic table">
 | 
				
			||||||
 | 
								<tbody>
 | 
				
			||||||
 | 
									<tr>
 | 
				
			||||||
 | 
										<td class="collapsing"><h5>{{ctx.Locale.Tr "packages.rpm.repository.architectures"}}</h5></td>
 | 
				
			||||||
 | 
										<td>{{StringUtils.Join .Architectures ", "}}</td>
 | 
				
			||||||
 | 
									</tr>
 | 
				
			||||||
 | 
								</tbody>
 | 
				
			||||||
 | 
							</table>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{{if or .PackageDescriptor.Metadata.Summary .PackageDescriptor.Metadata.Description}}
 | 
						{{if or .PackageDescriptor.Metadata.Summary .PackageDescriptor.Metadata.Description}}
 | 
				
			||||||
		<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
 | 
							<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
 | 
				
			||||||
		{{if .PackageDescriptor.Metadata.Summary}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Summary}}</div>{{end}}
 | 
							{{if .PackageDescriptor.Metadata.Summary}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Summary}}</div>{{end}}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/httptest"
 | 
						"net/http/httptest"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
@ -20,6 +21,7 @@ import (
 | 
				
			|||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	rpm_module "code.gitea.io/gitea/modules/packages/rpm"
 | 
						rpm_module "code.gitea.io/gitea/modules/packages/rpm"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/tests"
 | 
						"code.gitea.io/gitea/tests"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@ -73,346 +75,362 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	rootURL := fmt.Sprintf("/api/packages/%s/rpm", user.Name)
 | 
						rootURL := fmt.Sprintf("/api/packages/%s/rpm", user.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("RepositoryConfig", func(t *testing.T) {
 | 
						for _, group := range []string{"", "el9", "el9/stable"} {
 | 
				
			||||||
		defer tests.PrintCurrentTest(t)()
 | 
							t.Run(fmt.Sprintf("[Group:%s]", group), func(t *testing.T) {
 | 
				
			||||||
 | 
								var groupParts []string
 | 
				
			||||||
 | 
								if group != "" {
 | 
				
			||||||
 | 
									groupParts = strings.Split(group, "/")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								groupURL := strings.Join(append([]string{rootURL}, groupParts...), "/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req := NewRequest(t, "GET", rootURL+"/el9/stable.repo")
 | 
								t.Run("RepositoryConfig", func(t *testing.T) {
 | 
				
			||||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
									defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		expected := fmt.Sprintf(`[gitea-%s-el9-stable]
 | 
									req := NewRequest(t, "GET", groupURL+".repo")
 | 
				
			||||||
name=%s - %s - el9 - stable
 | 
									resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
baseurl=%sapi/packages/%s/rpm/el9/stable/
 | 
					
 | 
				
			||||||
 | 
									expected := fmt.Sprintf(`[gitea-%s]
 | 
				
			||||||
 | 
					name=%s
 | 
				
			||||||
 | 
					baseurl=%s
 | 
				
			||||||
enabled=1
 | 
					enabled=1
 | 
				
			||||||
gpgcheck=1
 | 
					gpgcheck=1
 | 
				
			||||||
gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppName, setting.AppURL, user.Name, setting.AppURL, user.Name)
 | 
					gpgkey=%sapi/packages/%s/rpm/repository.key`,
 | 
				
			||||||
 | 
										strings.Join(append([]string{user.LowerName}, groupParts...), "-"),
 | 
				
			||||||
 | 
										strings.Join(append([]string{user.Name, setting.AppName}, groupParts...), " - "),
 | 
				
			||||||
 | 
										util.URLJoin(setting.AppURL, groupURL),
 | 
				
			||||||
 | 
										setting.AppURL,
 | 
				
			||||||
 | 
										user.Name,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.Equal(t, expected, resp.Body.String())
 | 
									assert.Equal(t, expected, resp.Body.String())
 | 
				
			||||||
	})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("RepositoryKey", func(t *testing.T) {
 | 
								t.Run("RepositoryKey", func(t *testing.T) {
 | 
				
			||||||
		defer tests.PrintCurrentTest(t)()
 | 
									defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req := NewRequest(t, "GET", rootURL+"/repository.key")
 | 
									req := NewRequest(t, "GET", rootURL+"/repository.key")
 | 
				
			||||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
									resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
 | 
									assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
 | 
				
			||||||
		assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
 | 
									assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
 | 
				
			||||||
	})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("Upload", func(t *testing.T) {
 | 
								t.Run("Upload", func(t *testing.T) {
 | 
				
			||||||
		url := rootURL + "/el9/stable/upload"
 | 
									url := groupURL + "/upload"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
 | 
									req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
 | 
				
			||||||
		MakeRequest(t, req, http.StatusUnauthorized)
 | 
									MakeRequest(t, req, http.StatusUnauthorized)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
 | 
									req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
 | 
				
			||||||
			AddBasicAuth(user.Name)
 | 
										AddBasicAuth(user.Name)
 | 
				
			||||||
		MakeRequest(t, req, http.StatusCreated)
 | 
									MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
 | 
									pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
									assert.NoError(t, err)
 | 
				
			||||||
		assert.Len(t, pvs, 1)
 | 
									assert.Len(t, pvs, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
 | 
									pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
 | 
				
			||||||
		assert.NoError(t, err)
 | 
									assert.NoError(t, err)
 | 
				
			||||||
		assert.Nil(t, pd.SemVer)
 | 
									assert.Nil(t, pd.SemVer)
 | 
				
			||||||
		assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
 | 
									assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
 | 
				
			||||||
		assert.Equal(t, packageName, pd.Package.Name)
 | 
									assert.Equal(t, packageName, pd.Package.Name)
 | 
				
			||||||
		assert.Equal(t, fmt.Sprintf("el9/stable/%s", packageVersion), pd.Version.Version)
 | 
									assert.Equal(t, packageVersion, pd.Version.Version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
 | 
									pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
									assert.NoError(t, err)
 | 
				
			||||||
		assert.Len(t, pfs, 1)
 | 
									assert.Len(t, pfs, 1)
 | 
				
			||||||
		assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name)
 | 
									assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name)
 | 
				
			||||||
		assert.True(t, pfs[0].IsLead)
 | 
									assert.True(t, pfs[0].IsLead)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
 | 
									pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
									assert.NoError(t, err)
 | 
				
			||||||
		assert.Equal(t, int64(len(content)), pb.Size)
 | 
									assert.Equal(t, int64(len(content)), pb.Size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
 | 
									req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
 | 
				
			||||||
			AddBasicAuth(user.Name)
 | 
										AddBasicAuth(user.Name)
 | 
				
			||||||
		MakeRequest(t, req, http.StatusConflict)
 | 
									MakeRequest(t, req, http.StatusConflict)
 | 
				
			||||||
	})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("Download", func(t *testing.T) {
 | 
								t.Run("Download", func(t *testing.T) {
 | 
				
			||||||
		defer tests.PrintCurrentTest(t)()
 | 
									defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req := NewRequest(t, "GET", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
 | 
									req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
 | 
				
			||||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
									resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.Equal(t, content, resp.Body.Bytes())
 | 
									assert.Equal(t, content, resp.Body.Bytes())
 | 
				
			||||||
	})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("Repository", func(t *testing.T) {
 | 
								t.Run("Repository", func(t *testing.T) {
 | 
				
			||||||
		defer tests.PrintCurrentTest(t)()
 | 
									defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		url := rootURL + "/el9/stable/repodata"
 | 
									url := groupURL + "/repodata"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req := NewRequest(t, "HEAD", url+"/dummy.xml")
 | 
									req := NewRequest(t, "HEAD", url+"/dummy.xml")
 | 
				
			||||||
		MakeRequest(t, req, http.StatusNotFound)
 | 
									MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req = NewRequest(t, "GET", url+"/dummy.xml")
 | 
									req = NewRequest(t, "GET", url+"/dummy.xml")
 | 
				
			||||||
		MakeRequest(t, req, http.StatusNotFound)
 | 
									MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		t.Run("repomd.xml", func(t *testing.T) {
 | 
									t.Run("repomd.xml", func(t *testing.T) {
 | 
				
			||||||
			defer tests.PrintCurrentTest(t)()
 | 
										defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			req = NewRequest(t, "HEAD", url+"/repomd.xml")
 | 
										req = NewRequest(t, "HEAD", url+"/repomd.xml")
 | 
				
			||||||
			MakeRequest(t, req, http.StatusOK)
 | 
										MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			req = NewRequest(t, "GET", url+"/repomd.xml")
 | 
										req = NewRequest(t, "GET", url+"/repomd.xml")
 | 
				
			||||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
										resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			type Repomd struct {
 | 
										type Repomd struct {
 | 
				
			||||||
				XMLName  xml.Name `xml:"repomd"`
 | 
											XMLName  xml.Name `xml:"repomd"`
 | 
				
			||||||
				Xmlns    string   `xml:"xmlns,attr"`
 | 
											Xmlns    string   `xml:"xmlns,attr"`
 | 
				
			||||||
				XmlnsRpm string   `xml:"xmlns:rpm,attr"`
 | 
											XmlnsRpm string   `xml:"xmlns:rpm,attr"`
 | 
				
			||||||
				Data     []struct {
 | 
											Data     []struct {
 | 
				
			||||||
					Type     string `xml:"type,attr"`
 | 
												Type     string `xml:"type,attr"`
 | 
				
			||||||
					Checksum struct {
 | 
												Checksum struct {
 | 
				
			||||||
						Value string `xml:",chardata"`
 | 
													Value string `xml:",chardata"`
 | 
				
			||||||
						Type  string `xml:"type,attr"`
 | 
													Type  string `xml:"type,attr"`
 | 
				
			||||||
					} `xml:"checksum"`
 | 
												} `xml:"checksum"`
 | 
				
			||||||
					OpenChecksum struct {
 | 
												OpenChecksum struct {
 | 
				
			||||||
						Value string `xml:",chardata"`
 | 
													Value string `xml:",chardata"`
 | 
				
			||||||
						Type  string `xml:"type,attr"`
 | 
													Type  string `xml:"type,attr"`
 | 
				
			||||||
					} `xml:"open-checksum"`
 | 
												} `xml:"open-checksum"`
 | 
				
			||||||
					Location struct {
 | 
												Location struct {
 | 
				
			||||||
						Href string `xml:"href,attr"`
 | 
													Href string `xml:"href,attr"`
 | 
				
			||||||
					} `xml:"location"`
 | 
												} `xml:"location"`
 | 
				
			||||||
					Timestamp int64 `xml:"timestamp"`
 | 
												Timestamp int64 `xml:"timestamp"`
 | 
				
			||||||
					Size      int64 `xml:"size"`
 | 
												Size      int64 `xml:"size"`
 | 
				
			||||||
					OpenSize  int64 `xml:"open-size"`
 | 
												OpenSize  int64 `xml:"open-size"`
 | 
				
			||||||
				} `xml:"data"`
 | 
											} `xml:"data"`
 | 
				
			||||||
			}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var result Repomd
 | 
										var result Repomd
 | 
				
			||||||
			decodeXML(t, resp, &result)
 | 
										decodeXML(t, resp, &result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.Len(t, result.Data, 3)
 | 
										assert.Len(t, result.Data, 3)
 | 
				
			||||||
			for _, d := range result.Data {
 | 
										for _, d := range result.Data {
 | 
				
			||||||
				assert.Equal(t, "sha256", d.Checksum.Type)
 | 
											assert.Equal(t, "sha256", d.Checksum.Type)
 | 
				
			||||||
				assert.NotEmpty(t, d.Checksum.Value)
 | 
											assert.NotEmpty(t, d.Checksum.Value)
 | 
				
			||||||
				assert.Equal(t, "sha256", d.OpenChecksum.Type)
 | 
											assert.Equal(t, "sha256", d.OpenChecksum.Type)
 | 
				
			||||||
				assert.NotEmpty(t, d.OpenChecksum.Value)
 | 
											assert.NotEmpty(t, d.OpenChecksum.Value)
 | 
				
			||||||
				assert.NotEqual(t, d.Checksum.Value, d.OpenChecksum.Value)
 | 
											assert.NotEqual(t, d.Checksum.Value, d.OpenChecksum.Value)
 | 
				
			||||||
				assert.Greater(t, d.OpenSize, d.Size)
 | 
											assert.Greater(t, d.OpenSize, d.Size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				switch d.Type {
 | 
											switch d.Type {
 | 
				
			||||||
				case "primary":
 | 
											case "primary":
 | 
				
			||||||
					assert.EqualValues(t, 722, d.Size)
 | 
												assert.EqualValues(t, 722, d.Size)
 | 
				
			||||||
					assert.EqualValues(t, 1759, d.OpenSize)
 | 
												assert.EqualValues(t, 1759, d.OpenSize)
 | 
				
			||||||
					assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
 | 
												assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
 | 
				
			||||||
				case "filelists":
 | 
											case "filelists":
 | 
				
			||||||
					assert.EqualValues(t, 257, d.Size)
 | 
												assert.EqualValues(t, 257, d.Size)
 | 
				
			||||||
					assert.EqualValues(t, 326, d.OpenSize)
 | 
												assert.EqualValues(t, 326, d.OpenSize)
 | 
				
			||||||
					assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href)
 | 
												assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href)
 | 
				
			||||||
				case "other":
 | 
											case "other":
 | 
				
			||||||
					assert.EqualValues(t, 306, d.Size)
 | 
												assert.EqualValues(t, 306, d.Size)
 | 
				
			||||||
					assert.EqualValues(t, 394, d.OpenSize)
 | 
												assert.EqualValues(t, 394, d.OpenSize)
 | 
				
			||||||
					assert.Equal(t, "repodata/other.xml.gz", d.Location.Href)
 | 
												assert.Equal(t, "repodata/other.xml.gz", d.Location.Href)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									t.Run("repomd.xml.asc", func(t *testing.T) {
 | 
				
			||||||
 | 
										defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										req = NewRequest(t, "GET", url+"/repomd.xml.asc")
 | 
				
			||||||
 | 
										resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
 | 
				
			||||||
 | 
										t.Helper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										zr, err := gzip.NewReader(resp.Body)
 | 
				
			||||||
 | 
										assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										assert.NoError(t, xml.NewDecoder(zr).Decode(v))
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
 | 
									t.Run("primary.xml.gz", func(t *testing.T) {
 | 
				
			||||||
 | 
										defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										req = NewRequest(t, "GET", url+"/primary.xml.gz")
 | 
				
			||||||
 | 
										resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										type EntryList struct {
 | 
				
			||||||
 | 
											Entries []*rpm_module.Entry `xml:"entry"`
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										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     []struct {
 | 
				
			||||||
 | 
												XMLName      xml.Name `xml:"package"`
 | 
				
			||||||
 | 
												Type         string   `xml:"type,attr"`
 | 
				
			||||||
 | 
												Name         string   `xml:"name"`
 | 
				
			||||||
 | 
												Architecture string   `xml:"arch"`
 | 
				
			||||||
 | 
												Version      struct {
 | 
				
			||||||
 | 
													Epoch   string `xml:"epoch,attr"`
 | 
				
			||||||
 | 
													Version string `xml:"ver,attr"`
 | 
				
			||||||
 | 
													Release string `xml:"rel,attr"`
 | 
				
			||||||
 | 
												} `xml:"version"`
 | 
				
			||||||
 | 
												Checksum struct {
 | 
				
			||||||
 | 
													Checksum string `xml:",chardata"`
 | 
				
			||||||
 | 
													Type     string `xml:"type,attr"`
 | 
				
			||||||
 | 
													Pkgid    string `xml:"pkgid,attr"`
 | 
				
			||||||
 | 
												} `xml:"checksum"`
 | 
				
			||||||
 | 
												Summary     string `xml:"summary"`
 | 
				
			||||||
 | 
												Description string `xml:"description"`
 | 
				
			||||||
 | 
												Packager    string `xml:"packager"`
 | 
				
			||||||
 | 
												URL         string `xml:"url"`
 | 
				
			||||||
 | 
												Time        struct {
 | 
				
			||||||
 | 
													File  uint64 `xml:"file,attr"`
 | 
				
			||||||
 | 
													Build uint64 `xml:"build,attr"`
 | 
				
			||||||
 | 
												} `xml:"time"`
 | 
				
			||||||
 | 
												Size struct {
 | 
				
			||||||
 | 
													Package   int64  `xml:"package,attr"`
 | 
				
			||||||
 | 
													Installed uint64 `xml:"installed,attr"`
 | 
				
			||||||
 | 
													Archive   uint64 `xml:"archive,attr"`
 | 
				
			||||||
 | 
												} `xml:"size"`
 | 
				
			||||||
 | 
												Location struct {
 | 
				
			||||||
 | 
													Href string `xml:"href,attr"`
 | 
				
			||||||
 | 
												} `xml:"location"`
 | 
				
			||||||
 | 
												Format struct {
 | 
				
			||||||
 | 
													License   string             `xml:"license"`
 | 
				
			||||||
 | 
													Vendor    string             `xml:"vendor"`
 | 
				
			||||||
 | 
													Group     string             `xml:"group"`
 | 
				
			||||||
 | 
													Buildhost string             `xml:"buildhost"`
 | 
				
			||||||
 | 
													Sourcerpm string             `xml:"sourcerpm"`
 | 
				
			||||||
 | 
													Provides  EntryList          `xml:"provides"`
 | 
				
			||||||
 | 
													Requires  EntryList          `xml:"requires"`
 | 
				
			||||||
 | 
													Conflicts EntryList          `xml:"conflicts"`
 | 
				
			||||||
 | 
													Obsoletes EntryList          `xml:"obsoletes"`
 | 
				
			||||||
 | 
													Files     []*rpm_module.File `xml:"file"`
 | 
				
			||||||
 | 
												} `xml:"format"`
 | 
				
			||||||
 | 
											} `xml:"package"`
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var result Metadata
 | 
				
			||||||
 | 
										decodeGzipXML(t, resp, &result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										assert.EqualValues(t, 1, result.PackageCount)
 | 
				
			||||||
 | 
										assert.Len(t, result.Packages, 1)
 | 
				
			||||||
 | 
										p := result.Packages[0]
 | 
				
			||||||
 | 
										assert.Equal(t, "rpm", p.Type)
 | 
				
			||||||
 | 
										assert.Equal(t, packageName, p.Name)
 | 
				
			||||||
 | 
										assert.Equal(t, packageArchitecture, p.Architecture)
 | 
				
			||||||
 | 
										assert.Equal(t, "YES", p.Checksum.Pkgid)
 | 
				
			||||||
 | 
										assert.Equal(t, "sha256", p.Checksum.Type)
 | 
				
			||||||
 | 
										assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
 | 
				
			||||||
 | 
										assert.Equal(t, "https://gitea.io", p.URL)
 | 
				
			||||||
 | 
										assert.EqualValues(t, len(content), p.Size.Package)
 | 
				
			||||||
 | 
										assert.EqualValues(t, 13, p.Size.Installed)
 | 
				
			||||||
 | 
										assert.EqualValues(t, 272, p.Size.Archive)
 | 
				
			||||||
 | 
										assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
 | 
				
			||||||
 | 
										f := p.Format
 | 
				
			||||||
 | 
										assert.Equal(t, "MIT", f.License)
 | 
				
			||||||
 | 
										assert.Len(t, f.Provides.Entries, 2)
 | 
				
			||||||
 | 
										assert.Len(t, f.Requires.Entries, 7)
 | 
				
			||||||
 | 
										assert.Empty(t, f.Conflicts.Entries)
 | 
				
			||||||
 | 
										assert.Empty(t, f.Obsoletes.Entries)
 | 
				
			||||||
 | 
										assert.Len(t, f.Files, 1)
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									t.Run("filelists.xml.gz", func(t *testing.T) {
 | 
				
			||||||
 | 
										defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										req = NewRequest(t, "GET", url+"/filelists.xml.gz")
 | 
				
			||||||
 | 
										resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										type Filelists struct {
 | 
				
			||||||
 | 
											XMLName      xml.Name `xml:"filelists"`
 | 
				
			||||||
 | 
											Xmlns        string   `xml:"xmlns,attr"`
 | 
				
			||||||
 | 
											PackageCount int      `xml:"packages,attr"`
 | 
				
			||||||
 | 
											Packages     []struct {
 | 
				
			||||||
 | 
												Pkgid        string `xml:"pkgid,attr"`
 | 
				
			||||||
 | 
												Name         string `xml:"name,attr"`
 | 
				
			||||||
 | 
												Architecture string `xml:"arch,attr"`
 | 
				
			||||||
 | 
												Version      struct {
 | 
				
			||||||
 | 
													Epoch   string `xml:"epoch,attr"`
 | 
				
			||||||
 | 
													Version string `xml:"ver,attr"`
 | 
				
			||||||
 | 
													Release string `xml:"rel,attr"`
 | 
				
			||||||
 | 
												} `xml:"version"`
 | 
				
			||||||
 | 
												Files []*rpm_module.File `xml:"file"`
 | 
				
			||||||
 | 
											} `xml:"package"`
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var result Filelists
 | 
				
			||||||
 | 
										decodeGzipXML(t, resp, &result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										assert.EqualValues(t, 1, result.PackageCount)
 | 
				
			||||||
 | 
										assert.Len(t, result.Packages, 1)
 | 
				
			||||||
 | 
										p := result.Packages[0]
 | 
				
			||||||
 | 
										assert.NotEmpty(t, p.Pkgid)
 | 
				
			||||||
 | 
										assert.Equal(t, packageName, p.Name)
 | 
				
			||||||
 | 
										assert.Equal(t, packageArchitecture, p.Architecture)
 | 
				
			||||||
 | 
										assert.Len(t, p.Files, 1)
 | 
				
			||||||
 | 
										f := p.Files[0]
 | 
				
			||||||
 | 
										assert.Equal(t, "/usr/local/bin/hello", f.Path)
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									t.Run("other.xml.gz", func(t *testing.T) {
 | 
				
			||||||
 | 
										defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										req = NewRequest(t, "GET", url+"/other.xml.gz")
 | 
				
			||||||
 | 
										resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										type Other struct {
 | 
				
			||||||
 | 
											XMLName      xml.Name `xml:"otherdata"`
 | 
				
			||||||
 | 
											Xmlns        string   `xml:"xmlns,attr"`
 | 
				
			||||||
 | 
											PackageCount int      `xml:"packages,attr"`
 | 
				
			||||||
 | 
											Packages     []struct {
 | 
				
			||||||
 | 
												Pkgid        string `xml:"pkgid,attr"`
 | 
				
			||||||
 | 
												Name         string `xml:"name,attr"`
 | 
				
			||||||
 | 
												Architecture string `xml:"arch,attr"`
 | 
				
			||||||
 | 
												Version      struct {
 | 
				
			||||||
 | 
													Epoch   string `xml:"epoch,attr"`
 | 
				
			||||||
 | 
													Version string `xml:"ver,attr"`
 | 
				
			||||||
 | 
													Release string `xml:"rel,attr"`
 | 
				
			||||||
 | 
												} `xml:"version"`
 | 
				
			||||||
 | 
												Changelogs []*rpm_module.Changelog `xml:"changelog"`
 | 
				
			||||||
 | 
											} `xml:"package"`
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var result Other
 | 
				
			||||||
 | 
										decodeGzipXML(t, resp, &result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										assert.EqualValues(t, 1, result.PackageCount)
 | 
				
			||||||
 | 
										assert.Len(t, result.Packages, 1)
 | 
				
			||||||
 | 
										p := result.Packages[0]
 | 
				
			||||||
 | 
										assert.NotEmpty(t, p.Pkgid)
 | 
				
			||||||
 | 
										assert.Equal(t, packageName, p.Name)
 | 
				
			||||||
 | 
										assert.Equal(t, packageArchitecture, p.Architecture)
 | 
				
			||||||
 | 
										assert.Len(t, p.Changelogs, 1)
 | 
				
			||||||
 | 
										c := p.Changelogs[0]
 | 
				
			||||||
 | 
										assert.Equal(t, "KN4CK3R <dummy@gitea.io>", c.Author)
 | 
				
			||||||
 | 
										assert.EqualValues(t, 1678276800, c.Date)
 | 
				
			||||||
 | 
										assert.Equal(t, "- Changelog message.", c.Text)
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								t.Run("Delete", func(t *testing.T) {
 | 
				
			||||||
 | 
									defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
 | 
				
			||||||
 | 
									MakeRequest(t, req, http.StatusUnauthorized)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
 | 
				
			||||||
 | 
										AddBasicAuth(user.Name)
 | 
				
			||||||
 | 
									MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
 | 
				
			||||||
 | 
									assert.NoError(t, err)
 | 
				
			||||||
 | 
									assert.Empty(t, pvs)
 | 
				
			||||||
 | 
									req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
 | 
				
			||||||
 | 
										AddBasicAuth(user.Name)
 | 
				
			||||||
 | 
									MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
		t.Run("repomd.xml.asc", func(t *testing.T) {
 | 
					 | 
				
			||||||
			defer tests.PrintCurrentTest(t)()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			req = NewRequest(t, "GET", url+"/repomd.xml.asc")
 | 
					 | 
				
			||||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
 | 
					 | 
				
			||||||
			t.Helper()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			zr, err := gzip.NewReader(resp.Body)
 | 
					 | 
				
			||||||
			assert.NoError(t, err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			assert.NoError(t, xml.NewDecoder(zr).Decode(v))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		t.Run("primary.xml.gz", func(t *testing.T) {
 | 
					 | 
				
			||||||
			defer tests.PrintCurrentTest(t)()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			req = NewRequest(t, "GET", url+"/primary.xml.gz")
 | 
					 | 
				
			||||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			type EntryList struct {
 | 
					 | 
				
			||||||
				Entries []*rpm_module.Entry `xml:"entry"`
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			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     []struct {
 | 
					 | 
				
			||||||
					XMLName      xml.Name `xml:"package"`
 | 
					 | 
				
			||||||
					Type         string   `xml:"type,attr"`
 | 
					 | 
				
			||||||
					Name         string   `xml:"name"`
 | 
					 | 
				
			||||||
					Architecture string   `xml:"arch"`
 | 
					 | 
				
			||||||
					Version      struct {
 | 
					 | 
				
			||||||
						Epoch   string `xml:"epoch,attr"`
 | 
					 | 
				
			||||||
						Version string `xml:"ver,attr"`
 | 
					 | 
				
			||||||
						Release string `xml:"rel,attr"`
 | 
					 | 
				
			||||||
					} `xml:"version"`
 | 
					 | 
				
			||||||
					Checksum struct {
 | 
					 | 
				
			||||||
						Checksum string `xml:",chardata"`
 | 
					 | 
				
			||||||
						Type     string `xml:"type,attr"`
 | 
					 | 
				
			||||||
						Pkgid    string `xml:"pkgid,attr"`
 | 
					 | 
				
			||||||
					} `xml:"checksum"`
 | 
					 | 
				
			||||||
					Summary     string `xml:"summary"`
 | 
					 | 
				
			||||||
					Description string `xml:"description"`
 | 
					 | 
				
			||||||
					Packager    string `xml:"packager"`
 | 
					 | 
				
			||||||
					URL         string `xml:"url"`
 | 
					 | 
				
			||||||
					Time        struct {
 | 
					 | 
				
			||||||
						File  uint64 `xml:"file,attr"`
 | 
					 | 
				
			||||||
						Build uint64 `xml:"build,attr"`
 | 
					 | 
				
			||||||
					} `xml:"time"`
 | 
					 | 
				
			||||||
					Size struct {
 | 
					 | 
				
			||||||
						Package   int64  `xml:"package,attr"`
 | 
					 | 
				
			||||||
						Installed uint64 `xml:"installed,attr"`
 | 
					 | 
				
			||||||
						Archive   uint64 `xml:"archive,attr"`
 | 
					 | 
				
			||||||
					} `xml:"size"`
 | 
					 | 
				
			||||||
					Location struct {
 | 
					 | 
				
			||||||
						Href string `xml:"href,attr"`
 | 
					 | 
				
			||||||
					} `xml:"location"`
 | 
					 | 
				
			||||||
					Format struct {
 | 
					 | 
				
			||||||
						License   string             `xml:"license"`
 | 
					 | 
				
			||||||
						Vendor    string             `xml:"vendor"`
 | 
					 | 
				
			||||||
						Group     string             `xml:"group"`
 | 
					 | 
				
			||||||
						Buildhost string             `xml:"buildhost"`
 | 
					 | 
				
			||||||
						Sourcerpm string             `xml:"sourcerpm"`
 | 
					 | 
				
			||||||
						Provides  EntryList          `xml:"provides"`
 | 
					 | 
				
			||||||
						Requires  EntryList          `xml:"requires"`
 | 
					 | 
				
			||||||
						Conflicts EntryList          `xml:"conflicts"`
 | 
					 | 
				
			||||||
						Obsoletes EntryList          `xml:"obsoletes"`
 | 
					 | 
				
			||||||
						Files     []*rpm_module.File `xml:"file"`
 | 
					 | 
				
			||||||
					} `xml:"format"`
 | 
					 | 
				
			||||||
				} `xml:"package"`
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var result Metadata
 | 
					 | 
				
			||||||
			decodeGzipXML(t, resp, &result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			assert.EqualValues(t, 1, result.PackageCount)
 | 
					 | 
				
			||||||
			assert.Len(t, result.Packages, 1)
 | 
					 | 
				
			||||||
			p := result.Packages[0]
 | 
					 | 
				
			||||||
			assert.Equal(t, "rpm", p.Type)
 | 
					 | 
				
			||||||
			assert.Equal(t, packageName, p.Name)
 | 
					 | 
				
			||||||
			assert.Equal(t, packageArchitecture, p.Architecture)
 | 
					 | 
				
			||||||
			assert.Equal(t, "YES", p.Checksum.Pkgid)
 | 
					 | 
				
			||||||
			assert.Equal(t, "sha256", p.Checksum.Type)
 | 
					 | 
				
			||||||
			assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
 | 
					 | 
				
			||||||
			assert.Equal(t, "https://gitea.io", p.URL)
 | 
					 | 
				
			||||||
			assert.EqualValues(t, len(content), p.Size.Package)
 | 
					 | 
				
			||||||
			assert.EqualValues(t, 13, p.Size.Installed)
 | 
					 | 
				
			||||||
			assert.EqualValues(t, 272, p.Size.Archive)
 | 
					 | 
				
			||||||
			assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
 | 
					 | 
				
			||||||
			f := p.Format
 | 
					 | 
				
			||||||
			assert.Equal(t, "MIT", f.License)
 | 
					 | 
				
			||||||
			assert.Len(t, f.Provides.Entries, 2)
 | 
					 | 
				
			||||||
			assert.Len(t, f.Requires.Entries, 7)
 | 
					 | 
				
			||||||
			assert.Empty(t, f.Conflicts.Entries)
 | 
					 | 
				
			||||||
			assert.Empty(t, f.Obsoletes.Entries)
 | 
					 | 
				
			||||||
			assert.Len(t, f.Files, 1)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		t.Run("filelists.xml.gz", func(t *testing.T) {
 | 
					 | 
				
			||||||
			defer tests.PrintCurrentTest(t)()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			req = NewRequest(t, "GET", url+"/filelists.xml.gz")
 | 
					 | 
				
			||||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			type Filelists struct {
 | 
					 | 
				
			||||||
				XMLName      xml.Name `xml:"filelists"`
 | 
					 | 
				
			||||||
				Xmlns        string   `xml:"xmlns,attr"`
 | 
					 | 
				
			||||||
				PackageCount int      `xml:"packages,attr"`
 | 
					 | 
				
			||||||
				Packages     []struct {
 | 
					 | 
				
			||||||
					Pkgid        string `xml:"pkgid,attr"`
 | 
					 | 
				
			||||||
					Name         string `xml:"name,attr"`
 | 
					 | 
				
			||||||
					Architecture string `xml:"arch,attr"`
 | 
					 | 
				
			||||||
					Version      struct {
 | 
					 | 
				
			||||||
						Epoch   string `xml:"epoch,attr"`
 | 
					 | 
				
			||||||
						Version string `xml:"ver,attr"`
 | 
					 | 
				
			||||||
						Release string `xml:"rel,attr"`
 | 
					 | 
				
			||||||
					} `xml:"version"`
 | 
					 | 
				
			||||||
					Files []*rpm_module.File `xml:"file"`
 | 
					 | 
				
			||||||
				} `xml:"package"`
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var result Filelists
 | 
					 | 
				
			||||||
			decodeGzipXML(t, resp, &result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			assert.EqualValues(t, 1, result.PackageCount)
 | 
					 | 
				
			||||||
			assert.Len(t, result.Packages, 1)
 | 
					 | 
				
			||||||
			p := result.Packages[0]
 | 
					 | 
				
			||||||
			assert.NotEmpty(t, p.Pkgid)
 | 
					 | 
				
			||||||
			assert.Equal(t, packageName, p.Name)
 | 
					 | 
				
			||||||
			assert.Equal(t, packageArchitecture, p.Architecture)
 | 
					 | 
				
			||||||
			assert.Len(t, p.Files, 1)
 | 
					 | 
				
			||||||
			f := p.Files[0]
 | 
					 | 
				
			||||||
			assert.Equal(t, "/usr/local/bin/hello", f.Path)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		t.Run("other.xml.gz", func(t *testing.T) {
 | 
					 | 
				
			||||||
			defer tests.PrintCurrentTest(t)()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			req = NewRequest(t, "GET", url+"/other.xml.gz")
 | 
					 | 
				
			||||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			type Other struct {
 | 
					 | 
				
			||||||
				XMLName      xml.Name `xml:"otherdata"`
 | 
					 | 
				
			||||||
				Xmlns        string   `xml:"xmlns,attr"`
 | 
					 | 
				
			||||||
				PackageCount int      `xml:"packages,attr"`
 | 
					 | 
				
			||||||
				Packages     []struct {
 | 
					 | 
				
			||||||
					Pkgid        string `xml:"pkgid,attr"`
 | 
					 | 
				
			||||||
					Name         string `xml:"name,attr"`
 | 
					 | 
				
			||||||
					Architecture string `xml:"arch,attr"`
 | 
					 | 
				
			||||||
					Version      struct {
 | 
					 | 
				
			||||||
						Epoch   string `xml:"epoch,attr"`
 | 
					 | 
				
			||||||
						Version string `xml:"ver,attr"`
 | 
					 | 
				
			||||||
						Release string `xml:"rel,attr"`
 | 
					 | 
				
			||||||
					} `xml:"version"`
 | 
					 | 
				
			||||||
					Changelogs []*rpm_module.Changelog `xml:"changelog"`
 | 
					 | 
				
			||||||
				} `xml:"package"`
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var result Other
 | 
					 | 
				
			||||||
			decodeGzipXML(t, resp, &result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			assert.EqualValues(t, 1, result.PackageCount)
 | 
					 | 
				
			||||||
			assert.Len(t, result.Packages, 1)
 | 
					 | 
				
			||||||
			p := result.Packages[0]
 | 
					 | 
				
			||||||
			assert.NotEmpty(t, p.Pkgid)
 | 
					 | 
				
			||||||
			assert.Equal(t, packageName, p.Name)
 | 
					 | 
				
			||||||
			assert.Equal(t, packageArchitecture, p.Architecture)
 | 
					 | 
				
			||||||
			assert.Len(t, p.Changelogs, 1)
 | 
					 | 
				
			||||||
			c := p.Changelogs[0]
 | 
					 | 
				
			||||||
			assert.Equal(t, "KN4CK3R <dummy@gitea.io>", c.Author)
 | 
					 | 
				
			||||||
			assert.EqualValues(t, 1678276800, c.Date)
 | 
					 | 
				
			||||||
			assert.Equal(t, "- Changelog message.", c.Text)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("Delete", func(t *testing.T) {
 | 
					 | 
				
			||||||
		defer tests.PrintCurrentTest(t)()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
 | 
					 | 
				
			||||||
		MakeRequest(t, req, http.StatusUnauthorized)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
 | 
					 | 
				
			||||||
			AddBasicAuth(user.Name)
 | 
					 | 
				
			||||||
		MakeRequest(t, req, http.StatusNoContent)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
 | 
					 | 
				
			||||||
		assert.NoError(t, err)
 | 
					 | 
				
			||||||
		assert.Empty(t, pvs)
 | 
					 | 
				
			||||||
		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
 | 
					 | 
				
			||||||
			AddBasicAuth(user.Name)
 | 
					 | 
				
			||||||
		MakeRequest(t, req, http.StatusNotFound)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user