mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	Only show the latest version of the package in the arch repo. closes #33534 --------- Co-authored-by: Giteabot <teabot@gitea.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			337 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			337 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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()
 | 
						|
	}
 | 
						|
	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
 | 
						|
	}
 | 
						|
 | 
						|
	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)
 | 
						|
				})
 | 
						|
 | 
						|
				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)
 | 
						|
				})
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	t.Run("KeepLastVersion", func(t *testing.T) {
 | 
						|
		defer tests.PrintCurrentTest(t)()
 | 
						|
		pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64")
 | 
						|
		pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64")
 | 
						|
		req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)).
 | 
						|
			AddBasicAuth(user.Name)
 | 
						|
		MakeRequest(t, req, http.StatusCreated)
 | 
						|
		req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)).
 | 
						|
			AddBasicAuth(user.Name)
 | 
						|
		MakeRequest(t, req, http.StatusCreated)
 | 
						|
 | 
						|
		req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
 | 
						|
		resp := MakeRequest(t, req, http.StatusOK)
 | 
						|
 | 
						|
		content, err := readIndexContent(resp.Body)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.Len(t, content, 2)
 | 
						|
 | 
						|
		_, has := content["gitea-test-1.0.0/desc"]
 | 
						|
		assert.False(t, has)
 | 
						|
		_, has = content["gitea-test-1.0.1/desc"]
 | 
						|
		assert.True(t, has)
 | 
						|
 | 
						|
		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/gitea-test/1.0.1/aarch64", rootURL)).
 | 
						|
			AddBasicAuth(user.Name)
 | 
						|
		MakeRequest(t, req, http.StatusNoContent)
 | 
						|
 | 
						|
		req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
 | 
						|
		resp = MakeRequest(t, req, http.StatusOK)
 | 
						|
		content, err = readIndexContent(resp.Body)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.Len(t, content, 2)
 | 
						|
		_, has = content["gitea-test-1.0.0/desc"]
 | 
						|
		assert.True(t, has)
 | 
						|
	})
 | 
						|
}
 |