1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
package web
import (
"bytes"
"context"
"errors"
"html/template"
"log"
"net/http"
"strings"
"git.wilk.cx/cr-browser/registry"
)
type RepositoryLister interface {
Repositories(ctx context.Context) ([]registry.Repository, error)
Repository(ctx context.Context, name string) (registry.Repository, error)
Manifest(ctx context.Context, repository, digest string) (registry.Manifest, error)
}
type Server struct {
provider RepositoryLister
registryHost string
tmpl *template.Template
}
type PageData struct {
Title string
RegistryHost string
Repositories []registry.Repository
Repository registry.Repository
Manifest registry.Manifest
SelectedTag string
}
func NewServer(provider RepositoryLister, registryHost string) (*Server, error) {
tmpl, err := template.New("").
Funcs(templateFuncs()).
ParseGlob("templates/*.gohtml")
if err != nil {
return nil, err
}
return &Server{
provider: provider,
registryHost: registryHost,
tmpl: tmpl,
}, nil
}
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
// Static assets
mux.Handle(
"/assets/",
http.StripPrefix(
"/assets/",
http.FileServer(http.Dir("./assets")),
),
)
// Repository listing page
mux.HandleFunc("/", s.repositories)
mux.HandleFunc("/r/{name...}", s.repository)
return mux
}
func (s *Server) repositories(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodGet {
w.Header().Set("Allow", "GET")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
repos, err := s.provider.Repositories(r.Context())
if err != nil {
log.Printf("fetch repositories: %v", err)
http.Error(w, "failed to fetch repositories", http.StatusBadGateway)
return
}
data := PageData{
Title: "Container Registry",
RegistryHost: s.registryHost,
Repositories: repos,
}
var buf bytes.Buffer
if err := s.tmpl.ExecuteTemplate(&buf, "repositories", data); err != nil {
log.Printf("render repositories: %v", err)
http.Error(w, "failed to render page", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(buf.Bytes())
}
func (s *Server) repository(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", "GET")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
name := strings.TrimSuffix(r.PathValue("name"), "/")
// Dispatch manifest links while allowing slashes in repository names.
if i := strings.LastIndex(name, "/m/"); i >= 0 {
digest := name[i+3:]
if strings.Contains(digest, ":") && !strings.Contains(digest, "/") {
s.manifest(w, r, name[:i], digest)
return
}
}
repo, err := s.provider.Repository(r.Context(), name)
if err != nil {
if errors.Is(err, registry.ErrNotFound) {
http.NotFound(w, r)
return
}
log.Printf("fetch repository: %v", err)
http.Error(w, "failed to fetch repository", http.StatusBadGateway)
return
}
data := PageData{
Title: repo.Name,
RegistryHost: s.registryHost,
Repository: repo,
}
var buf bytes.Buffer
if err := s.tmpl.ExecuteTemplate(&buf, "repository", data); err != nil {
log.Printf("render repository: %v", err)
http.Error(w, "failed to render page", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(buf.Bytes())
}
func (s *Server) manifest(w http.ResponseWriter, r *http.Request, repository, digest string) {
manifest, err := s.provider.Manifest(r.Context(), repository, digest)
if err != nil {
if errors.Is(err, registry.ErrNotFound) {
http.NotFound(w, r)
return
}
log.Printf("fetch manifest: %v", err)
http.Error(w, "failed to fetch manifest", http.StatusBadGateway)
return
}
data := PageData{
Title: "Manifest in " + repository,
RegistryHost: s.registryHost,
Repository: registry.Repository{Name: repository},
Manifest: manifest,
SelectedTag: r.URL.Query().Get("tag"),
}
var buf bytes.Buffer
if err := s.tmpl.ExecuteTemplate(&buf, "manifest", data); err != nil {
log.Printf("render manifest: %v", err)
http.Error(w, "failed to render page", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(buf.Bytes())
}
|