mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-30 01:37:44 +01:00
87d50eca87
Previous arch package grouping was not well-suited for complex or multi-architecture environments. It now supports the following content: - Support grouping by any path. - New support for packages in `xz` format. - Fix clean up rules <!--start release-notes-assistant--> ## Draft release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/4903): <!--number 4903 --><!--line 0 --><!--description c3VwcG9ydCBncm91cGluZyBieSBhbnkgcGF0aCBmb3IgYXJjaCBwYWNrYWdl-->support grouping by any path for arch package<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4903 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Exploding Dragon <explodingfkl@gmail.com> Co-committed-by: Exploding Dragon <explodingfkl@gmail.com>
326 lines
12 KiB
Go
326 lines
12 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bufio"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"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_model "code.gitea.io/gitea/modules/packages/arch"
|
|
"code.gitea.io/gitea/tests"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPackageArch(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
unPack := func(s string) []byte {
|
|
data, _ := base64.StdEncoding.DecodeString(strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(s), "\n", ""), "\r", ""))
|
|
return data
|
|
}
|
|
rootURL := fmt.Sprintf("/api/packages/%s/arch", user.Name)
|
|
|
|
pkgs := map[string][]byte{
|
|
"any": unPack(`
|
|
KLUv/QBYXRMABmOHSbCWag6dY6d8VNtVR3rpBnWdBbkDAxM38Dj3XG3FK01TCKlWtMV9QpskYdsm
|
|
e6fh5gWqM8edeurYNESoIUz/RmtyQy68HVrBj1p+AIoAYABFSJh4jcDyWNQgHIKIuNgIll64S4oY
|
|
FFIUk6vJQBMIIl2iYtIysqKWVYMCYvXDpAKTMzVGwZTUWhbciFCglIMH1QMbEtjHpohSi8XRYwPr
|
|
AwACSy/fzxO1FobizlP7sFgHcpx90Pus94Edjcc9GOustbD3PBprLUxH50IGC1sfw31c7LOfT4Qe
|
|
nh0KP1uKywwdPrRYmuyIkWBHRlcLfeBIDpKKqw44N0K2nNAfFW5grHRfSShyVgaEIZwIVVmFGL7O
|
|
88XDE5whJm4NkwA91dRoPBCcrgqozKSyah1QygsWkCshAaYrvbHCFdUTJCOgBpeUTMuJJ6+SRtcj
|
|
wIRua8mGJyg7qWoqJQq9z/4+DU1rHrEO8f6QZ3HUu3IM7GY37u+jeWjUu45637yN+qj338cdi0Uc
|
|
y0a9a+e5//1cYnPUu37dxr15khzNQ9/PE80aC/1okjz9mGo3bqP5Ue+scflGshdzx2g28061k2PW
|
|
uKwzjmV/XzTzzmKdcfz3eRbJoRPddcaP/n4PSZqQeYa1PDtPQzOHJK0amfjvz0IUV/v38xHJK/rz
|
|
JtFpalPD30drDWi7Bl8NB3J/P3csijQyldWZ8gy3TNslLsozMw74DhoAXoAfnE8xydUUHPZ3hML4
|
|
2zVDGiEXSGYRx4BKQDcDJA5S9Ca25FRgPtSWSowZJpJTYAR9WCPHUDgACm6+hBecGDPNClpwHZ2A
|
|
EQ==
|
|
`),
|
|
"x86_64": unPack(`
|
|
KLUv/QBYnRMAFmOJS7BUbg7Un8q21hxCopsOMn6UGTzJRbHI753uOeMdxZ+V7ajoETVxl9CSBCR5
|
|
2a3K1vr1gwyp9gCTH422bRNxHEg7Z0z9HV4rH/DGFn8AjABjAFQ2oaUVMRRGViVoqmxAVKuoKQVM
|
|
NJRwTDl9NcHCClliWjTpWin6sRUZsXSipWlAipQnleThRgFF5QTAzpth0UPFkhQeJRnYOaqSScEC
|
|
djCPDwE8pQTfVXW9F7bmznX3YTNZDeP7IHgxDazNQhp+UDa798KeRgvvvbCamgsYdL461TfvcmlY
|
|
djFowWYH5yaH5ztZcemh4omAkm7iQIWvGypNIXJQNgc7DVuHjx06I4MZGTIkeEBIOIL0OxcvnGps
|
|
0TwxycqKYESrwwQYEDKI2F0hNXH1/PCQ2BS4Ykki48EAaflAbRHxYrRQbdAZ4oXVAMGCkYOXkBRb
|
|
NkwjNCoIF07ByTlyfJhmoHQtCbFYDN+941783KqzusznmPePXJPluS1+cL/74Rd/1UHluW15blFv
|
|
ol6e+8XPPZNDPN/Kc9vOdX/xNZrT8twWnH34U9Xkqw76rqqrPjPQl6nJde9i74e/8Mtz6zOjT3R7
|
|
Uve8BrabpT4zanE83158MtVbkxbH84vPNWkGqeu2OF704vfRzAGl6mhRtXPdmOrRzFla+BO+DL34
|
|
uHHN9r74usjkduX5VEhNz9TnxV9trSabvYAwuIZffN0zSeZM3c3GUHX8dG6jeUgHGgBbgB9cUDHJ
|
|
1RR09teBwvjbNUMaIRdIZhHHgEpANwMkDpL0JsbkVFA+0JZKjBkmklNgBH1YI8dQOAAKbr6EF5wY
|
|
M80KWnAdnYAR
|
|
`),
|
|
"aarch64": unPack(`
|
|
KLUv/QBYdRQAVuSMS7BUbg7Un8q21hxCopsOMn6UGTzJRbHI753uOeMdxZ+V7ajoEbUkUXbXhXW/
|
|
7FanWzv7B/EcMxhodFqyZkUcB9LOGVN/h9MqG7zFFmoAaQB8AEFrvpXntn3V/cXXaE7Lc9uP5uFP
|
|
VXPl+ue7qnJ9Zp8vU3PVvYu9HvbAL8+tz4y+0O1J3TPXqbZ5l3+lapk5ee+L577qXvdf+Atn+P69
|
|
4Qz8QhpYw4/xd78Q3/v6Wg28974u1Ojc2ODseAGpHs2crYG4kef84uNGnu198fWQuVq+8ymQmp5p
|
|
z4vPbRjOaBC+FxziF1/3TJI5U3ezMlQdPZ3baA7SMhnMunvHvfg5rrO6zOeY94+rJstzW/zgetfD
|
|
Lz7XP+W5bXluUW+hXp77xc89kwFRTF1PrKxAFpgXT7ZWhjzYjpRIStGyNCAGBYM6AnGrkKKCAmAH
|
|
k3HBI8VyBBYdGdApmoqJYQE62EeIADCkBF1VOW0WYnz/+y6ufTMaDQ2GDDme7Wapz4xa3JpvLz6Z
|
|
6q1Ji1vzi79q0vxR+ba4dejF76OZ80nV0aJqX3VjKCsuP1g0EWDSURyw0JVDZWlEzsnmYLdh8wDS
|
|
I2dkIEMjxsSOiAlJjH4HIwbTjayZJidXVxKQYH2gICOCBhK7KqMlLZ4gMCU1BapYlsTAXnywepyy
|
|
jMBmtEhxyCnCZdUAwYKxAxeRFVk4TCL0aYgWjt3kHTg9SjVStppI2YCSWshUEFGdmJmyCVGpnqIU
|
|
KNlA0hEjIOACGSLqYpXAD5SSNVT2MJRJwREAF4FRHPBlCJMSNwFguGAWDJBg+KIArkIJGNtCydUL
|
|
TuN1oBh/+zKkEblAsgjGqVgUwKLP+UOMOGCpAhICtg6ncFJH`),
|
|
"other": unPack(`
|
|
/Td6WFoAAATm1rRGBMCyBIAYIQEWAAAAAAAAABaHRszgC/8CKl0AFxNGhTWwfXmuDQEJlHgNLrkq
|
|
VxpJY6d9iRTt6gB4uCj0481rnYfXaUADHzOFuF3490RPrM6juPXrknqtVyuWJ5efW19BgwctN6xk
|
|
UiXiZaXVAWVWJWy2XHJiyYCMWBfIjUfo1ccOgwolwgFHJ64ZJjbayA3k6lYPcImuAqYL5NEVHpwl
|
|
Z8CWIjiXXSMQGsB3gxMdq9nySZbHQLK/KCKQ+oseF6kXyIgSEyuG4HhjVBBYIwTvWzI06kjNUXEy
|
|
2sw0n50uocLSAwJ/3mdX3n3XF5nmmuQMPtFbdQgQtC2VhyVd3TdIF+pT6zAEzXFJJ3uLkNbKSS88
|
|
ZdBny6X/ftT5lQpNi/Wg0xLEQA4m4fu4fRAR0kOKzHM2svNLbTxa/wOPidqPzR6b/jfKmHkXxBNa
|
|
jFafty0a5K2S3F6JpwXZ2fqti/zG9NtMc+bbuXycC327EofXRXNtuOupELDD+ltTOIBF7CcTswyi
|
|
MZDP1PBie6GqDV2GuPz+0XXmul/ds+XysG19HIkKbJ+cQKp5o7Y0tI7EHM8GhwMl7MjgpQGj5nuv
|
|
0u2hqt4NXPNYqaMm9bFnnIUxEN82HgNWBcXf2baWKOdGzPzCuWg2fAM4zxHnBWcimxLXiJgaI8mU
|
|
J/QqTPWE0nJf1PW/J9yFQVR1Xo0TJyiX8/ObwmbqUPpxRGjKlYRBvn0jbTdUAENBSn+QVcASRGFE
|
|
SB9OM2B8Bg4jR/oojs8Beoq7zbIblgAAAACfRtXvhmznOgABzgSAGAAAKklb4rHEZ/sCAAAAAARZ
|
|
Wg==`), // this is tar.xz file
|
|
}
|
|
|
|
t.Run("RepositoryKey", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
req := NewRequest(t, "GET", rootURL+"/repository.key")
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
require.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
|
|
require.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
|
})
|
|
|
|
for _, group := range []string{"", "arch", "arch/os", "x86_64"} {
|
|
groupURL := rootURL
|
|
if group != "" {
|
|
groupURL = groupURL + "/" + group
|
|
}
|
|
t.Run(fmt.Sprintf("Upload[%s]", group), func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
req := NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["any"]))
|
|
MakeRequest(t, req, http.StatusUnauthorized)
|
|
|
|
req = NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["any"])).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
req = NewRequestWithBody(t, "PUT", groupURL, bytes.NewBuffer([]byte("any string"))).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusBadRequest)
|
|
|
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeArch)
|
|
require.NoError(t, err)
|
|
require.Len(t, pvs, 1)
|
|
|
|
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
|
require.NoError(t, err)
|
|
require.Nil(t, pd.SemVer)
|
|
require.IsType(t, &arch_model.VersionMetadata{}, pd.Metadata)
|
|
require.Equal(t, "test", pd.Package.Name)
|
|
require.Equal(t, "1.0.0-1", pd.Version.Version)
|
|
|
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
|
require.NoError(t, err)
|
|
size := 0
|
|
for _, pf := range pfs {
|
|
if pf.CompositeKey == group {
|
|
size++
|
|
}
|
|
}
|
|
require.Equal(t, 2, size) // zst and zst.sig
|
|
|
|
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(len(pkgs["any"])), pb.Size)
|
|
|
|
req = NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["any"])).
|
|
AddBasicAuth(user.Name) // exists
|
|
MakeRequest(t, req, http.StatusConflict)
|
|
req = NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["x86_64"])).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
req = NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["aarch64"])).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
req = NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["aarch64"])).
|
|
AddBasicAuth(user.Name) // exists again
|
|
MakeRequest(t, req, http.StatusConflict)
|
|
})
|
|
|
|
t.Run(fmt.Sprintf("Download[%s]", group), func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", groupURL+"/x86_64/test-1.0.0-1-x86_64.pkg.tar.zst")
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
require.Equal(t, pkgs["x86_64"], resp.Body.Bytes())
|
|
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/test-1.0.0-1-any.pkg.tar.zst")
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
require.Equal(t, pkgs["any"], resp.Body.Bytes())
|
|
|
|
// get other group
|
|
req = NewRequest(t, "GET", rootURL+"/unknown/x86_64/test-1.0.0-1-aarch64.pkg.tar.zst")
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
|
|
t.Run(fmt.Sprintf("SignVerify[%s]", group), func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", rootURL+"/repository.key")
|
|
respPub := MakeRequest(t, req, http.StatusOK)
|
|
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/test-1.0.0-1-any.pkg.tar.zst")
|
|
respPkg := MakeRequest(t, req, http.StatusOK)
|
|
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/test-1.0.0-1-any.pkg.tar.zst.sig")
|
|
respSig := MakeRequest(t, req, http.StatusOK)
|
|
|
|
if err := gpgVerify(respPub.Body.Bytes(), respSig.Body.Bytes(), respPkg.Body.Bytes()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
t.Run(fmt.Sprintf("RepositoryDB[%s]", group), func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", rootURL+"/repository.key")
|
|
respPub := MakeRequest(t, req, http.StatusOK)
|
|
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db")
|
|
respPkg := MakeRequest(t, req, http.StatusOK)
|
|
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db.sig")
|
|
respSig := MakeRequest(t, req, http.StatusOK)
|
|
|
|
if err := gpgVerify(respPub.Body.Bytes(), respSig.Body.Bytes(), respPkg.Body.Bytes()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
files, err := listTarGzFiles(respPkg.Body.Bytes())
|
|
require.NoError(t, err)
|
|
require.Len(t, files, 1)
|
|
for s, d := range files {
|
|
name := getProperty(string(d.Data), "NAME")
|
|
ver := getProperty(string(d.Data), "VERSION")
|
|
require.Equal(t, name+"-"+ver+"/desc", s)
|
|
fn := getProperty(string(d.Data), "FILENAME")
|
|
pgp := getProperty(string(d.Data), "PGPSIG")
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/"+fn+".sig")
|
|
respSig := MakeRequest(t, req, http.StatusOK)
|
|
decodeString, err := base64.StdEncoding.DecodeString(pgp)
|
|
require.NoError(t, err)
|
|
require.Equal(t, respSig.Body.Bytes(), decodeString)
|
|
}
|
|
})
|
|
|
|
t.Run(fmt.Sprintf("Delete[%s]", group), func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
// test data
|
|
req := NewRequestWithBody(t, "PUT", groupURL, bytes.NewReader(pkgs["other"])).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1", nil).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1", nil).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db")
|
|
respPkg := MakeRequest(t, req, http.StatusOK)
|
|
files, err := listTarGzFiles(respPkg.Body.Bytes())
|
|
require.NoError(t, err)
|
|
require.Len(t, files, 1) // other pkg in L225
|
|
|
|
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil).
|
|
AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db")
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
}
|
|
}
|
|
|
|
func getProperty(data, key string) string {
|
|
r := bufio.NewReader(strings.NewReader(data))
|
|
for {
|
|
line, _, err := r.ReadLine()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
if strings.Contains(string(line), "%"+key+"%") {
|
|
readLine, _, _ := r.ReadLine()
|
|
return string(readLine)
|
|
}
|
|
}
|
|
}
|
|
|
|
func listTarGzFiles(data []byte) (fstest.MapFS, error) {
|
|
reader, err := gzip.NewReader(bytes.NewBuffer(data))
|
|
defer reader.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tarRead := tar.NewReader(reader)
|
|
files := make(fstest.MapFS)
|
|
for {
|
|
cur, err := tarRead.Next()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
if cur.Typeflag != tar.TypeReg {
|
|
continue
|
|
}
|
|
data, err := io.ReadAll(tarRead)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
files[cur.Name] = &fstest.MapFile{Data: data}
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func gpgVerify(pub, sig, data []byte) error {
|
|
sigPack, err := packet.Read(bytes.NewBuffer(sig))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signature, ok := sigPack.(*packet.Signature)
|
|
if !ok {
|
|
return errors.New("invalid sign key")
|
|
}
|
|
pubBlock, err := armor.Decode(bytes.NewReader(pub))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pack, err := packet.Read(pubBlock.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
publicKey, ok := pack.(*packet.PublicKey)
|
|
if !ok {
|
|
return errors.New("invalid public key")
|
|
}
|
|
hash := signature.Hash.New()
|
|
_, err = hash.Write(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return publicKey.VerifySignature(hash, signature)
|
|
}
|