images_test.go
⎇
Raw
1package e2e
2
3import (
4 "net/http"
5 "net/url"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10)
11
12func (e *env) blobFile(digest string) string {
13 return filepath.Join(e.DataDir, "registry", "blobs", strings.Replace(digest, ":", "/", 1))
14}
15
16func TestImagesTab(t *testing.T) {
17 e := newEnv(t, "REGISTRY_PULL", "users")
18 admin := e.admin()
19 alice := e.register("alice", "password123")
20 e.createRepo(admin, "img-repo")
21 e.createRepo(admin, "other-repo")
22 cfgA, layA, _ := pushImage(t, e, "img-repo", "v1", "a")
23 _, _, manB := pushImage(t, e, "img-repo", "v2", "b")
24 pushImage(t, e, "img-repo/web", "latest", "c")
25 // Shared layer: other-repo references the same bytes as img-repo v1.
26 sharedCfg, sharedLay, _ := pushImage(t, e, "other-repo", "v1", "a")
27 if sharedCfg != cfgA || sharedLay != layA {
28 t.Fatal("expected identical digests for identical content")
29 }
30
31 t.Run("tab visible and lists images with tags", func(t *testing.T) {
32 r := admin.get("/img-repo/images").mustStatus(200)
33 if !contains(admin.get("/img-repo").Texts(".repo-tab"), "Images") {
34 t.Error("Images tab missing")
35 }
36 titles := r.Texts(".release-item-title")
37 if len(titles) != 2 || !contains(titles, "img-repo") || !contains(titles, "img-repo/web") {
38 t.Errorf("images = %v", titles)
39 }
40 if tags := r.Texts(".image-tag .badge"); len(tags) != 3 {
41 t.Errorf("tags = %v", tags)
42 }
43 })
44
45 t.Run("access follows REGISTRY_PULL", func(t *testing.T) {
46 alice.get("/img-repo/images").mustStatus(200)
47 if e.anon().get("/img-repo/images").Code != 403 {
48 t.Error("anonymous could open the Images tab with REGISTRY_PULL=users")
49 }
50 if contains(e.anon().get("/img-repo").Texts(".repo-tab"), "Images") {
51 t.Error("Images tab shown to anonymous")
52 }
53 if alice.get("/img-repo/images").Has(`form[action="/img-repo/images/delete"]`) {
54 t.Error("delete form shown to non-admin")
55 }
56 alice.post("/img-repo/images/delete-all", nil).mustStatus(403)
57 })
58
59 t.Run("delete tag removes an untagged manifest", func(t *testing.T) {
60 // v2 is unique to this image, so its manifest file must go.
61 admin.post("/img-repo/images/delete", url.Values{"image": {""}, "tag": {"v2"}}).mustRedirect("/img-repo/images")
62 if got := regTags(t, e, "img-repo", ""); len(got) != 1 || got[0] != "v1" {
63 t.Errorf("tags = %v", got)
64 }
65 regAdmin(t, e, http.MethodGet, "/v2/img-repo/manifests/"+manB, nil).mustStatus(404)
66 if _, err := os.Stat(e.blobFile(manB)); !os.IsNotExist(err) {
67 t.Error("manifest file still on disk")
68 }
69 // The v1 layer stays: other-repo links the same bytes.
70 if _, err := os.Stat(e.blobFile(layA)); err != nil {
71 t.Error("shared layer file removed")
72 }
73 })
74
75 t.Run("delete image removes only its unshared files", func(t *testing.T) {
76 admin.post("/img-repo/images/delete", url.Values{"image": {""}}).mustRedirect("/img-repo/images")
77 regAdmin(t, e, http.MethodGet, "/v2/img-repo/tags/list", nil).mustStatus(200)
78 if got := regTags(t, e, "img-repo", ""); len(got) != 0 {
79 t.Errorf("tags = %v", got)
80 }
81 if _, err := os.Stat(e.blobFile(layA)); err != nil {
82 t.Error("layer shared with other-repo was removed")
83 }
84 if titles := admin.get("/img-repo/images").Texts(".release-item-title"); len(titles) != 1 {
85 t.Errorf("images after delete = %v", titles)
86 }
87 })
88
89 t.Run("delete all empties the tab", func(t *testing.T) {
90 admin.post("/img-repo/images/delete-all", nil).mustRedirect("/img-repo/images")
91 r := admin.get("/img-repo/images")
92 if r.Count(".release-item-title") != 0 || !r.Contains("No images yet") {
93 t.Error("images remain after delete all")
94 }
95 })
96
97 t.Run("deleting a repo removes its unshared image files", func(t *testing.T) {
98 _, layD, manD := pushImage(t, e, "other-repo", "v2", "d")
99 admin.post("/other-repo/settings/delete", nil).mustRedirect("/")
100 for _, d := range []string{layD, manD} {
101 if _, err := os.Stat(e.blobFile(d)); !os.IsNotExist(err) {
102 t.Errorf("file %s survived repo delete", d)
103 }
104 }
105 })
106}
107
108func TestImagesTabPublicAndPrivate(t *testing.T) {
109 e := newEnv(t, "REGISTRY_PULL", "public")
110 admin := e.admin()
111 e.createRepo(admin, "pub-img")
112 e.createRepo(admin, "priv-img", "is_private", "1")
113 pushImage(t, e, "pub-img", "v1", "p")
114 pushImage(t, e, "priv-img", "v1", "q")
115
116 if !contains(e.anon().get("/pub-img").Texts(".repo-tab"), "Images") {
117 t.Error("Images tab hidden from anonymous with REGISTRY_PULL=public")
118 }
119 e.anon().get("/pub-img/images").mustStatus(200)
120 if e.anon().get("/priv-img/images").Code == 200 {
121 t.Error("private repo images visible to anonymous")
122 }
123 admin.get("/priv-img/images").mustStatus(200)
124}
125