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  }