Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Arch package registry #32692

Merged
merged 15 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions models/packages/arch/search.go
Original file line number Diff line number Diff line change
@@ -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,
},
)
}
3 changes: 3 additions & 0 deletions models/packages/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions models/packages/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -55,6 +56,7 @@ const (

var TypeList = []Type{
TypeAlpine,
TypeArch,
TypeCargo,
TypeChef,
TypeComposer,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions models/packages/package_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
249 changes: 249 additions & 0 deletions modules/packages/arch/metadata.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading