github.com/uber/kraken@v0.1.4/proxy/registryoverride/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 registryoverride 15 16 import ( 17 "encoding/json" 18 "fmt" 19 "io" 20 "net/http" 21 "net/url" 22 "strconv" 23 "strings" 24 25 "github.com/pressly/chi" 26 "github.com/uber/kraken/build-index/tagclient" 27 "github.com/uber/kraken/utils/handler" 28 "github.com/uber/kraken/utils/listener" 29 "github.com/uber/kraken/utils/log" 30 "github.com/uber/kraken/utils/stringset" 31 ) 32 33 // Server overrides Docker registry endpoints. 34 type Server struct { 35 config Config 36 tagClient tagclient.Client 37 } 38 39 // NewServer creates a new Server. 40 func NewServer(config Config, tagClient tagclient.Client) *Server { 41 return &Server{config, tagClient} 42 } 43 44 // Handler returns a handler for s. 45 func (s *Server) Handler() http.Handler { 46 r := chi.NewRouter() 47 r.Get("/v2/_catalog", handler.Wrap(s.catalogHandler)) 48 return r 49 } 50 51 // ListenAndServe is a blocking call which runs s. 52 func (s *Server) ListenAndServe() error { 53 log.Infof("Starting registry override server on %s", s.config.Listener) 54 return listener.Serve(s.config.Listener, s.Handler()) 55 } 56 57 type catalogResponse struct { 58 Repositories []string `json:"repositories"` 59 } 60 61 // catalogHandler handles catalog request. 62 // https://docs.docker.com/registry/spec/api/#pagination for more reference. 63 func (s *Server) catalogHandler(w http.ResponseWriter, r *http.Request) error { 64 limitQ := "n" 65 offsetQ := "last" 66 67 // Build request for ListWithPagination. 68 var filter tagclient.ListFilter 69 u := r.URL 70 q := u.Query() 71 for k, v := range q { 72 if len(v) != 1 { 73 return handler.Errorf( 74 "invalid query %s:%s", k, v).Status(http.StatusBadRequest) 75 } 76 switch k { 77 case limitQ: 78 limitCount, err := strconv.Atoi(v[0]) 79 if err != nil { 80 return handler.Errorf( 81 "invalid limit %s: %s", v, err).Status(http.StatusBadRequest) 82 } 83 if limitCount == 0 { 84 return handler.Errorf( 85 "invalid limit %d", limitCount).Status(http.StatusBadRequest) 86 } 87 filter.Limit = limitCount 88 case offsetQ: 89 filter.Offset = v[0] 90 default: 91 return handler.Errorf("invalid query %s", k).Status(http.StatusBadRequest) 92 } 93 } 94 95 // List with pagination. 96 listResp, err := s.tagClient.ListWithPagination("", filter) 97 if err != nil { 98 return handler.Errorf("list: %s", err) 99 } 100 repos := stringset.New() 101 for _, tag := range listResp.Result { 102 parts := strings.Split(tag, ":") 103 if len(parts) != 2 { 104 log.With("tag", tag).Errorf("Invalid tag format, expected repo:tag") 105 continue 106 } 107 repos.Add(parts[0]) 108 } 109 110 // Build Link for response. 111 offset, err := listResp.GetOffset() 112 if err != nil && err != io.EOF { 113 return handler.Errorf("invalid offset %s", err) 114 } 115 if offset != "" { 116 nextUrl, err := url.Parse(u.String()) 117 if err != nil { 118 return handler.Errorf( 119 "invalid url string: %s", err).Status(http.StatusBadRequest) 120 } 121 val, err := url.ParseQuery(nextUrl.RawQuery) 122 if err != nil { 123 return handler.Errorf( 124 "invalid url string: %s", err).Status(http.StatusBadRequest) 125 } 126 val.Set(offsetQ, offset) 127 nextUrl.RawQuery = val.Encode() 128 129 // Set header (https://docs.docker.com/registry/spec/api/#pagination), 130 // except the host and scheme. 131 // Link: <<url>?n=2&last=b>; rel="next" 132 w.Header().Set("Link", fmt.Sprintf("%s; rel=\"next\"", nextUrl.String())) 133 } 134 135 resp := catalogResponse{Repositories: repos.ToSlice()} 136 if err := json.NewEncoder(w).Encode(&resp); err != nil { 137 return handler.Errorf("json encode: %s", err) 138 } 139 return nil 140 }