lib/backend/testfs/server.go (124 lines of code) (raw):
// Copyright (c) 2016-2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testfs
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/uber/kraken/utils/handler"
"github.com/go-chi/chi"
)
// Server provides HTTP endpoints for operating on files on disk.
type Server struct {
sync.RWMutex
dir string
}
// NewServer creates a new Server.
func NewServer() *Server {
dir, err := ioutil.TempDir("/tmp", "kraken-testfs")
if err != nil {
panic(err)
}
return &Server{dir: dir}
}
// Handler returns an HTTP handler for s.
func (s *Server) Handler() http.Handler {
r := chi.NewRouter()
r.Get("/health", s.healthHandler)
r.Head("/files/*", handler.Wrap(s.statHandler))
r.Get("/files/*", handler.Wrap(s.downloadHandler))
r.Post("/files/*", handler.Wrap(s.uploadHandler))
r.Get("/list/*", handler.Wrap(s.listHandler))
return r
}
// Cleanup cleans up the underlying directory of s.
func (s *Server) Cleanup() {
os.RemoveAll(s.dir)
}
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
}
func (s *Server) statHandler(w http.ResponseWriter, r *http.Request) error {
s.RLock()
defer s.RUnlock()
name := r.URL.Path[len("/files/"):]
info, err := os.Stat(s.path(name))
if err != nil {
if os.IsNotExist(err) {
return handler.ErrorStatus(http.StatusNotFound)
}
return handler.Errorf("file store: %s", err)
}
w.Header().Add("Size", strconv.FormatInt(info.Size(), 10))
w.WriteHeader(http.StatusOK)
return nil
}
func (s *Server) downloadHandler(w http.ResponseWriter, r *http.Request) error {
s.RLock()
defer s.RUnlock()
name := r.URL.Path[len("/files/"):]
f, err := os.Open(s.path(name))
if err != nil {
if os.IsNotExist(err) {
return handler.ErrorStatus(http.StatusNotFound)
}
return handler.Errorf("open: %s", err)
}
if _, err := io.Copy(w, f); err != nil {
return handler.Errorf("copy: %s", err)
}
return nil
}
func (s *Server) uploadHandler(w http.ResponseWriter, r *http.Request) error {
s.Lock()
defer s.Unlock()
name := r.URL.Path[len("/files/"):]
p := s.path(name)
if err := os.MkdirAll(filepath.Dir(p), 0775); err != nil {
return handler.Errorf("mkdir: %s", err)
}
f, err := os.Create(p)
if err != nil {
return handler.Errorf("create: %s", err)
}
defer f.Close()
if _, err := io.Copy(f, r.Body); err != nil {
return handler.Errorf("copy: %s", err)
}
return nil
}
func (s *Server) listHandler(w http.ResponseWriter, r *http.Request) error {
s.RLock()
defer s.RUnlock()
prefix := s.path(r.URL.Path[len("/list/"):])
var paths []string
err := filepath.Walk(prefix, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if path == prefix {
// Handle case where prefix is a file.
return nil
}
path, err = filepath.Rel(s.dir, path)
if err != nil {
return err
}
paths = append(paths, path)
return nil
})
if err != nil {
return fmt.Errorf("walk: %s", err)
}
if err := json.NewEncoder(w).Encode(&paths); err != nil {
return handler.Errorf("json encode: %s", err)
}
return nil
}
// path normalizes some file or directory entry into a path.
func (s *Server) path(entry string) string {
// Allows listing tags by repo.
entry = strings.Replace(entry, ":", "/", -1)
return filepath.Join(s.dir, entry)
}