1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/runner.git synced 2025-09-15 18:57:01 +00:00

chore: refactor act/artifactcache Handler to an interface (#934)

- the Handler struct becomes handler (lowercase)
- the Handler interface is defined to be the existing methods
- isClosed() is added and used only in tests
- setgcAt() is added and used only in tests

---

This is to allow mocking the Handler interface for testing.

<!--start release-notes-assistant-->
<!--URL:https://code.forgejo.org/forgejo/runner-->
- other
  - [PR](https://code.forgejo.org/forgejo/runner/pulls/934): <!--number 934 --><!--line 0 --><!--description Y2hvcmU6IHJlZmFjdG9yIGFjdC9hcnRpZmFjdGNhY2hlIEhhbmRsZXIgdG8gYW4gaW50ZXJmYWNl-->chore: refactor act/artifactcache Handler to an interface<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://code.forgejo.org/forgejo/runner/pulls/934
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.code.forgejo.org>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
This commit is contained in:
Earl Warren 2025-09-04 14:38:50 +00:00 committed by earl-warren
parent bfc5516467
commit 69c6c70845
No known key found for this signature in database
GPG key ID: F128CBE6AB3A7201
5 changed files with 52 additions and 29 deletions

View file

@ -28,7 +28,26 @@ const (
urlBase = "/_apis/artifactcache" urlBase = "/_apis/artifactcache"
) )
type Handler struct { type Handler interface {
ExternalURL() string
Close() error
isClosed() bool
openDB() (*bolthold.Store, error)
find(w http.ResponseWriter, r *http.Request, params httprouter.Params)
reserve(w http.ResponseWriter, r *http.Request, params httprouter.Params)
upload(w http.ResponseWriter, r *http.Request, params httprouter.Params)
commit(w http.ResponseWriter, r *http.Request, params httprouter.Params)
get(w http.ResponseWriter, r *http.Request, params httprouter.Params)
clean(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
middleware(handler httprouter.Handle) httprouter.Handle
readCache(id uint64) (*Cache, error)
useCache(id uint64) error
setgcAt(at time.Time)
gcCache()
responseJSON(w http.ResponseWriter, r *http.Request, code int, v ...any)
}
type handler struct {
dir string dir string
storage *Storage storage *Storage
router *httprouter.Router router *httprouter.Router
@ -43,8 +62,8 @@ type Handler struct {
outboundIP string outboundIP string
} }
func StartHandler(dir, outboundIP string, port uint16, secret string, logger logrus.FieldLogger) (*Handler, error) { func StartHandler(dir, outboundIP string, port uint16, secret string, logger logrus.FieldLogger) (Handler, error) {
h := &Handler{ h := &handler{
secret: secret, secret: secret,
} }
@ -114,14 +133,14 @@ func StartHandler(dir, outboundIP string, port uint16, secret string, logger log
return h, nil return h, nil
} }
func (h *Handler) ExternalURL() string { func (h *handler) ExternalURL() string {
port := strconv.Itoa(h.listener.Addr().(*net.TCPAddr).Port) port := strconv.Itoa(h.listener.Addr().(*net.TCPAddr).Port)
// TODO: make the external url configurable if necessary // TODO: make the external url configurable if necessary
return fmt.Sprintf("http://%s", net.JoinHostPort(h.outboundIP, port)) return fmt.Sprintf("http://%s", net.JoinHostPort(h.outboundIP, port))
} }
func (h *Handler) Close() error { func (h *handler) Close() error {
if h == nil { if h == nil {
return nil return nil
} }
@ -146,7 +165,11 @@ func (h *Handler) Close() error {
return retErr return retErr
} }
func (h *Handler) openDB() (*bolthold.Store, error) { func (h *handler) isClosed() bool {
return h.listener == nil && h.server == nil
}
func (h *handler) openDB() (*bolthold.Store, error) {
return bolthold.Open(filepath.Join(h.dir, "bolt.db"), 0o644, &bolthold.Options{ return bolthold.Open(filepath.Join(h.dir, "bolt.db"), 0o644, &bolthold.Options{
Encoder: json.Marshal, Encoder: json.Marshal,
Decoder: json.Unmarshal, Decoder: json.Unmarshal,
@ -159,7 +182,7 @@ func (h *Handler) openDB() (*bolthold.Store, error) {
} }
// GET /_apis/artifactcache/cache // GET /_apis/artifactcache/cache
func (h *Handler) find(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func (h *handler) find(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
rundata := runDataFromHeaders(r) rundata := runDataFromHeaders(r)
repo, err := h.validateMac(rundata) repo, err := h.validateMac(rundata)
if err != nil { if err != nil {
@ -216,7 +239,7 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, params httprouter
} }
// POST /_apis/artifactcache/caches // POST /_apis/artifactcache/caches
func (h *Handler) reserve(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func (h *handler) reserve(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
rundata := runDataFromHeaders(r) rundata := runDataFromHeaders(r)
repo, err := h.validateMac(rundata) repo, err := h.validateMac(rundata)
if err != nil { if err != nil {
@ -255,7 +278,7 @@ func (h *Handler) reserve(w http.ResponseWriter, r *http.Request, params httprou
} }
// PATCH /_apis/artifactcache/caches/:id // PATCH /_apis/artifactcache/caches/:id
func (h *Handler) upload(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func (h *handler) upload(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
rundata := runDataFromHeaders(r) rundata := runDataFromHeaders(r)
repo, err := h.validateMac(rundata) repo, err := h.validateMac(rundata)
if err != nil { if err != nil {
@ -310,7 +333,7 @@ func (h *Handler) upload(w http.ResponseWriter, r *http.Request, params httprout
} }
// POST /_apis/artifactcache/caches/:id // POST /_apis/artifactcache/caches/:id
func (h *Handler) commit(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func (h *handler) commit(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
rundata := runDataFromHeaders(r) rundata := runDataFromHeaders(r)
repo, err := h.validateMac(rundata) repo, err := h.validateMac(rundata)
if err != nil { if err != nil {
@ -374,7 +397,7 @@ func (h *Handler) commit(w http.ResponseWriter, r *http.Request, params httprout
} }
// GET /_apis/artifactcache/artifacts/:id // GET /_apis/artifactcache/artifacts/:id
func (h *Handler) get(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func (h *handler) get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
rundata := runDataFromHeaders(r) rundata := runDataFromHeaders(r)
repo, err := h.validateMac(rundata) repo, err := h.validateMac(rundata)
if err != nil { if err != nil {
@ -417,7 +440,7 @@ func (h *Handler) get(w http.ResponseWriter, r *http.Request, params httprouter.
} }
// POST /_apis/artifactcache/clean // POST /_apis/artifactcache/clean
func (h *Handler) clean(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func (h *handler) clean(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
rundata := runDataFromHeaders(r) rundata := runDataFromHeaders(r)
_, err := h.validateMac(rundata) _, err := h.validateMac(rundata)
if err != nil { if err != nil {
@ -430,7 +453,7 @@ func (h *Handler) clean(w http.ResponseWriter, r *http.Request, _ httprouter.Par
h.responseJSON(w, r, 200) h.responseJSON(w, r, 200)
} }
func (h *Handler) middleware(handler httprouter.Handle) httprouter.Handle { func (h *handler) middleware(handler httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
h.logger.Debugf("%s %s", r.Method, r.RequestURI) h.logger.Debugf("%s %s", r.Method, r.RequestURI)
handler(w, r, params) handler(w, r, params)
@ -488,7 +511,7 @@ func insertCache(db *bolthold.Store, cache *Cache) error {
return nil return nil
} }
func (h *Handler) readCache(id uint64) (*Cache, error) { func (h *handler) readCache(id uint64) (*Cache, error) {
db, err := h.openDB() db, err := h.openDB()
if err != nil { if err != nil {
return nil, err return nil, err
@ -501,7 +524,7 @@ func (h *Handler) readCache(id uint64) (*Cache, error) {
return cache, nil return cache, nil
} }
func (h *Handler) useCache(id uint64) error { func (h *handler) useCache(id uint64) error {
db, err := h.openDB() db, err := h.openDB()
if err != nil { if err != nil {
return err return err
@ -522,7 +545,11 @@ const (
keepOld = 5 * time.Minute keepOld = 5 * time.Minute
) )
func (h *Handler) gcCache() { func (h *handler) setgcAt(at time.Time) {
h.gcAt = at
}
func (h *handler) gcCache() {
if h.gcing.Load() { if h.gcing.Load() {
return return
} }
@ -629,7 +656,7 @@ func (h *Handler) gcCache() {
} }
} }
func (h *Handler) responseJSON(w http.ResponseWriter, r *http.Request, code int, v ...any) { func (h *handler) responseJSON(w http.ResponseWriter, r *http.Request, code int, v ...any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
var data []byte var data []byte
if len(v) == 0 || v[0] == nil { if len(v) == 0 || v[0] == nil {

View file

@ -76,8 +76,7 @@ func TestHandler(t *testing.T) {
}) })
t.Run("close", func(t *testing.T) { t.Run("close", func(t *testing.T) {
require.NoError(t, handler.Close()) require.NoError(t, handler.Close())
assert.Nil(t, handler.server) assert.True(t, handler.isClosed())
assert.Nil(t, handler.listener)
_, err := httpClient.Post(fmt.Sprintf("%s/caches/%d", base, 1), "", nil) _, err := httpClient.Post(fmt.Sprintf("%s/caches/%d", base, 1), "", nil)
assert.Error(t, err) assert.Error(t, err)
}) })
@ -983,7 +982,7 @@ func TestHandler_gcCache(t *testing.T) {
} }
require.NoError(t, db.Close()) require.NoError(t, db.Close())
handler.gcAt = time.Time{} // ensure gcCache will not skip handler.setgcAt(time.Time{}) // ensure gcCache will not skip
handler.gcCache() handler.gcCache()
db, err = handler.openDB() db, err = handler.openDB()
@ -1010,8 +1009,7 @@ func TestHandler_ExternalURL(t *testing.T) {
assert.Equal(t, handler.ExternalURL(), "http://127.0.0.1:34567") assert.Equal(t, handler.ExternalURL(), "http://127.0.0.1:34567")
require.NoError(t, handler.Close()) require.NoError(t, handler.Close())
assert.Nil(t, handler.server) assert.True(t, handler.isClosed())
assert.Nil(t, handler.listener)
}) })
t.Run("reports correct URL on IPv6 zero host", func(t *testing.T) { t.Run("reports correct URL on IPv6 zero host", func(t *testing.T) {
@ -1021,8 +1019,7 @@ func TestHandler_ExternalURL(t *testing.T) {
assert.Equal(t, handler.ExternalURL(), "http://[2001:db8::]:34567") assert.Equal(t, handler.ExternalURL(), "http://[2001:db8::]:34567")
require.NoError(t, handler.Close()) require.NoError(t, handler.Close())
assert.Nil(t, handler.server) assert.True(t, handler.isClosed())
assert.Nil(t, handler.listener)
}) })
t.Run("reports correct URL on IPv6", func(t *testing.T) { t.Run("reports correct URL on IPv6", func(t *testing.T) {
@ -1032,7 +1029,6 @@ func TestHandler_ExternalURL(t *testing.T) {
assert.Equal(t, handler.ExternalURL(), "http://[2001:db8::1:2:3:4]:34567") assert.Equal(t, handler.ExternalURL(), "http://[2001:db8::1:2:3:4]:34567")
require.NoError(t, handler.Close()) require.NoError(t, handler.Close())
assert.Nil(t, handler.server) assert.True(t, handler.isClosed())
assert.Nil(t, handler.listener)
}) })
} }

View file

@ -16,7 +16,7 @@ import (
var ErrValidation = errors.New("validation error") var ErrValidation = errors.New("validation error")
func (h *Handler) validateMac(rundata cacheproxy.RunData) (string, error) { func (h *handler) validateMac(rundata cacheproxy.RunData) (string, error) {
// TODO: allow configurable max age // TODO: allow configurable max age
if !validateAge(rundata.Timestamp) { if !validateAge(rundata.Timestamp) {
return "", ErrValidation return "", ErrValidation

View file

@ -10,7 +10,7 @@ import (
) )
func TestMac(t *testing.T) { func TestMac(t *testing.T) {
handler := &Handler{ handler := &handler{
secret: "secret for testing", secret: "secret for testing",
} }

View file

@ -54,7 +54,7 @@ type executeArgs struct {
debug bool debug bool
dryrun bool dryrun bool
image string image string
cacheHandler *artifactcache.Handler cacheHandler artifactcache.Handler
network string network string
enableIPv6 bool enableIPv6 bool
githubInstance string githubInstance string