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

Arch packages implementation #31037

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fcb84f1
fix anything ...
ExplodingDragon Sep 19, 2024
c98a705
fix anything v2 ...
ExplodingDragon Sep 19, 2024
97aefc5
fix anything v3 ...
ExplodingDragon Sep 19, 2024
2667260
fix anything v4 ...
ExplodingDragon Sep 19, 2024
317ea7f
fix defer error
ExplodingDragon Sep 20, 2024
fbe09b2
fix cache download
ExplodingDragon Sep 20, 2024
0328200
fix some bugs
ExplodingDragon Sep 20, 2024
7b53f05
fix ui bugs
ExplodingDragon Sep 20, 2024
6978762
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 20, 2024
c71fd97
fix url typo
ExplodingDragon Sep 23, 2024
d7373b5
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 23, 2024
5c2625a
fix typo and errors
ExplodingDragon Sep 24, 2024
b37e591
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 24, 2024
bc72567
add locker
ExplodingDragon Sep 24, 2024
3da98af
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 24, 2024
f4092d6
fix tests
ExplodingDragon Sep 24, 2024
90f3b30
Merge branch 'pacman-packages' of github.com:ExplodingDragon/gitea in…
ExplodingDragon Sep 24, 2024
3caa520
add comments.
ExplodingDragon Sep 24, 2024
c35f354
add Concurrent tests.
ExplodingDragon Sep 24, 2024
1b0a4e2
fix errors.
ExplodingDragon Sep 24, 2024
1b256e4
fix errors.
ExplodingDragon Sep 24, 2024
5aeccde
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 24, 2024
d80ca1f
remove xdata check
ExplodingDragon Sep 24, 2024
3c71f34
add more version checks
ExplodingDragon Sep 24, 2024
b3db785
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 25, 2024
568f7a6
clean style
ExplodingDragon Sep 25, 2024
0b95ef2
fix bugs
ExplodingDragon Sep 26, 2024
07c2b6d
fix typo
ExplodingDragon Sep 26, 2024
8b63213
Merge branch 'main' into pacman-packages
ExplodingDragon Sep 26, 2024
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
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
342 changes: 342 additions & 0 deletions modules/packages/arch/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package arch

import (
"bufio"
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"

"code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"

"github.com/mholt/archiver/v3"
)

// Arch Linux Packages
// https://man.archlinux.org/man/PKGBUILD.5

const (
PropertyDescription = "arch.description"
PropertyArch = "arch.architecture"
PropertyDistribution = "arch.distribution"

SettingKeyPrivate = "arch.key.private"
SettingKeyPublic = "arch.key.public"

RepositoryPackage = "_arch"
RepositoryVersion = "_repository"
)

var (
reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`)
reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`)
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`)

magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD}
magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}
magicGZ = []byte{0x1F, 0x8B}
)

type Package struct {
Name string `json:"name"`
Version string `json:"version"` // Includes version, release and epoch
CompressType string `json:"compress_type"`
VersionMetadata VersionMetadata
FileMetadata FileMetadata
}

// Arch package metadata related to specific version.
// Version metadata the same across different architectures and distributions.
type VersionMetadata struct {
Base string `json:"base"`
Description string `json:"description"`
ProjectURL string `json:"project_url"`
Groups []string `json:"groups,omitempty"`
Provides []string `json:"provides,omitempty"`
License []string `json:"license,omitempty"`
Depends []string `json:"depends,omitempty"`
OptDepends []string `json:"opt_depends,omitempty"`
MakeDepends []string `json:"make_depends,omitempty"`
CheckDepends []string `json:"check_depends,omitempty"`
Conflicts []string `json:"conflicts,omitempty"`
Replaces []string `json:"replaces,omitempty"`
Backup []string `json:"backup,omitempty"`
XData []string `json:"xdata,omitempty"`
}

// FileMetadata Metadata related to specific package file.
// This metadata might vary for different architecture and distribution.
type FileMetadata struct {
CompressedSize int64 `json:"compressed_size"`
InstalledSize int64 `json:"installed_size"`
MD5 string `json:"md5"`
SHA256 string `json:"sha256"`
BuildDate int64 `json:"build_date"`
Packager string `json:"packager"`
Arch string `json:"arch"`
PgpSigned string `json:"pgp"`
}

// ParsePackage Function that receives arch package archive data and returns it's metadata.
func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
md5, _, sha256, _ := r.Sums()
_, err := r.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
header := make([]byte, 5)
yp05327 marked this conversation as resolved.
Show resolved Hide resolved
_, err = r.Read(header)
if err != nil {
return nil, err
}
_, err = r.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}

var tarball archiver.Reader
var tarballType string
if bytes.Equal(header[:len(magicZSTD)], magicZSTD) {
tarballType = "zst"
tarball = archiver.NewTarZstd()
} else if bytes.Equal(header[:len(magicXZ)], magicXZ) {
tarballType = "xz"
tarball = archiver.NewTarXz()
} else if bytes.Equal(header[:len(magicGZ)], magicGZ) {
tarballType = "gz"
tarball = archiver.NewTarGz()
} else {
return nil, errors.New("not supported compression")
}
err = tarball.Open(r, 0)
if err != nil {
return nil, err
}
defer tarball.Close()

var pkg *Package
var mtree bool

for {
f, err := tarball.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

switch f.Name() {
case ".PKGINFO":
pkg, err = ParsePackageInfo(tarballType, f)
if err != nil {
_ = f.Close()
return nil, err
ExplodingDragon marked this conversation as resolved.
Show resolved Hide resolved
}
case ".MTREE":
mtree = true
}
_ = f.Close()
}

if pkg == nil {
return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found")
}

if !mtree {
return nil, util.NewInvalidArgumentErrorf(".MTREE file not found")
}

pkg.FileMetadata.CompressedSize = r.Size()
pkg.FileMetadata.MD5 = hex.EncodeToString(md5)
pkg.FileMetadata.SHA256 = hex.EncodeToString(sha256)

return pkg, nil
}

// ParsePackageInfo Function that accepts reader for .PKGINFO file from package archive,
// validates all field according to PKGBUILD spec and returns package.
func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) {
p := &Package{
CompressType: compressType,
}

scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()

if strings.HasPrefix(line, "#") {
continue
}

key, value, find := strings.Cut(line, "=")
lunny marked this conversation as resolved.
Show resolved Hide resolved
if !find {
continue
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
switch key {
case "pkgname":
p.Name = value
case "pkgbase":
p.VersionMetadata.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.Arch = value
case "provides":
p.VersionMetadata.Provides = append(p.VersionMetadata.Provides, value)
case "license":
p.VersionMetadata.License = append(p.VersionMetadata.License, value)
case "depend":
p.VersionMetadata.Depends = append(p.VersionMetadata.Depends, value)
case "optdepend":
p.VersionMetadata.OptDepends = append(p.VersionMetadata.OptDepends, value)
case "makedepend":
p.VersionMetadata.MakeDepends = append(p.VersionMetadata.MakeDepends, value)
case "checkdepend":
p.VersionMetadata.CheckDepends = append(p.VersionMetadata.CheckDepends, value)
case "backup":
p.VersionMetadata.Backup = append(p.VersionMetadata.Backup, value)
case "group":
p.VersionMetadata.Groups = append(p.VersionMetadata.Groups, value)
case "conflict":
p.VersionMetadata.Conflicts = append(p.VersionMetadata.Conflicts, value)
case "replaces":
p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value)
case "xdata":
p.VersionMetadata.XData = append(p.VersionMetadata.XData, value)
case "builddate":
bd, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil, err
}
p.FileMetadata.BuildDate = bd
case "size":
is, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil, err
}
p.FileMetadata.InstalledSize = is
default:
return nil, util.NewInvalidArgumentErrorf("property is not supported %s", key)
}
}

return p, errors.Join(scanner.Err(), ValidatePackageSpec(p))
}

// ValidatePackageSpec Arch package validation according to PKGBUILD specification.
func ValidatePackageSpec(p *Package) error {
if !reName.MatchString(p.Name) {
return util.NewInvalidArgumentErrorf("invalid package name")
}
if !reName.MatchString(p.VersionMetadata.Base) {
return util.NewInvalidArgumentErrorf("invalid package base")
}
if !reVer.MatchString(p.Version) {
return util.NewInvalidArgumentErrorf("invalid package version")
}
if p.FileMetadata.Arch == "" {
return util.NewInvalidArgumentErrorf("architecture should be specified")
}
if p.VersionMetadata.ProjectURL != "" {
if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
return util.NewInvalidArgumentErrorf("invalid project URL")
}
}
for _, checkDepend := range p.VersionMetadata.CheckDepends {
if !rePkgVer.MatchString(checkDepend) {
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend)
}
}
for _, depend := range p.VersionMetadata.Depends {
if !rePkgVer.MatchString(depend) {
return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend)
}
}
for _, makeDepend := range p.VersionMetadata.MakeDepends {
if !rePkgVer.MatchString(makeDepend) {
return util.NewInvalidArgumentErrorf("invalid make dependency: %s ", makeDepend)
}
}
for _, provide := range p.VersionMetadata.Provides {
if !rePkgVer.MatchString(provide) {
return util.NewInvalidArgumentErrorf("invalid provides: %s", provide)
}
}
for _, conflict := range p.VersionMetadata.Conflicts {
if !rePkgVer.MatchString(conflict) {
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict)
}
}
for _, replace := range p.VersionMetadata.Replaces {
if !rePkgVer.MatchString(replace) {
return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace)
}
}
for _, optDepend := range p.VersionMetadata.OptDepends {
if !reOptDep.MatchString(optDepend) {
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend)
}
}
for _, backup := range p.VersionMetadata.Backup {
if strings.HasPrefix(backup, "/") {
return util.NewInvalidArgumentErrorf("backup file contains leading forward slash")
}
}
return nil
}

// Desc Create pacman package description file.
func (p *Package) Desc() string {
entries := []string{
"FILENAME", fmt.Sprintf("%s-%s-%s.pkg.tar.%s", p.Name, p.Version, p.FileMetadata.Arch, p.CompressType),
"NAME", p.Name,
"BASE", p.VersionMetadata.Base,
"VERSION", p.Version,
"DESC", p.VersionMetadata.Description,
"GROUPS", strings.Join(p.VersionMetadata.Groups, "\n"),
"CSIZE", fmt.Sprintf("%d", p.FileMetadata.CompressedSize),
"ISIZE", fmt.Sprintf("%d", p.FileMetadata.InstalledSize),
"MD5SUM", p.FileMetadata.MD5,
"SHA256SUM", p.FileMetadata.SHA256,
"PGPSIG", p.FileMetadata.PgpSigned,
"URL", p.VersionMetadata.ProjectURL,
"LICENSE", strings.Join(p.VersionMetadata.License, "\n"),
"ARCH", p.FileMetadata.Arch,
"BUILDDATE", fmt.Sprintf("%d", p.FileMetadata.BuildDate),
"PACKAGER", p.FileMetadata.Packager,
"REPLACES", strings.Join(p.VersionMetadata.Replaces, "\n"),
"CONFLICTS", strings.Join(p.VersionMetadata.Conflicts, "\n"),
"PROVIDES", strings.Join(p.VersionMetadata.Provides, "\n"),
"DEPENDS", strings.Join(p.VersionMetadata.Depends, "\n"),
"OPTDEPENDS", strings.Join(p.VersionMetadata.OptDepends, "\n"),
"MAKEDEPENDS", strings.Join(p.VersionMetadata.MakeDepends, "\n"),
"CHECKDEPENDS", strings.Join(p.VersionMetadata.CheckDepends, "\n"),
}

var buf bytes.Buffer
for i := 0; i < len(entries); i += 2 {
if entries[i+1] != "" {
_, _ = fmt.Fprintf(&buf, "%%%s%%\n%s\n\n", entries[i], entries[i+1])
}
}
return buf.String()
}
Loading
Loading