mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 08:34:26 +00:00
Add Arch package registry (#32692)
Close #25037 Close #31037 This PR adds a Arch package registry usable with pacman. ![grafik](https://github.com/user-attachments/assets/81cdb0c2-02f9-4733-bee2-e48af6b45224) Rewrite of #25396 and #31037. You can follow [this tutorial](https://wiki.archlinux.org/title/Creating_packages) to build a package for testing. Docs PR: https://gitea.com/gitea/docs/pulls/111 Co-authored-by: [d1nch8g@ion.lc](mailto:d1nch8g@ion.lc) Co-authored-by: @ExplodingDragon --------- Co-authored-by: dancheg97 <dancheg97@fmnx.su> Co-authored-by: dragon <ExplodingFKL@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
5ab7aa700f
commit
0c3c041c88
38
models/packages/arch/search.go
Normal file
38
models/packages/arch/search.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
)
|
||||
|
||||
// GetRepositories gets all available repositories
|
||||
func GetRepositories(ctx context.Context, ownerID int64) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeArch,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
arch_module.PropertyRepository,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// GetArchitectures gets all available architectures for the given repository
|
||||
func GetArchitectures(ctx context.Context, ownerID int64, repository string) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeArch,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
arch_module.PropertyArchitecture,
|
||||
&packages_model.DistinctPropertyDependency{
|
||||
Name: arch_module.PropertyRepository,
|
||||
Value: repository,
|
||||
},
|
||||
)
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/packages/alpine"
|
||||
"code.gitea.io/gitea/modules/packages/arch"
|
||||
"code.gitea.io/gitea/modules/packages/cargo"
|
||||
"code.gitea.io/gitea/modules/packages/chef"
|
||||
"code.gitea.io/gitea/modules/packages/composer"
|
||||
@ -150,6 +151,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
|
||||
switch p.Type {
|
||||
case TypeAlpine:
|
||||
metadata = &alpine.VersionMetadata{}
|
||||
case TypeArch:
|
||||
metadata = &arch.VersionMetadata{}
|
||||
case TypeCargo:
|
||||
metadata = &cargo.Metadata{}
|
||||
case TypeChef:
|
||||
|
@ -31,6 +31,7 @@ type Type string
|
||||
// List of supported packages
|
||||
const (
|
||||
TypeAlpine Type = "alpine"
|
||||
TypeArch Type = "arch"
|
||||
TypeCargo Type = "cargo"
|
||||
TypeChef Type = "chef"
|
||||
TypeComposer Type = "composer"
|
||||
@ -55,6 +56,7 @@ const (
|
||||
|
||||
var TypeList = []Type{
|
||||
TypeAlpine,
|
||||
TypeArch,
|
||||
TypeCargo,
|
||||
TypeChef,
|
||||
TypeComposer,
|
||||
@ -82,6 +84,8 @@ func (pt Type) Name() string {
|
||||
switch pt {
|
||||
case TypeAlpine:
|
||||
return "Alpine"
|
||||
case TypeArch:
|
||||
return "Arch"
|
||||
case TypeCargo:
|
||||
return "Cargo"
|
||||
case TypeChef:
|
||||
@ -131,6 +135,8 @@ func (pt Type) SVGName() string {
|
||||
switch pt {
|
||||
case TypeAlpine:
|
||||
return "gitea-alpine"
|
||||
case TypeArch:
|
||||
return "gitea-arch"
|
||||
case TypeCargo:
|
||||
return "gitea-cargo"
|
||||
case TypeChef:
|
||||
|
@ -221,6 +221,11 @@ func SearchFiles(ctx context.Context, opts *PackageFileSearchOptions) ([]*Packag
|
||||
return pfs, count, err
|
||||
}
|
||||
|
||||
// HasFiles tests if there are files of packages matching the search options
|
||||
func HasFiles(ctx context.Context, opts *PackageFileSearchOptions) (bool, error) {
|
||||
return db.Exist[PackageFile](ctx, opts.toConds())
|
||||
}
|
||||
|
||||
// CalculateFileSize sums up all blob sizes matching the search options.
|
||||
// It does NOT respect the deduplication of blobs.
|
||||
func CalculateFileSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) {
|
||||
|
249
modules/packages/arch/metadata.go
Normal file
249
modules/packages/arch/metadata.go
Normal file
@ -0,0 +1,249 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
const (
|
||||
PropertyRepository = "arch.repository"
|
||||
PropertyArchitecture = "arch.architecture"
|
||||
PropertySignature = "arch.signature"
|
||||
PropertyMetadata = "arch.metadata"
|
||||
|
||||
SettingKeyPrivate = "arch.key.private"
|
||||
SettingKeyPublic = "arch.key.public"
|
||||
|
||||
RepositoryPackage = "_arch"
|
||||
RepositoryVersion = "_repository"
|
||||
|
||||
AnyArch = "any"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf(".PKGINFO file is missing")
|
||||
ErrUnsupportedFormat = util.NewInvalidArgumentErrorf("unsupported package container format")
|
||||
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
|
||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||
ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
|
||||
|
||||
// https://man.archlinux.org/man/PKGBUILD.5
|
||||
namePattern = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
|
||||
versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
Name string
|
||||
Version string
|
||||
VersionMetadata VersionMetadata
|
||||
FileMetadata FileMetadata
|
||||
FileCompressionExtension string
|
||||
}
|
||||
|
||||
type VersionMetadata struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
ProjectURL string `json:"project_url,omitempty"`
|
||||
Licenses []string `json:"licenses,omitempty"`
|
||||
}
|
||||
|
||||
type FileMetadata struct {
|
||||
Architecture string `json:"architecture"`
|
||||
Base string `json:"base,omitempty"`
|
||||
InstalledSize int64 `json:"installed_size,omitempty"`
|
||||
BuildDate int64 `json:"build_date,omitempty"`
|
||||
Packager string `json:"packager,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Provides []string `json:"provides,omitempty"`
|
||||
Depends []string `json:"depends,omitempty"`
|
||||
OptDepends []string `json:"opt_depends,omitempty"`
|
||||
MakeDepends []string `json:"make_depends,omitempty"`
|
||||
CheckDepends []string `json:"check_depends,omitempty"`
|
||||
XData []string `json:"xdata,omitempty"`
|
||||
Backup []string `json:"backup,omitempty"`
|
||||
Files []string `json:"files,omitempty"`
|
||||
}
|
||||
|
||||
// ParsePackage parses an Arch package file
|
||||
func ParsePackage(r io.Reader) (*Package, error) {
|
||||
header := make([]byte, 10)
|
||||
n, err := util.ReadAtMost(r, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r = io.MultiReader(bytes.NewReader(header[:n]), r)
|
||||
|
||||
var inner io.Reader
|
||||
var compressionType string
|
||||
if bytes.HasPrefix(header, []byte{0x28, 0xB5, 0x2F, 0xFD}) { // zst
|
||||
zr, err := zstd.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
inner = zr
|
||||
compressionType = "zst"
|
||||
} else if bytes.HasPrefix(header, []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}) { // xz
|
||||
xzr, err := xz.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inner = xzr
|
||||
compressionType = "xz"
|
||||
} else if bytes.HasPrefix(header, []byte{0x1F, 0x8B}) { // gz
|
||||
gzr, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
inner = gzr
|
||||
compressionType = "gz"
|
||||
} else {
|
||||
return nil, ErrUnsupportedFormat
|
||||
}
|
||||
|
||||
var p *Package
|
||||
files := make([]string, 0, 10)
|
||||
|
||||
tr := tar.NewReader(inner)
|
||||
for {
|
||||
hd, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hd.Typeflag != tar.TypeReg {
|
||||
continue
|
||||
}
|
||||
|
||||
filename := hd.FileInfo().Name()
|
||||
if filename == ".PKGINFO" {
|
||||
p, err = ParsePackageInfo(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !strings.HasPrefix(filename, ".") {
|
||||
files = append(files, hd.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return nil, ErrMissingPKGINFOFile
|
||||
}
|
||||
|
||||
p.FileMetadata.Files = files
|
||||
p.FileCompressionExtension = compressionType
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ParsePackageInfo parses a .PKGINFO file to retrieve the metadata
|
||||
// https://man.archlinux.org/man/PKGBUILD.5
|
||||
// https://gitlab.archlinux.org/pacman/pacman/-/blob/master/lib/libalpm/be_package.c#L161
|
||||
func ParsePackageInfo(r io.Reader) (*Package, error) {
|
||||
p := &Package{}
|
||||
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
i := strings.IndexRune(line, '=')
|
||||
if i == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(line[:i])
|
||||
value := strings.TrimSpace(line[i+1:])
|
||||
|
||||
switch key {
|
||||
case "pkgname":
|
||||
p.Name = value
|
||||
case "pkgbase":
|
||||
p.FileMetadata.Base = value
|
||||
case "pkgver":
|
||||
p.Version = value
|
||||
case "pkgdesc":
|
||||
p.VersionMetadata.Description = value
|
||||
case "url":
|
||||
p.VersionMetadata.ProjectURL = value
|
||||
case "packager":
|
||||
p.FileMetadata.Packager = value
|
||||
case "arch":
|
||||
p.FileMetadata.Architecture = value
|
||||
case "license":
|
||||
p.VersionMetadata.Licenses = append(p.VersionMetadata.Licenses, value)
|
||||
case "provides":
|
||||
p.FileMetadata.Provides = append(p.FileMetadata.Provides, value)
|
||||
case "depend":
|
||||
p.FileMetadata.Depends = append(p.FileMetadata.Depends, value)
|
||||
case "optdepend":
|
||||
p.FileMetadata.OptDepends = append(p.FileMetadata.OptDepends, value)
|
||||
case "makedepend":
|
||||
p.FileMetadata.MakeDepends = append(p.FileMetadata.MakeDepends, value)
|
||||
case "checkdepend":
|
||||
p.FileMetadata.CheckDepends = append(p.FileMetadata.CheckDepends, value)
|
||||
case "backup":
|
||||
p.FileMetadata.Backup = append(p.FileMetadata.Backup, value)
|
||||
case "group":
|
||||
p.FileMetadata.Groups = append(p.FileMetadata.Groups, value)
|
||||
case "builddate":
|
||||
date, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.FileMetadata.BuildDate = date
|
||||
case "size":
|
||||
size, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.FileMetadata.InstalledSize = size
|
||||
case "xdata":
|
||||
p.FileMetadata.XData = append(p.FileMetadata.XData, value)
|
||||
}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !namePattern.MatchString(p.Name) {
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
if !versionPattern.MatchString(p.Version) {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
if p.FileMetadata.Architecture == "" {
|
||||
return nil, ErrInvalidArchitecture
|
||||
}
|
||||
|
||||
if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
|
||||
p.VersionMetadata.ProjectURL = ""
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
157
modules/packages/arch/metadata_test.go
Normal file
157
modules/packages/arch/metadata_test.go
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
const (
|
||||
packageName = "gitea"
|
||||
packageVersion = "1.0.1"
|
||||
packageDescription = "Package Description"
|
||||
packageProjectURL = "https://gitea.com"
|
||||
packagePackager = "KN4CK3R <packager@gitea.com>"
|
||||
)
|
||||
|
||||
func createPKGINFOContent(name, version string) []byte {
|
||||
return []byte(`pkgname = ` + name + `
|
||||
pkgbase = ` + name + `
|
||||
pkgver = ` + version + `
|
||||
pkgdesc = ` + packageDescription + `
|
||||
url = ` + packageProjectURL + `
|
||||
# comment
|
||||
group=group
|
||||
builddate = 1678834800
|
||||
size = 123456
|
||||
arch = x86_64
|
||||
license = MIT
|
||||
packager = ` + packagePackager + `
|
||||
depend = common
|
||||
xdata = value
|
||||
depend = gitea
|
||||
provides = common
|
||||
provides = gitea
|
||||
optdepend = hex
|
||||
checkdepend = common
|
||||
makedepend = cmake
|
||||
backup = usr/bin/paket1`)
|
||||
}
|
||||
|
||||
func TestParsePackage(t *testing.T) {
|
||||
createPackage := func(compression string, files map[string][]byte) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
var cw io.WriteCloser
|
||||
switch compression {
|
||||
case "zst":
|
||||
cw, _ = zstd.NewWriter(&buf)
|
||||
case "xz":
|
||||
cw, _ = xz.NewWriter(&buf)
|
||||
case "gz":
|
||||
cw = gzip.NewWriter(&buf)
|
||||
}
|
||||
tw := tar.NewWriter(cw)
|
||||
|
||||
for name, content := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: name,
|
||||
Mode: 0o600,
|
||||
Size: int64(len(content)),
|
||||
}
|
||||
tw.WriteHeader(hdr)
|
||||
tw.Write(content)
|
||||
}
|
||||
|
||||
tw.Close()
|
||||
cw.Close()
|
||||
|
||||
return &buf
|
||||
}
|
||||
|
||||
for _, c := range []string{"gz", "xz", "zst"} {
|
||||
t.Run(c, func(t *testing.T) {
|
||||
t.Run("MissingPKGINFOFile", func(t *testing.T) {
|
||||
data := createPackage(c, map[string][]byte{"dummy.txt": {}})
|
||||
|
||||
pp, err := ParsePackage(data)
|
||||
assert.Nil(t, pp)
|
||||
assert.ErrorIs(t, err, ErrMissingPKGINFOFile)
|
||||
})
|
||||
|
||||
t.Run("InvalidPKGINFOFile", func(t *testing.T) {
|
||||
data := createPackage(c, map[string][]byte{".PKGINFO": {}})
|
||||
|
||||
pp, err := ParsePackage(data)
|
||||
assert.Nil(t, pp)
|
||||
assert.ErrorIs(t, err, ErrInvalidName)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
data := createPackage(c, map[string][]byte{
|
||||
".PKGINFO": createPKGINFOContent(packageName, packageVersion),
|
||||
"/test/dummy.txt": {},
|
||||
})
|
||||
|
||||
p, err := ParsePackage(data)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
|
||||
assert.ElementsMatch(t, []string{"/test/dummy.txt"}, p.FileMetadata.Files)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePackageInfo(t *testing.T) {
|
||||
t.Run("InvalidName", func(t *testing.T) {
|
||||
data := createPKGINFOContent("", packageVersion)
|
||||
|
||||
p, err := ParsePackageInfo(bytes.NewReader(data))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidName)
|
||||
})
|
||||
|
||||
t.Run("InvalidVersion", func(t *testing.T) {
|
||||
data := createPKGINFOContent(packageName, "")
|
||||
|
||||
p, err := ParsePackageInfo(bytes.NewReader(data))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidVersion)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
data := createPKGINFOContent(packageName, packageVersion)
|
||||
|
||||
p, err := ParsePackageInfo(bytes.NewReader(data))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
|
||||
assert.Equal(t, packageName, p.Name)
|
||||
assert.Equal(t, packageName, p.FileMetadata.Base)
|
||||
assert.Equal(t, packageVersion, p.Version)
|
||||
assert.Equal(t, packageDescription, p.VersionMetadata.Description)
|
||||
assert.Equal(t, packagePackager, p.FileMetadata.Packager)
|
||||
assert.Equal(t, packageProjectURL, p.VersionMetadata.ProjectURL)
|
||||
assert.ElementsMatch(t, []string{"MIT"}, p.VersionMetadata.Licenses)
|
||||
assert.EqualValues(t, 1678834800, p.FileMetadata.BuildDate)
|
||||
assert.EqualValues(t, 123456, p.FileMetadata.InstalledSize)
|
||||
assert.Equal(t, "x86_64", p.FileMetadata.Architecture)
|
||||
assert.ElementsMatch(t, []string{"value"}, p.FileMetadata.XData)
|
||||
assert.ElementsMatch(t, []string{"group"}, p.FileMetadata.Groups)
|
||||
assert.ElementsMatch(t, []string{"common", "gitea"}, p.FileMetadata.Provides)
|
||||
assert.ElementsMatch(t, []string{"common", "gitea"}, p.FileMetadata.Depends)
|
||||
assert.ElementsMatch(t, []string{"hex"}, p.FileMetadata.OptDepends)
|
||||
assert.ElementsMatch(t, []string{"common"}, p.FileMetadata.CheckDepends)
|
||||
assert.ElementsMatch(t, []string{"cmake"}, p.FileMetadata.MakeDepends)
|
||||
assert.ElementsMatch(t, []string{"usr/bin/paket1"}, p.FileMetadata.Backup)
|
||||
})
|
||||
}
|
@ -22,6 +22,7 @@ var (
|
||||
LimitTotalOwnerCount int64
|
||||
LimitTotalOwnerSize int64
|
||||
LimitSizeAlpine int64
|
||||
LimitSizeArch int64
|
||||
LimitSizeCargo int64
|
||||
LimitSizeChef int64
|
||||
LimitSizeComposer int64
|
||||
@ -79,6 +80,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
|
||||
|
||||
Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
|
||||
Packages.LimitSizeAlpine = mustBytes(sec, "LIMIT_SIZE_ALPINE")
|
||||
Packages.LimitSizeArch = mustBytes(sec, "LIMIT_SIZE_ARCH")
|
||||
Packages.LimitSizeCargo = mustBytes(sec, "LIMIT_SIZE_CARGO")
|
||||
Packages.LimitSizeChef = mustBytes(sec, "LIMIT_SIZE_CHEF")
|
||||
Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER")
|
||||
|
@ -3524,6 +3524,11 @@ alpine.repository = Repository Info
|
||||
alpine.repository.branches = Branches
|
||||
alpine.repository.repositories = Repositories
|
||||
alpine.repository.architectures = Architectures
|
||||
arch.registry = Add server with related repository and architecture to <code>/etc/pacman.conf</code>:
|
||||
arch.install = Sync package with pacman:
|
||||
arch.repository = Repository Info
|
||||
arch.repository.repositories = Repositories
|
||||
arch.repository.architectures = Architectures
|
||||
cargo.registry = Setup this registry in the Cargo configuration file (for example <code>~/.cargo/config.toml</code>):
|
||||
cargo.install = To install the package using Cargo, run the following command:
|
||||
chef.registry = Setup this registry in your <code>~/.chef/config.rb</code> file:
|
||||
|
1
public/assets/img/svg/gitea-arch.svg
generated
Normal file
1
public/assets/img/svg/gitea-arch.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg gitea-arch" width="16" height="16" aria-hidden="true"><path fill="#1793d1" d="M256 72c-14 35-23 57-39 91 10 11 22 23 41 36-21-8-35-17-45-26-21 43-53 103-117 220 50-30 90-48 127-55-2-7-3-14-3-22v-1c1-33 18-58 38-56 20 1 36 29 35 62l-2 17c36 7 75 26 125 54l-27-50c-13-10-27-23-55-38 19 5 33 11 44 17-86-159-93-180-122-250z"/></svg>
|
After Width: | Height: | Size: 402 B |
@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/packages/alpine"
|
||||
"code.gitea.io/gitea/routers/api/packages/arch"
|
||||
"code.gitea.io/gitea/routers/api/packages/cargo"
|
||||
"code.gitea.io/gitea/routers/api/packages/chef"
|
||||
"code.gitea.io/gitea/routers/api/packages/composer"
|
||||
@ -135,6 +136,49 @@ func CommonRoutes() *web.Router {
|
||||
})
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/arch", func() {
|
||||
r.Methods("HEAD,GET", "/repository.key", arch.GetRepositoryKey)
|
||||
|
||||
r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
|
||||
path := strings.Trim(ctx.PathParam("*"), "/")
|
||||
|
||||
if ctx.Req.Method == "PUT" {
|
||||
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.SetPathParam("repository", path)
|
||||
arch.UploadPackageFile(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
pathFields := strings.Split(path, "/")
|
||||
pathFieldsLen := len(pathFields)
|
||||
|
||||
if (ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET") && pathFieldsLen >= 2 {
|
||||
ctx.SetPathParam("repository", strings.Join(pathFields[:pathFieldsLen-2], "/"))
|
||||
ctx.SetPathParam("architecture", pathFields[pathFieldsLen-2])
|
||||
ctx.SetPathParam("filename", pathFields[pathFieldsLen-1])
|
||||
arch.GetPackageOrRepositoryFile(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Req.Method == "DELETE" && pathFieldsLen >= 3 {
|
||||
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.SetPathParam("repository", strings.Join(pathFields[:pathFieldsLen-3], "/"))
|
||||
ctx.SetPathParam("name", pathFields[pathFieldsLen-3])
|
||||
ctx.SetPathParam("version", pathFields[pathFieldsLen-2])
|
||||
ctx.SetPathParam("architecture", pathFields[pathFieldsLen-1])
|
||||
arch.DeletePackageVersion(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNotFound)
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/cargo", func() {
|
||||
r.Group("/api/v1/crates", func() {
|
||||
r.Get("", cargo.SearchPackages)
|
||||
|
306
routers/api/packages/arch/arch.go
Normal file
306
routers/api/packages/arch/arch.go
Normal file
@ -0,0 +1,306 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
arch_service "code.gitea.io/gitea/services/packages/arch"
|
||||
)
|
||||
|
||||
func apiError(ctx *context.Context, status int, obj any) {
|
||||
helper.LogAndProcessError(ctx, status, obj, func(message string) {
|
||||
ctx.PlainText(status, message)
|
||||
})
|
||||
}
|
||||
|
||||
func GetRepositoryKey(ctx *context.Context) {
|
||||
_, pub, err := arch_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{
|
||||
ContentType: "application/pgp-keys",
|
||||
})
|
||||
}
|
||||
|
||||
func UploadPackageFile(ctx *context.Context) {
|
||||
repository := strings.TrimSpace(ctx.PathParam("repository"))
|
||||
|
||||
upload, needToClose, err := ctx.UploadStream()
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if needToClose {
|
||||
defer upload.Close()
|
||||
}
|
||||
|
||||
buf, err := packages_module.CreateHashedBufferFromReader(upload)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer buf.Close()
|
||||
|
||||
pck, err := arch_module.ParsePackage(buf)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
} else {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := buf.Seek(0, io.SeekStart); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := arch_service.SignData(ctx, ctx.Package.Owner.ID, buf)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := buf.Seek(0, io.SeekStart); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
release, err := arch_service.AquireRegistryLock(ctx, ctx.Package.Owner.ID)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer release()
|
||||
|
||||
// Search for duplicates with different file compression
|
||||
has, err := packages_model.HasFiles(ctx, &packages_model.PackageFileSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
PackageType: packages_model.TypeArch,
|
||||
Query: fmt.Sprintf("%s-%s-%s.pkg.tar.%%", pck.Name, pck.Version, pck.FileMetadata.Architecture),
|
||||
Properties: map[string]string{
|
||||
arch_module.PropertyRepository: repository,
|
||||
arch_module.PropertyArchitecture: pck.FileMetadata.Architecture,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if has {
|
||||
apiError(ctx, http.StatusConflict, packages_model.ErrDuplicatePackageFile)
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
|
||||
ctx,
|
||||
&packages_service.PackageCreationInfo{
|
||||
PackageInfo: packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
PackageType: packages_model.TypeArch,
|
||||
Name: pck.Name,
|
||||
Version: pck.Version,
|
||||
},
|
||||
Creator: ctx.Doer,
|
||||
Metadata: pck.VersionMetadata,
|
||||
},
|
||||
&packages_service.PackageFileCreationInfo{
|
||||
PackageFileInfo: packages_service.PackageFileInfo{
|
||||
Filename: fmt.Sprintf("%s-%s-%s.pkg.tar.%s", pck.Name, pck.Version, pck.FileMetadata.Architecture, pck.FileCompressionExtension),
|
||||
CompositeKey: fmt.Sprintf("%s|%s", repository, pck.FileMetadata.Architecture),
|
||||
},
|
||||
Creator: ctx.Doer,
|
||||
Data: buf,
|
||||
IsLead: true,
|
||||
Properties: map[string]string{
|
||||
arch_module.PropertyRepository: repository,
|
||||
arch_module.PropertyArchitecture: pck.FileMetadata.Architecture,
|
||||
arch_module.PropertyMetadata: string(fileMetadataRaw),
|
||||
arch_module.PropertySignature: base64.StdEncoding.EncodeToString(signature),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
|
||||
apiError(ctx, http.StatusConflict, err)
|
||||
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
|
||||
apiError(ctx, http.StatusForbidden, err)
|
||||
default:
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := arch_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, repository, pck.FileMetadata.Architecture); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
func GetPackageOrRepositoryFile(ctx *context.Context) {
|
||||
repository := ctx.PathParam("repository")
|
||||
architecture := ctx.PathParam("architecture")
|
||||
filename := ctx.PathParam("filename")
|
||||
filenameOrig := filename
|
||||
|
||||
isSignature := strings.HasSuffix(filename, ".sig")
|
||||
if isSignature {
|
||||
filename = filename[:len(filename)-len(".sig")]
|
||||
}
|
||||
|
||||
opts := &packages_model.PackageFileSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
PackageType: packages_model.TypeArch,
|
||||
Query: filename,
|
||||
CompositeKey: fmt.Sprintf("%s|%s", repository, architecture),
|
||||
}
|
||||
|
||||
if strings.HasSuffix(filename, ".db.tar.gz") || strings.HasSuffix(filename, ".files.tar.gz") || strings.HasSuffix(filename, ".files") || strings.HasSuffix(filename, ".db") {
|
||||
// The requested filename is based on the user-defined repository name.
|
||||
// Normalize everything to "packages.db".
|
||||
opts.Query = arch_service.IndexArchiveFilename
|
||||
|
||||
pv, err := arch_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
opts.VersionID = pv.ID
|
||||
}
|
||||
|
||||
pfs, _, err := packages_model.SearchFiles(ctx, opts)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if len(pfs) == 0 {
|
||||
// Try again with architecture 'any'
|
||||
if architecture == arch_module.AnyArch {
|
||||
apiError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
opts.CompositeKey = fmt.Sprintf("%s|%s", repository, arch_module.AnyArch)
|
||||
if pfs, _, err = packages_model.SearchFiles(ctx, opts); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(pfs) != 1 {
|
||||
apiError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if isSignature {
|
||||
pfps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pfs[0].ID, arch_module.PropertySignature)
|
||||
if err != nil || len(pfps) == 0 {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(pfps[0].Value)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.ServeContent(bytes.NewReader(data), &context.ServeHeaderOptions{
|
||||
Filename: filenameOrig,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
} else {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
func DeletePackageVersion(ctx *context.Context) {
|
||||
repository := ctx.PathParam("repository")
|
||||
architecture := ctx.PathParam("architecture")
|
||||
name := ctx.PathParam("name")
|
||||
version := ctx.PathParam("version")
|
||||
|
||||
release, err := arch_service.AquireRegistryLock(ctx, ctx.Package.Owner.ID)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer release()
|
||||
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeArch, name, version)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
} else {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
|
||||
VersionID: pv.ID,
|
||||
CompositeKey: fmt.Sprintf("%s|%s", repository, architecture),
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if len(pfs) != 1 {
|
||||
apiError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.Doer, pfs[0]); err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
} else {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := arch_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, repository, architecture); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
@ -20,6 +20,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
debian_module "code.gitea.io/gitea/modules/packages/debian"
|
||||
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -178,13 +179,13 @@ func ViewPackageVersion(ctx *context.Context) {
|
||||
ctx.Data["IsPackagesPage"] = true
|
||||
ctx.Data["PackageDescriptor"] = pd
|
||||
|
||||
switch pd.Package.Type {
|
||||
case packages_model.TypeContainer:
|
||||
registryAppURL, err := url.Parse(httplib.GuessCurrentAppURL(ctx))
|
||||
registryHostURL, err := url.Parse(httplib.GuessCurrentHostURL(ctx))
|
||||
if err != nil {
|
||||
registryAppURL, _ = url.Parse(setting.AppURL)
|
||||
registryHostURL, _ = url.Parse(setting.AppURL)
|
||||
}
|
||||
ctx.Data["RegistryHost"] = registryAppURL.Host
|
||||
ctx.Data["PackageRegistryHost"] = registryHostURL.Host
|
||||
|
||||
switch pd.Package.Type {
|
||||
case packages_model.TypeAlpine:
|
||||
branches := make(container.Set[string])
|
||||
repositories := make(container.Set[string])
|
||||
@ -204,6 +205,23 @@ func ViewPackageVersion(ctx *context.Context) {
|
||||
}
|
||||
|
||||
ctx.Data["Branches"] = util.Sorted(branches.Values())
|
||||
ctx.Data["Repositories"] = util.Sorted(repositories.Values())
|
||||
ctx.Data["Architectures"] = util.Sorted(architectures.Values())
|
||||
case packages_model.TypeArch:
|
||||
repositories := make(container.Set[string])
|
||||
architectures := make(container.Set[string])
|
||||
|
||||
for _, f := range pd.Files {
|
||||
for _, pp := range f.Properties {
|
||||
switch pp.Name {
|
||||
case arch_module.PropertyRepository:
|
||||
repositories.Add(pp.Value)
|
||||
case arch_module.PropertyArchitecture:
|
||||
architectures.Add(pp.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Repositories"] = util.Sorted(repositories.Values())
|
||||
ctx.Data["Architectures"] = util.Sorted(architectures.Values())
|
||||
case packages_model.TypeDebian:
|
||||
@ -249,7 +267,6 @@ func ViewPackageVersion(ctx *context.Context) {
|
||||
var (
|
||||
total int64
|
||||
pvs []*packages_model.PackageVersion
|
||||
err error
|
||||
)
|
||||
switch pd.Package.Type {
|
||||
case packages_model.TypeContainer:
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
type PackageCleanupRuleForm struct {
|
||||
ID int64
|
||||
Enabled bool
|
||||
Type string `binding:"Required;In(alpine,cargo,chef,composer,conan,conda,container,cran,debian,generic,go,helm,maven,npm,nuget,pub,pypi,rpm,rubygems,swift,vagrant)"`
|
||||
Type string `binding:"Required;In(alpine,arch,cargo,chef,composer,conan,conda,container,cran,debian,generic,go,helm,maven,npm,nuget,pub,pypi,rpm,rubygems,swift,vagrant)"`
|
||||
KeepCount int `binding:"In(0,1,5,10,25,50,100)"`
|
||||
KeepPattern string `binding:"RegexPattern"`
|
||||
RemoveDays int `binding:"In(0,7,14,30,60,90,180)"`
|
||||
|
@ -72,7 +72,7 @@ func GetOrCreateKeyPair(ctx context.Context, ownerID int64) (string, string, err
|
||||
return priv, pub, nil
|
||||
}
|
||||
|
||||
// BuildAllRepositoryFiles (re)builds all repository files for every available distributions, components and architectures
|
||||
// BuildAllRepositoryFiles (re)builds all repository files for every available branches, repositories and architectures
|
||||
func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
|
||||
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
|
||||
if err != nil {
|
||||
|
401
services/packages/arch/repository.go
Normal file
401
services/packages/arch/repository.go
Normal file
@ -0,0 +1,401 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
arch_model "code.gitea.io/gitea/models/packages/arch"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/globallock"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
||||
"github.com/keybase/go-crypto/openpgp"
|
||||
"github.com/keybase/go-crypto/openpgp/armor"
|
||||
"github.com/keybase/go-crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
const (
|
||||
IndexArchiveFilename = "packages.db"
|
||||
)
|
||||
|
||||
func AquireRegistryLock(ctx context.Context, ownerID int64) (globallock.ReleaseFunc, error) {
|
||||
return globallock.Lock(ctx, fmt.Sprintf("packages_arch_%d", ownerID))
|
||||
}
|
||||
|
||||
// GetOrCreateRepositoryVersion gets or creates the internal repository package
|
||||
// The Arch registry needs multiple index files which are stored in this package.
|
||||
func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) {
|
||||
return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeArch, arch_module.RepositoryPackage, arch_module.RepositoryVersion)
|
||||
}
|
||||
|
||||
// GetOrCreateKeyPair gets or creates the PGP keys used to sign repository files
|
||||
func GetOrCreateKeyPair(ctx context.Context, ownerID int64) (string, string, error) {
|
||||
priv, err := user_model.GetSetting(ctx, ownerID, arch_module.SettingKeyPrivate)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
pub, err := user_model.GetSetting(ctx, ownerID, arch_module.SettingKeyPublic)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if priv == "" || pub == "" {
|
||||
priv, pub, err = generateKeypair()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := user_model.SetUserSetting(ctx, ownerID, arch_module.SettingKeyPrivate, priv); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := user_model.SetUserSetting(ctx, ownerID, arch_module.SettingKeyPublic, pub); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
return priv, pub, nil
|
||||
}
|
||||
|
||||
func generateKeypair() (string, string, error) {
|
||||
e, err := openpgp.NewEntity("", "Arch Registry", "", nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var priv strings.Builder
|
||||
var pub strings.Builder
|
||||
|
||||
w, err := armor.Encode(&priv, openpgp.PrivateKeyType, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := e.SerializePrivate(w, nil); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
w.Close()
|
||||
|
||||
w, err = armor.Encode(&pub, openpgp.PublicKeyType, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := e.Serialize(w); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
w.Close()
|
||||
|
||||
return priv.String(), pub.String(), nil
|
||||
}
|
||||
|
||||
func SignData(ctx context.Context, ownerID int64, r io.Reader) ([]byte, error) {
|
||||
priv, _, err := GetOrCreateKeyPair(ctx, ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := armor.Decode(strings.NewReader(priv))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e, err := openpgp.ReadEntity(packet.NewReader(block.Body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if err := openpgp.DetachSign(buf, e, r, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// BuildAllRepositoryFiles (re)builds all repository files for every available repositories and architectures
|
||||
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
|
||||
repositories, err := arch_model.GetRepositories(ctx, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, repository := range repositories {
|
||||
architectures, err := arch_model.GetArchitectures(ctx, ownerID, repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, architecture := range architectures {
|
||||
if err := buildPackagesIndex(ctx, ownerID, pv, repository, architecture); err != nil {
|
||||
return fmt.Errorf("failed to build repository files [%s/%s]: %w", repository, architecture, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildSpecificRepositoryFiles builds index files for the repository
|
||||
func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, repository, architecture string) error {
|
||||
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
architectures := container.SetOf(architecture)
|
||||
if architecture == arch_module.AnyArch {
|
||||
// Update all other architectures too when updating the any index
|
||||
additionalArchitectures, err := arch_model.GetArchitectures(ctx, ownerID, repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
architectures.AddMultiple(additionalArchitectures...)
|
||||
}
|
||||
|
||||
for architecture := range architectures {
|
||||
if err := buildPackagesIndex(ctx, ownerID, pv, repository, architecture); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func searchPackageFiles(ctx context.Context, ownerID int64, repository, architecture string) ([]*packages_model.PackageFile, error) {
|
||||
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
PackageType: packages_model.TypeArch,
|
||||
Query: "%.pkg.tar.%",
|
||||
Properties: map[string]string{
|
||||
arch_module.PropertyRepository: repository,
|
||||
arch_module.PropertyArchitecture: architecture,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pfs, nil
|
||||
}
|
||||
|
||||
func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, repository, architecture string) error {
|
||||
pfs, err := searchPackageFiles(ctx, ownerID, repository, architecture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if architecture != arch_module.AnyArch {
|
||||
// Add all any packages too
|
||||
anyarchFiles, err := searchPackageFiles(ctx, ownerID, repository, arch_module.AnyArch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pfs = append(pfs, anyarchFiles...)
|
||||
}
|
||||
|
||||
// Delete the package indices if there are no packages
|
||||
if len(pfs) == 0 {
|
||||
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexArchiveFilename, fmt.Sprintf("%s|%s", repository, architecture))
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
} else if pf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return packages_service.DeletePackageFile(ctx, pf)
|
||||
}
|
||||
|
||||
indexContent, _ := packages_module.NewHashedBuffer()
|
||||
defer indexContent.Close()
|
||||
|
||||
gw := gzip.NewWriter(indexContent)
|
||||
tw := tar.NewWriter(gw)
|
||||
|
||||
cache := make(map[int64]*packages_model.Package)
|
||||
|
||||
for _, pf := range pfs {
|
||||
opts := &entryOptions{
|
||||
File: pf,
|
||||
}
|
||||
|
||||
opts.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(opts.Version.MetadataJSON), &opts.VersionMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Package = cache[opts.Version.PackageID]
|
||||
if opts.Package == nil {
|
||||
opts.Package, err = packages_model.GetPackageByID(ctx, opts.Version.PackageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache[opts.Package.ID] = opts.Package
|
||||
}
|
||||
opts.Blob, err = packages_model.GetBlobByID(ctx, pf.BlobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertySignature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(sig) == 0 {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
opts.Signature = sig[0].Value
|
||||
|
||||
meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(meta) == 0 {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
if err := json.Unmarshal([]byte(meta[0].Value), &opts.FileMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeFiles(tw, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeDescription(tw, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tw.Close()
|
||||
gw.Close()
|
||||
|
||||
signature, err := SignData(ctx, ownerID, indexContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := indexContent.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = packages_service.AddFileToPackageVersionInternal(
|
||||
ctx,
|
||||
repoVersion,
|
||||
&packages_service.PackageFileCreationInfo{
|
||||
PackageFileInfo: packages_service.PackageFileInfo{
|
||||
Filename: IndexArchiveFilename,
|
||||
CompositeKey: fmt.Sprintf("%s|%s", repository, architecture),
|
||||
},
|
||||
Creator: user_model.NewGhostUser(),
|
||||
Data: indexContent,
|
||||
IsLead: false,
|
||||
OverwriteExisting: true,
|
||||
Properties: map[string]string{
|
||||
arch_module.PropertyRepository: repository,
|
||||
arch_module.PropertyArchitecture: architecture,
|
||||
arch_module.PropertySignature: base64.StdEncoding.EncodeToString(signature),
|
||||
},
|
||||
},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
type entryOptions struct {
|
||||
Package *packages_model.Package
|
||||
Version *packages_model.PackageVersion
|
||||
VersionMetadata *arch_module.VersionMetadata
|
||||
File *packages_model.PackageFile
|
||||
FileMetadata *arch_module.FileMetadata
|
||||
Blob *packages_model.PackageBlob
|
||||
Signature string
|
||||
}
|
||||
|
||||
type keyValue struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func writeFiles(tw *tar.Writer, opts *entryOptions) error {
|
||||
return writeFields(tw, fmt.Sprintf("%s-%s/files", opts.Package.Name, opts.Version.Version), []keyValue{
|
||||
{"FILES", strings.Join(opts.FileMetadata.Files, "\n")},
|
||||
})
|
||||
}
|
||||
|
||||
// https://gitlab.archlinux.org/pacman/pacman/-/blob/master/lib/libalpm/be_sync.c#L562
|
||||
func writeDescription(tw *tar.Writer, opts *entryOptions) error {
|
||||
return writeFields(tw, fmt.Sprintf("%s-%s/desc", opts.Package.Name, opts.Version.Version), []keyValue{
|
||||
{"FILENAME", opts.File.Name},
|
||||
{"MD5SUM", opts.Blob.HashMD5},
|
||||
{"SHA256SUM", opts.Blob.HashSHA256},
|
||||
{"PGPSIG", opts.Signature},
|
||||
{"CSIZE", fmt.Sprintf("%d", opts.Blob.Size)},
|
||||
{"ISIZE", fmt.Sprintf("%d", opts.FileMetadata.InstalledSize)},
|
||||
{"NAME", opts.Package.Name},
|
||||
{"BASE", opts.FileMetadata.Base},
|
||||
{"ARCH", opts.FileMetadata.Architecture},
|
||||
{"VERSION", opts.Version.Version},
|
||||
{"DESC", opts.VersionMetadata.Description},
|
||||
{"URL", opts.VersionMetadata.ProjectURL},
|
||||
{"LICENSE", strings.Join(opts.VersionMetadata.Licenses, "\n")},
|
||||
{"GROUPS", strings.Join(opts.FileMetadata.Groups, "\n")},
|
||||
{"BUILDDATE", fmt.Sprintf("%d", opts.FileMetadata.BuildDate)},
|
||||
{"PACKAGER", opts.FileMetadata.Packager},
|
||||
{"PROVIDES", strings.Join(opts.FileMetadata.Provides, "\n")},
|
||||
{"DEPENDS", strings.Join(opts.FileMetadata.Depends, "\n")},
|
||||
{"OPTDEPENDS", strings.Join(opts.FileMetadata.OptDepends, "\n")},
|
||||
{"MAKEDEPENDS", strings.Join(opts.FileMetadata.MakeDepends, "\n")},
|
||||
{"CHECKDEPENDS", strings.Join(opts.FileMetadata.CheckDepends, "\n")},
|
||||
{"XDATA", strings.Join(opts.FileMetadata.XData, "\n")},
|
||||
})
|
||||
}
|
||||
|
||||
func writeFields(tw *tar.Writer, filename string, fields []keyValue) error {
|
||||
buf := &bytes.Buffer{}
|
||||
for _, kv := range fields {
|
||||
if kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(buf, "%%%s%%\n%s\n\n", kv.Key, kv.Value)
|
||||
}
|
||||
|
||||
if err := tw.WriteHeader(&tar.Header{
|
||||
Name: filename,
|
||||
Size: int64(buf.Len()),
|
||||
Mode: int64(os.ModePerm),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := io.Copy(tw, buf)
|
||||
return err
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
alpine_service "code.gitea.io/gitea/services/packages/alpine"
|
||||
arch_service "code.gitea.io/gitea/services/packages/arch"
|
||||
cargo_service "code.gitea.io/gitea/services/packages/cargo"
|
||||
container_service "code.gitea.io/gitea/services/packages/container"
|
||||
debian_service "code.gitea.io/gitea/services/packages/debian"
|
||||
@ -120,18 +121,29 @@ func ExecuteCleanupRules(outerCtx context.Context) error {
|
||||
}
|
||||
|
||||
if anyVersionDeleted {
|
||||
if pcr.Type == packages_model.TypeDebian {
|
||||
switch pcr.Type {
|
||||
case packages_model.TypeDebian:
|
||||
if err := debian_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
|
||||
return fmt.Errorf("CleanupRule [%d]: debian.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
|
||||
}
|
||||
} else if pcr.Type == packages_model.TypeAlpine {
|
||||
case packages_model.TypeAlpine:
|
||||
if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
|
||||
return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
|
||||
}
|
||||
} else if pcr.Type == packages_model.TypeRpm {
|
||||
case 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)
|
||||
}
|
||||
case packages_model.TypeArch:
|
||||
release, err := arch_service.AquireRegistryLock(ctx, pcr.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
if err := arch_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
|
||||
return fmt.Errorf("CleanupRule [%d]: arch.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -355,6 +355,8 @@ func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p
|
||||
switch packageType {
|
||||
case packages_model.TypeAlpine:
|
||||
typeSpecificSize = setting.Packages.LimitSizeAlpine
|
||||
case packages_model.TypeArch:
|
||||
typeSpecificSize = setting.Packages.LimitSizeArch
|
||||
case packages_model.TypeCargo:
|
||||
typeSpecificSize = setting.Packages.LimitSizeCargo
|
||||
case packages_model.TypeChef:
|
||||
|
41
templates/package/content/arch.tmpl
Normal file
41
templates/package/content/arch.tmpl
Normal file
@ -0,0 +1,41 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "arch"}}
|
||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.installation"}}</h4>
|
||||
<div class="ui attached segment">
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-gear"}} {{ctx.Locale.Tr "packages.arch.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>[{{.PackageDescriptor.Owner.LowerName}}.{{.PackageRegistryHost}}]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/arch/$repo/$arch"></origin-url></code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-sync"}} {{ctx.Locale.Tr "packages.arch.install"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>pacman -Sy {{.PackageDescriptor.Package.LowerName}}</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "packages.registry.documentation" "Arch" "https://docs.gitea.com/usage/packages/arch/"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.arch.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.arch.repository.repositories"}}</h5></td>
|
||||
<td>{{StringUtils.Join .Repositories ", "}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="collapsing"><h5>{{ctx.Locale.Tr "packages.arch.repository.architectures"}}</h5></td>
|
||||
<td>{{StringUtils.Join .Architectures ", "}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{if .PackageDescriptor.Metadata.Description}}
|
||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
||||
<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>
|
||||
{{end}}
|
||||
{{end}}
|
@ -5,13 +5,13 @@
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.container.pull"}}</label>
|
||||
{{if eq .PackageDescriptor.Metadata.Type "helm"}}
|
||||
<div class="markup"><pre class="code-block"><code>helm pull oci://{{.RegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}} --version {{.PackageDescriptor.Version.LowerVersion}}</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>helm pull oci://{{.PackageRegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}} --version {{.PackageDescriptor.Version.LowerVersion}}</code></pre></div>
|
||||
{{else}}
|
||||
{{$separator := ":"}}
|
||||
{{if not .PackageDescriptor.Metadata.IsTagged}}
|
||||
{{$separator = "@"}}
|
||||
{{end}}
|
||||
<div class="markup"><pre class="code-block"><code>docker pull {{.RegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}}{{$separator}}{{.PackageDescriptor.Version.LowerVersion}}</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>docker pull {{.PackageRegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}}{{$separator}}{{.PackageDescriptor.Version.LowerVersion}}</code></pre></div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="field">
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "alpine"}}
|
||||
{{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{end}}
|
||||
|
4
templates/package/metadata/arch.tmpl
Normal file
4
templates/package/metadata/arch.tmpl
Normal file
@ -0,0 +1,4 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "arch"}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{end}}
|
@ -1,7 +1,7 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "cargo"}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "chef"}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "composer"}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.Name}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Homepage}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.Homepage}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.Name}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Homepage}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.Homepage}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "conan"}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "conda"}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "container"}}
|
||||
<div class="item" title="{{ctx.Locale.Tr "packages.container.details.type"}}">{{svg "octicon-package" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Type.Name}}</div>
|
||||
{{if .PackageDescriptor.Metadata.Platform}}<div class="item" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">{{svg "octicon-cpu" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Platform}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Licenses}}<div class="item">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Licenses}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
<div class="item" title="{{ctx.Locale.Tr "packages.container.details.type"}}">{{svg "octicon-package"}} {{.PackageDescriptor.Metadata.Type.Name}}</div>
|
||||
{{if .PackageDescriptor.Metadata.Platform}}<div class="item" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">{{svg "octicon-cpu"}} {{.PackageDescriptor.Metadata.Platform}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Licenses}}<div class="item">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.Licenses}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "cran"}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "debian"}}
|
||||
{{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "helm"}}
|
||||
{{range .PackageDescriptor.Metadata.Maintainers}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.Name}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Home}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.Home}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Maintainers}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.Name}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Home}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.Home}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,8 +1,8 @@
|
||||
{{if and (eq .PackageDescriptor.Package.Type "maven") (not .PackageDescriptor.Metadata)}}
|
||||
<div class="item">{{svg "octicon-note" 16 "tw-mr-2"}} {{ctx.Locale.Tr "packages.no_metadata"}}</div>
|
||||
<div class="item">{{svg "octicon-note"}} {{ctx.Locale.Tr "packages.no_metadata"}}</div>
|
||||
{{end}}
|
||||
{{if and (eq .PackageDescriptor.Package.Type "maven") .PackageDescriptor.Metadata}}
|
||||
{{if .PackageDescriptor.Metadata.Name}}<div class="item">{{svg "octicon-note" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Name}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Name}}<div class="item">{{svg "octicon-note"}} {{.PackageDescriptor.Metadata.Name}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,8 +1,8 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "npm"}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{range .PackageDescriptor.VersionProperties}}
|
||||
{{if eq .Name "npm.tag"}}<div class="item" title="{{ctx.Locale.Tr "packages.npm.details.tag"}}">{{svg "octicon-versions" 16 "tw-mr-2"}} {{.Value}}</div>{{end}}
|
||||
{{if eq .Name "npm.tag"}}<div class="item" title="{{ctx.Locale.Tr "packages.npm.details.tag"}}">{{svg "octicon-versions"}} {{.Value}}</div>{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "nuget"}}
|
||||
{{if .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Authors}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Authors}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "pub"}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "pypi"}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "rpm"}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "rubygems"}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div> {{end}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div> {{end}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.}}</div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "swift"}}
|
||||
{{if .PackageDescriptor.Metadata.Author.String}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Author.String}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "vagrant"}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -1,12 +1,10 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content repository view issue packages">
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content repository packages">
|
||||
{{template "shared/user/org_profile_avatar" .}}
|
||||
<div class="ui container">
|
||||
{{template "user/overview/header" .}}
|
||||
<div class="issue-title-header">
|
||||
<div class="issue-title">
|
||||
<h1>{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</h1>
|
||||
</div>
|
||||
<div>
|
||||
{{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}}
|
||||
{{if .HasRepositoryAccess}}
|
||||
@ -19,6 +17,7 @@
|
||||
<div class="issue-content">
|
||||
<div class="issue-content-left">
|
||||
{{template "package/content/alpine" .}}
|
||||
{{template "package/content/arch" .}}
|
||||
{{template "package/content/cargo" .}}
|
||||
{{template "package/content/chef" .}}
|
||||
{{template "package/content/composer" .}}
|
||||
@ -42,14 +41,15 @@
|
||||
</div>
|
||||
<div class="issue-content-right ui segment">
|
||||
<strong>{{ctx.Locale.Tr "packages.details"}}</strong>
|
||||
<div class="ui relaxed list">
|
||||
<div class="item">{{svg .PackageDescriptor.Package.Type.SVGName 16 "tw-mr-2"}} {{.PackageDescriptor.Package.Type.Name}}</div>
|
||||
<div class="ui relaxed list flex-items-block">
|
||||
<div class="item">{{svg .PackageDescriptor.Package.Type.SVGName}} {{.PackageDescriptor.Package.Type.Name}}</div>
|
||||
{{if .HasRepositoryAccess}}
|
||||
<div class="item">{{svg "octicon-repo" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Repository.Link}}">{{.PackageDescriptor.Repository.FullName}}</a></div>
|
||||
<div class="item">{{svg "octicon-repo"}} <a href="{{.PackageDescriptor.Repository.Link}}">{{.PackageDescriptor.Repository.FullName}}</a></div>
|
||||
{{end}}
|
||||
<div class="item">{{svg "octicon-calendar" 16 "tw-mr-2"}} {{DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}}</div>
|
||||
<div class="item">{{svg "octicon-download" 16 "tw-mr-2"}} {{.PackageDescriptor.Version.DownloadCount}}</div>
|
||||
<div class="item">{{svg "octicon-calendar"}} {{DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}}</div>
|
||||
<div class="item">{{svg "octicon-download"}} {{.PackageDescriptor.Version.DownloadCount}}</div>
|
||||
{{template "package/metadata/alpine" .}}
|
||||
{{template "package/metadata/arch" .}}
|
||||
{{template "package/metadata/cargo" .}}
|
||||
{{template "package/metadata/chef" .}}
|
||||
{{template "package/metadata/composer" .}}
|
||||
@ -70,7 +70,7 @@
|
||||
{{template "package/metadata/swift" .}}
|
||||
{{template "package/metadata/vagrant" .}}
|
||||
{{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}}
|
||||
<div class="item">{{svg "octicon-database" 16 "tw-mr-2"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div>
|
||||
<div class="item">{{svg "octicon-database"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if not (eq .PackageDescriptor.Package.Type "container")}}
|
||||
@ -98,12 +98,12 @@
|
||||
</div>
|
||||
{{if or .CanWritePackages .HasRepositoryAccess}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui relaxed list">
|
||||
<div class="ui relaxed list flex-items-block">
|
||||
{{if .HasRepositoryAccess}}
|
||||
<div class="item">{{svg "octicon-issue-opened" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
|
||||
<div class="item">{{svg "octicon-issue-opened"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
|
||||
{{end}}
|
||||
{{if .CanWritePackages}}
|
||||
<div class="item">{{svg "octicon-tools" 16 "tw-mr-2"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
|
||||
<div class="item">{{svg "octicon-tools"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
302
tests/integration/api_packages_arch_test.go
Normal file
302
tests/integration/api_packages_arch_test.go
Normal file
@ -0,0 +1,302 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
arch_service "code.gitea.io/gitea/services/packages/arch"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
func TestPackageArch(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
packageName := "gitea-test"
|
||||
packageVersion := "1.4.1-r3"
|
||||
|
||||
createPackage := func(compression, name, version, architecture string) []byte {
|
||||
var buf bytes.Buffer
|
||||
var cw io.WriteCloser
|
||||
switch compression {
|
||||
case "zst":
|
||||
cw, _ = zstd.NewWriter(&buf)
|
||||
case "xz":
|
||||
cw, _ = xz.NewWriter(&buf)
|
||||
case "gz":
|
||||
cw = gzip.NewWriter(&buf)
|
||||
}
|
||||
tw := tar.NewWriter(cw)
|
||||
|
||||
info := []byte(`pkgname = ` + name + `
|
||||
pkgbase = ` + name + `
|
||||
pkgver = ` + version + `
|
||||
pkgdesc = Description
|
||||
# comment
|
||||
builddate = 1678834800
|
||||
size = 8
|
||||
arch = ` + architecture + `
|
||||
license = MIT`)
|
||||
|
||||
hdr := &tar.Header{
|
||||
Name: ".PKGINFO",
|
||||
Mode: 0o600,
|
||||
Size: int64(len(info)),
|
||||
}
|
||||
tw.WriteHeader(hdr)
|
||||
tw.Write(info)
|
||||
|
||||
for _, file := range []string{"etc/dummy", "opt/file/bin"} {
|
||||
hdr := &tar.Header{
|
||||
Name: file,
|
||||
Mode: 0o600,
|
||||
Size: 4,
|
||||
}
|
||||
tw.WriteHeader(hdr)
|
||||
tw.Write([]byte("test"))
|
||||
}
|
||||
|
||||
tw.Close()
|
||||
cw.Close()
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
compressions := []string{"gz", "xz", "zst"}
|
||||
repositories := []string{"main", "testing", "with/slash", ""}
|
||||
|
||||
rootURL := fmt.Sprintf("/api/packages/%s/arch", user.Name)
|
||||
|
||||
t.Run("RepositoryKey", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", rootURL+"/repository.key")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
|
||||
assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
||||
})
|
||||
|
||||
contentAarch64Gz := createPackage("gz", packageName, packageVersion, "aarch64")
|
||||
for _, compression := range compressions {
|
||||
contentAarch64 := createPackage(compression, packageName, packageVersion, "aarch64")
|
||||
contentAny := createPackage(compression, packageName+"_"+arch_module.AnyArch, packageVersion, arch_module.AnyArch)
|
||||
|
||||
for _, repository := range repositories {
|
||||
t.Run(fmt.Sprintf("[%s,%s]", repository, compression), func(t *testing.T) {
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
uploadURL := fmt.Sprintf("%s/%s", rootURL, repository)
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeArch)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pvs, 1)
|
||||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, pd.SemVer)
|
||||
assert.IsType(t, &arch_module.VersionMetadata{}, pd.Metadata)
|
||||
assert.Equal(t, packageName, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||
|
||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, pfs)
|
||||
assert.Condition(t, func() bool {
|
||||
seen := false
|
||||
expectedFilename := fmt.Sprintf("%s-%s-aarch64.pkg.tar.%s", packageName, packageVersion, compression)
|
||||
expectedCompositeKey := fmt.Sprintf("%s|aarch64", repository)
|
||||
for _, pf := range pfs {
|
||||
if pf.Name == expectedFilename && pf.CompositeKey == expectedCompositeKey {
|
||||
if seen {
|
||||
return false
|
||||
}
|
||||
seen = true
|
||||
|
||||
assert.True(t, pf.IsLead)
|
||||
|
||||
pfps, err := packages.GetProperties(db.DefaultContext, packages.PropertyTypeFile, pf.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, pfp := range pfps {
|
||||
switch pfp.Name {
|
||||
case arch_module.PropertyRepository:
|
||||
assert.Equal(t, repository, pfp.Value)
|
||||
case arch_module.PropertyArchitecture:
|
||||
assert.Equal(t, "aarch64", pfp.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return seen
|
||||
})
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
|
||||
// Add same package with different compression leads to conflict
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64Gz)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
|
||||
readIndexContent := func(r io.Reader) (map[string]string, error) {
|
||||
gzr, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := make(map[string]string)
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
for {
|
||||
hd, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content[hd.Name] = string(buf)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
t.Run("Index", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
content, err := readIndexContent(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
|
||||
assert.True(t, has)
|
||||
assert.Contains(t, desc, "%FILENAME%\n"+fmt.Sprintf("%s-%s-aarch64.pkg.tar.%s", packageName, packageVersion, compression)+"\n\n")
|
||||
assert.Contains(t, desc, "%NAME%\n"+packageName+"\n\n")
|
||||
assert.Contains(t, desc, "%VERSION%\n"+packageVersion+"\n\n")
|
||||
assert.Contains(t, desc, "%ARCH%\naarch64\n")
|
||||
assert.NotContains(t, desc, "%ARCH%\n"+arch_module.AnyArch+"\n")
|
||||
assert.Contains(t, desc, "%LICENSE%\nMIT\n")
|
||||
|
||||
files, has := content[fmt.Sprintf("%s-%s/files", packageName, packageVersion)]
|
||||
assert.True(t, has)
|
||||
assert.Contains(t, files, "%FILES%\netc/dummy\nopt/file/bin\n\n")
|
||||
|
||||
for _, indexFile := range []string{
|
||||
arch_service.IndexArchiveFilename,
|
||||
arch_service.IndexArchiveFilename + ".tar.gz",
|
||||
"index.db",
|
||||
"index.db.tar.gz",
|
||||
"index.files",
|
||||
"index.files.tar.gz",
|
||||
} {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, indexFile))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s.sig", rootURL, repository, indexFile))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Download", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s-%s-aarch64.pkg.tar.%s", rootURL, repository, packageName, packageVersion, compression))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s-%s-aarch64.pkg.tar.%s.sig", rootURL, repository, packageName, packageVersion, compression))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("Any", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s", rootURL, repository), bytes.NewReader(contentAny)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
content, err := readIndexContent(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
|
||||
assert.True(t, has)
|
||||
assert.Contains(t, desc, "%NAME%\n"+packageName+"\n\n")
|
||||
assert.Contains(t, desc, "%ARCH%\naarch64\n")
|
||||
|
||||
desc, has = content[fmt.Sprintf("%s-%s/desc", packageName+"_"+arch_module.AnyArch, packageVersion)]
|
||||
assert.True(t, has)
|
||||
assert.Contains(t, desc, "%NAME%\n"+packageName+"_any\n\n")
|
||||
assert.Contains(t, desc, "%ARCH%\n"+arch_module.AnyArch+"\n")
|
||||
|
||||
// "any" architecture package should be available with every architecture requested
|
||||
for _, arch := range []string{arch_module.AnyArch, "aarch64", "myarch"} {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s-%s-any.pkg.tar.%s", rootURL, repository, arch, packageName+"_"+arch_module.AnyArch, packageVersion, compression))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/any", rootURL, repository, packageName+"_"+arch_module.AnyArch, packageVersion)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/aarch64", rootURL, repository, packageName, packageVersion))
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/aarch64", rootURL, repository, packageName, packageVersion)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Deleting the last file of an architecture should remove that index
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1386,7 +1386,7 @@ table th[data-sortt-desc] .svg {
|
||||
.flex-text-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
gap: .5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
1
web_src/svg/gitea-arch.svg
Normal file
1
web_src/svg/gitea-arch.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#1793d1" d="M256 72c-14 35-23 57-39 91 10 11 22 23 41 36-21-8-35-17-45-26-21 43-53 103-117 220 50-30 90-48 127-55-2-7-3-14-3-22v-1c1-33 18-58 38-56 20 1 36 29 35 62l-2 17c36 7 75 26 125 54l-27-50c-13-10-27-23-55-38 19 5 33 11 44 17-86-159-93-180-122-250z"/></svg>
|
After Width: | Height: | Size: 337 B |
Loading…
Reference in New Issue
Block a user