github.com/uber/kraken@v0.1.4/lib/backend/testfs/server.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package testfs 15 16 import ( 17 "encoding/json" 18 "fmt" 19 "io" 20 "io/ioutil" 21 "net/http" 22 "os" 23 "path/filepath" 24 "strconv" 25 "strings" 26 "sync" 27 28 "github.com/uber/kraken/utils/handler" 29 30 "github.com/pressly/chi" 31 ) 32 33 // Server provides HTTP endpoints for operating on files on disk. 34 type Server struct { 35 sync.RWMutex 36 dir string 37 } 38 39 // NewServer creates a new Server. 40 func NewServer() *Server { 41 dir, err := ioutil.TempDir("/tmp", "kraken-testfs") 42 if err != nil { 43 panic(err) 44 } 45 return &Server{dir: dir} 46 } 47 48 // Handler returns an HTTP handler for s. 49 func (s *Server) Handler() http.Handler { 50 r := chi.NewRouter() 51 r.Get("/health", s.healthHandler) 52 r.Head("/files/*", handler.Wrap(s.statHandler)) 53 r.Get("/files/*", handler.Wrap(s.downloadHandler)) 54 r.Post("/files/*", handler.Wrap(s.uploadHandler)) 55 r.Get("/list/*", handler.Wrap(s.listHandler)) 56 return r 57 } 58 59 // Cleanup cleans up the underlying directory of s. 60 func (s *Server) Cleanup() { 61 os.RemoveAll(s.dir) 62 } 63 64 func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 65 w.Write([]byte("OK")) 66 } 67 68 func (s *Server) statHandler(w http.ResponseWriter, r *http.Request) error { 69 s.RLock() 70 defer s.RUnlock() 71 72 name := r.URL.Path[len("/files/"):] 73 74 info, err := os.Stat(s.path(name)) 75 if err != nil { 76 if os.IsNotExist(err) { 77 return handler.ErrorStatus(http.StatusNotFound) 78 } 79 return handler.Errorf("file store: %s", err) 80 } 81 w.Header().Add("Size", strconv.FormatInt(info.Size(), 10)) 82 w.WriteHeader(http.StatusOK) 83 return nil 84 } 85 86 func (s *Server) downloadHandler(w http.ResponseWriter, r *http.Request) error { 87 s.RLock() 88 defer s.RUnlock() 89 90 name := r.URL.Path[len("/files/"):] 91 92 f, err := os.Open(s.path(name)) 93 if err != nil { 94 if os.IsNotExist(err) { 95 return handler.ErrorStatus(http.StatusNotFound) 96 } 97 return handler.Errorf("open: %s", err) 98 } 99 if _, err := io.Copy(w, f); err != nil { 100 return handler.Errorf("copy: %s", err) 101 } 102 return nil 103 } 104 105 func (s *Server) uploadHandler(w http.ResponseWriter, r *http.Request) error { 106 s.Lock() 107 defer s.Unlock() 108 109 name := r.URL.Path[len("/files/"):] 110 111 p := s.path(name) 112 if err := os.MkdirAll(filepath.Dir(p), 0775); err != nil { 113 return handler.Errorf("mkdir: %s", err) 114 } 115 f, err := os.Create(p) 116 if err != nil { 117 return handler.Errorf("create: %s", err) 118 } 119 defer f.Close() 120 if _, err := io.Copy(f, r.Body); err != nil { 121 return handler.Errorf("copy: %s", err) 122 } 123 return nil 124 } 125 126 func (s *Server) listHandler(w http.ResponseWriter, r *http.Request) error { 127 s.RLock() 128 defer s.RUnlock() 129 130 prefix := s.path(r.URL.Path[len("/list/"):]) 131 132 var paths []string 133 err := filepath.Walk(prefix, func(path string, info os.FileInfo, err error) error { 134 if err != nil { 135 return err 136 } 137 if info.IsDir() { 138 return nil 139 } 140 if path == prefix { 141 // Handle case where prefix is a file. 142 return nil 143 } 144 path, err = filepath.Rel(s.dir, path) 145 if err != nil { 146 return err 147 } 148 paths = append(paths, path) 149 return nil 150 }) 151 if err != nil { 152 return fmt.Errorf("walk: %s", err) 153 } 154 if err := json.NewEncoder(w).Encode(&paths); err != nil { 155 return handler.Errorf("json encode: %s", err) 156 } 157 return nil 158 } 159 160 // path normalizes some file or directory entry into a path. 161 func (s *Server) path(entry string) string { 162 // Allows listing tags by repo. 163 entry = strings.Replace(entry, ":", "/", -1) 164 return filepath.Join(s.dir, entry) 165 }