gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "archive/tar"
  6. "bytes"
  7. "compress/gzip"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "testing"
  12. "code.gitea.io/gitea/models/packages"
  13. "code.gitea.io/gitea/models/unittest"
  14. user_model "code.gitea.io/gitea/models/user"
  15. arch_module "code.gitea.io/gitea/modules/packages/arch"
  16. arch_service "code.gitea.io/gitea/services/packages/arch"
  17. "code.gitea.io/gitea/tests"
  18. "github.com/klauspost/compress/zstd"
  19. "github.com/stretchr/testify/assert"
  20. "github.com/ulikunitz/xz"
  21. )
  22. func TestPackageArch(t *testing.T) {
  23. defer tests.PrepareTestEnv(t)()
  24. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  25. packageName := "gitea-test"
  26. packageVersion := "1.4.1-r3"
  27. createPackage := func(compression, name, version, architecture string) []byte {
  28. var buf bytes.Buffer
  29. var cw io.WriteCloser
  30. switch compression {
  31. case "zst":
  32. cw, _ = zstd.NewWriter(&buf)
  33. case "xz":
  34. cw, _ = xz.NewWriter(&buf)
  35. case "gz":
  36. cw = gzip.NewWriter(&buf)
  37. }
  38. tw := tar.NewWriter(cw)
  39. info := []byte(`pkgname = ` + name + `
  40. pkgbase = ` + name + `
  41. pkgver = ` + version + `
  42. pkgdesc = Description
  43. # comment
  44. builddate = 1678834800
  45. size = 8
  46. arch = ` + architecture + `
  47. license = MIT`)
  48. hdr := &tar.Header{
  49. Name: ".PKGINFO",
  50. Mode: 0o600,
  51. Size: int64(len(info)),
  52. }
  53. tw.WriteHeader(hdr)
  54. tw.Write(info)
  55. for _, file := range []string{"etc/dummy", "opt/file/bin"} {
  56. hdr := &tar.Header{
  57. Name: file,
  58. Mode: 0o600,
  59. Size: 4,
  60. }
  61. tw.WriteHeader(hdr)
  62. tw.Write([]byte("test"))
  63. }
  64. tw.Close()
  65. cw.Close()
  66. return buf.Bytes()
  67. }
  68. readIndexContent := func(r io.Reader) (map[string]string, error) {
  69. gzr, err := gzip.NewReader(r)
  70. if err != nil {
  71. return nil, err
  72. }
  73. content := make(map[string]string)
  74. tr := tar.NewReader(gzr)
  75. for {
  76. hd, err := tr.Next()
  77. if err == io.EOF {
  78. break
  79. }
  80. if err != nil {
  81. return nil, err
  82. }
  83. buf, err := io.ReadAll(tr)
  84. if err != nil {
  85. return nil, err
  86. }
  87. content[hd.Name] = string(buf)
  88. }
  89. return content, nil
  90. }
  91. compressions := []string{"gz", "xz", "zst"}
  92. repositories := []string{"main", "testing", "with/slash", ""}
  93. rootURL := fmt.Sprintf("/api/packages/%s/arch", user.Name)
  94. t.Run("RepositoryKey", func(t *testing.T) {
  95. defer tests.PrintCurrentTest(t)()
  96. req := NewRequest(t, "GET", rootURL+"/repository.key")
  97. resp := MakeRequest(t, req, http.StatusOK)
  98. assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
  99. assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
  100. })
  101. contentAarch64Gz := createPackage("gz", packageName, packageVersion, "aarch64")
  102. for _, compression := range compressions {
  103. contentAarch64 := createPackage(compression, packageName, packageVersion, "aarch64")
  104. contentAny := createPackage(compression, packageName+"_"+arch_module.AnyArch, packageVersion, arch_module.AnyArch)
  105. for _, repository := range repositories {
  106. t.Run(fmt.Sprintf("[%s,%s]", repository, compression), func(t *testing.T) {
  107. t.Run("Upload", func(t *testing.T) {
  108. defer tests.PrintCurrentTest(t)()
  109. uploadURL := fmt.Sprintf("%s/%s", rootURL, repository)
  110. req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
  111. MakeRequest(t, req, http.StatusUnauthorized)
  112. req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})).
  113. AddBasicAuth(user.Name)
  114. MakeRequest(t, req, http.StatusBadRequest)
  115. req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)).
  116. AddBasicAuth(user.Name)
  117. MakeRequest(t, req, http.StatusCreated)
  118. pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeArch)
  119. assert.NoError(t, err)
  120. assert.Len(t, pvs, 1)
  121. pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0])
  122. assert.NoError(t, err)
  123. assert.Nil(t, pd.SemVer)
  124. assert.IsType(t, &arch_module.VersionMetadata{}, pd.Metadata)
  125. assert.Equal(t, packageName, pd.Package.Name)
  126. assert.Equal(t, packageVersion, pd.Version.Version)
  127. pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID)
  128. assert.NoError(t, err)
  129. assert.NotEmpty(t, pfs)
  130. assert.Condition(t, func() bool {
  131. seen := false
  132. expectedFilename := fmt.Sprintf("%s-%s-aarch64.pkg.tar.%s", packageName, packageVersion, compression)
  133. expectedCompositeKey := repository + "|aarch64"
  134. for _, pf := range pfs {
  135. if pf.Name == expectedFilename && pf.CompositeKey == expectedCompositeKey {
  136. if seen {
  137. return false
  138. }
  139. seen = true
  140. assert.True(t, pf.IsLead)
  141. pfps, err := packages.GetProperties(t.Context(), packages.PropertyTypeFile, pf.ID)
  142. assert.NoError(t, err)
  143. for _, pfp := range pfps {
  144. switch pfp.Name {
  145. case arch_module.PropertyRepository:
  146. assert.Equal(t, repository, pfp.Value)
  147. case arch_module.PropertyArchitecture:
  148. assert.Equal(t, "aarch64", pfp.Value)
  149. }
  150. }
  151. }
  152. }
  153. return seen
  154. })
  155. req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)).
  156. AddBasicAuth(user.Name)
  157. MakeRequest(t, req, http.StatusConflict)
  158. // Add same package with different compression leads to conflict
  159. req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64Gz)).
  160. AddBasicAuth(user.Name)
  161. MakeRequest(t, req, http.StatusConflict)
  162. })
  163. t.Run("Index", func(t *testing.T) {
  164. defer tests.PrintCurrentTest(t)()
  165. req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
  166. resp := MakeRequest(t, req, http.StatusOK)
  167. content, err := readIndexContent(resp.Body)
  168. assert.NoError(t, err)
  169. desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
  170. assert.True(t, has)
  171. assert.Contains(t, desc, "%FILENAME%\n"+fmt.Sprintf("%s-%s-aarch64.pkg.tar.%s", packageName, packageVersion, compression)+"\n\n")
  172. assert.Contains(t, desc, "%NAME%\n"+packageName+"\n\n")
  173. assert.Contains(t, desc, "%VERSION%\n"+packageVersion+"\n\n")
  174. assert.Contains(t, desc, "%ARCH%\naarch64\n")
  175. assert.NotContains(t, desc, "%ARCH%\n"+arch_module.AnyArch+"\n")
  176. assert.Contains(t, desc, "%LICENSE%\nMIT\n")
  177. files, has := content[fmt.Sprintf("%s-%s/files", packageName, packageVersion)]
  178. assert.True(t, has)
  179. assert.Contains(t, files, "%FILES%\netc/dummy\nopt/file/bin\n\n")
  180. for _, indexFile := range []string{
  181. arch_service.IndexArchiveFilename,
  182. arch_service.IndexArchiveFilename + ".tar.gz",
  183. "index.db",
  184. "index.db.tar.gz",
  185. "index.files",
  186. "index.files.tar.gz",
  187. } {
  188. req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, indexFile))
  189. MakeRequest(t, req, http.StatusOK)
  190. req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s.sig", rootURL, repository, indexFile))
  191. MakeRequest(t, req, http.StatusOK)
  192. }
  193. })
  194. t.Run("Download", func(t *testing.T) {
  195. defer tests.PrintCurrentTest(t)()
  196. req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s-%s-aarch64.pkg.tar.%s", rootURL, repository, packageName, packageVersion, compression))
  197. MakeRequest(t, req, http.StatusOK)
  198. req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s-%s-aarch64.pkg.tar.%s.sig", rootURL, repository, packageName, packageVersion, compression))
  199. MakeRequest(t, req, http.StatusOK)
  200. })
  201. t.Run("Any", func(t *testing.T) {
  202. defer tests.PrintCurrentTest(t)()
  203. req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s", rootURL, repository), bytes.NewReader(contentAny)).
  204. AddBasicAuth(user.Name)
  205. MakeRequest(t, req, http.StatusCreated)
  206. req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
  207. resp := MakeRequest(t, req, http.StatusOK)
  208. content, err := readIndexContent(resp.Body)
  209. assert.NoError(t, err)
  210. desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
  211. assert.True(t, has)
  212. assert.Contains(t, desc, "%NAME%\n"+packageName+"\n\n")
  213. assert.Contains(t, desc, "%ARCH%\naarch64\n")
  214. desc, has = content[fmt.Sprintf("%s-%s/desc", packageName+"_"+arch_module.AnyArch, packageVersion)]
  215. assert.True(t, has)
  216. assert.Contains(t, desc, "%NAME%\n"+packageName+"_any\n\n")
  217. assert.Contains(t, desc, "%ARCH%\n"+arch_module.AnyArch+"\n")
  218. // "any" architecture package should be available with every architecture requested
  219. for _, arch := range []string{arch_module.AnyArch, "aarch64", "myarch"} {
  220. req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s-%s-any.pkg.tar.%s", rootURL, repository, arch, packageName+"_"+arch_module.AnyArch, packageVersion, compression))
  221. MakeRequest(t, req, http.StatusOK)
  222. }
  223. req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/any", rootURL, repository, packageName+"_"+arch_module.AnyArch, packageVersion)).
  224. AddBasicAuth(user.Name)
  225. MakeRequest(t, req, http.StatusNoContent)
  226. })
  227. t.Run("Delete", func(t *testing.T) {
  228. defer tests.PrintCurrentTest(t)()
  229. req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/aarch64", rootURL, repository, packageName, packageVersion))
  230. MakeRequest(t, req, http.StatusUnauthorized)
  231. req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/aarch64", rootURL, repository, packageName, packageVersion)).
  232. AddBasicAuth(user.Name)
  233. MakeRequest(t, req, http.StatusNoContent)
  234. // Deleting the last file of an architecture should remove that index
  235. req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
  236. MakeRequest(t, req, http.StatusNotFound)
  237. })
  238. })
  239. }
  240. }
  241. t.Run("KeepLastVersion", func(t *testing.T) {
  242. defer tests.PrintCurrentTest(t)()
  243. pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64")
  244. pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64")
  245. req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)).
  246. AddBasicAuth(user.Name)
  247. MakeRequest(t, req, http.StatusCreated)
  248. req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)).
  249. AddBasicAuth(user.Name)
  250. MakeRequest(t, req, http.StatusCreated)
  251. req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
  252. resp := MakeRequest(t, req, http.StatusOK)
  253. content, err := readIndexContent(resp.Body)
  254. assert.NoError(t, err)
  255. assert.Len(t, content, 2)
  256. _, has := content["gitea-test-1.0.0/desc"]
  257. assert.False(t, has)
  258. _, has = content["gitea-test-1.0.1/desc"]
  259. assert.True(t, has)
  260. req = NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64").
  261. AddBasicAuth(user.Name)
  262. MakeRequest(t, req, http.StatusNoContent)
  263. req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
  264. resp = MakeRequest(t, req, http.StatusOK)
  265. content, err = readIndexContent(resp.Body)
  266. assert.NoError(t, err)
  267. assert.Len(t, content, 2)
  268. _, has = content["gitea-test-1.0.0/desc"]
  269. assert.True(t, has)
  270. })
  271. }