github.com/apcera/util@v0.0.0-20180322191801-7a50bc84ee48/dockertest/v2/mock_v2_registry.go (about)

     1  // Copyright 2015 Apcera Inc. All rights reserved.
     2  
     3  package v2
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"sync"
    12  
    13  	"github.com/gorilla/mux"
    14  )
    15  
    16  var (
    17  	testVerbose    = false // Change to true in order to see HTTP requests in test output.
    18  	testHttpServer *httptest.Server
    19  	mu             sync.Mutex
    20  
    21  	// skipAuth skips sending authorization challenges entirely.
    22  	skipAuth bool
    23  
    24  	// Note: currently does not support supplying signed manifests.
    25  	testImageManifests = map[string]string{
    26  		"library/nats:latest":   libraryNatsLatestManifest,
    27  		"library/foobar:latest": libraryFoobarLatestManifest,
    28  	}
    29  )
    30  
    31  func RunMockRegistry() *httptest.Server {
    32  	mu.Lock()
    33  	defer mu.Unlock()
    34  
    35  	if testHttpServer != nil {
    36  		return testHttpServer
    37  	}
    38  
    39  	r := mux.NewRouter()
    40  
    41  	r.HandleFunc("/token", handlerToken).Methods("GET")
    42  	r.HandleFunc("/v2/", handlerSupport).Methods("GET")
    43  	r.HandleFunc("/v2/{repo:[^/]+}/{image_name:[^/]+}/manifests/{image_ref:[^/]+}", handlerImageManifest).Methods("GET")
    44  	r.HandleFunc("/v2/{repo:[^/]+}/{image_name:[^/]+}/blobs/{blob_ref:[^/]+}", handlerBlob).Methods("GET")
    45  
    46  	testHttpServer = httptest.NewServer(logHandler(r))
    47  	return testHttpServer
    48  }
    49  
    50  // SetSkipAuth allows for configuring the mock registry to not send auth
    51  // challenges.
    52  func SetSkipAuth(enabled bool) {
    53  	mu.Lock()
    54  	defer mu.Unlock()
    55  	skipAuth = enabled
    56  }
    57  
    58  func logHandler(handler http.Handler) http.Handler {
    59  	if !testVerbose {
    60  		return handler
    61  	}
    62  	lh := func(w http.ResponseWriter, r *http.Request) {
    63  		fmt.Printf("%s \"%s %s\"\n", r.RemoteAddr, r.Method, r.URL)
    64  		handler.ServeHTTP(w, r)
    65  	}
    66  	return http.HandlerFunc(lh)
    67  }
    68  
    69  func writeResponse(w http.ResponseWriter, httpStatus int, payload interface{}) {
    70  	w.WriteHeader(httpStatus)
    71  	body, err := json.Marshal(payload)
    72  	if err != nil {
    73  		io.WriteString(w, err.Error())
    74  		return
    75  	}
    76  	w.Write(body)
    77  }
    78  
    79  func checkAuth(w http.ResponseWriter, r *http.Request) bool {
    80  	header := w.Header()
    81  	header.Add("Docker-Distribution-API-Version", "registry/2.0")
    82  
    83  	if skipAuth {
    84  		return true
    85  	}
    86  
    87  	if (len(r.Header.Get("Authorization"))) > 0 {
    88  		return true
    89  	}
    90  
    91  	realm := fmt.Sprintf("http://%s/token", r.Host)
    92  	service := fmt.Sprintf("http://%s", r.Host)
    93  
    94  	// TODO: allow user to specify a scope on the request and have it respected?
    95  	w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Bearer realm=%q,service=%q`, realm, service))
    96  	writeResponse(w, http.StatusUnauthorized, "Bad auth")
    97  	return false
    98  }
    99  
   100  func handlerToken(w http.ResponseWriter, r *http.Request) {
   101  	// Token request requires service and scope; see:
   102  	//
   103  	// https://docs.docker.com/registry/spec/auth/token/
   104  	//
   105  	// Expected form: `service=registry.docker.io&scope=repository:library/nats"`
   106  	query := r.URL.Query()
   107  
   108  	service := query.Get("service")
   109  	scope := query.Get("scope")
   110  
   111  	if service == "" {
   112  		writeResponse(w, http.StatusBadRequest, "bad request: service is required")
   113  	} else if scope == "" {
   114  		writeResponse(w, http.StatusBadRequest, "bad request: scope is required")
   115  	}
   116  
   117  	tokenResponse := `{"token": "Bearer someBearerToken"}`
   118  
   119  	io.WriteString(w, tokenResponse)
   120  }
   121  
   122  func handlerSupport(w http.ResponseWriter, r *http.Request) {
   123  	if !checkAuth(w, r) {
   124  		return
   125  	}
   126  
   127  	w.Write([]byte("v2 API supported!"))
   128  }
   129  
   130  func handlerImageManifest(w http.ResponseWriter, r *http.Request) {
   131  	if !checkAuth(w, r) {
   132  		return
   133  	}
   134  
   135  	vars := mux.Vars(r)
   136  	repo, exists := vars["repo"]
   137  	if !exists {
   138  		http.NotFound(w, r)
   139  		return
   140  	}
   141  
   142  	imageName, exists := vars["image_name"]
   143  	if !exists {
   144  		http.NotFound(w, r)
   145  		return
   146  	}
   147  
   148  	// Tag or digest.
   149  	imageRef, exists := vars["image_ref"]
   150  	if !exists {
   151  		http.NotFound(w, r)
   152  		return
   153  	}
   154  
   155  	manifest, exists := testImageManifests[fmt.Sprintf("%s/%s:%s", repo, imageName, imageRef)]
   156  	if !exists {
   157  		http.NotFound(w, r)
   158  		return
   159  	}
   160  	io.WriteString(w, manifest)
   161  }
   162  
   163  func handlerBlob(w http.ResponseWriter, r *http.Request) {
   164  	if !checkAuth(w, r) {
   165  		return
   166  	}
   167  
   168  	vars := mux.Vars(r)
   169  	_, exists := vars["repo"]
   170  	if !exists {
   171  		http.NotFound(w, r)
   172  		return
   173  	}
   174  
   175  	_, exists = vars["image_name"]
   176  	if !exists {
   177  		http.NotFound(w, r)
   178  		return
   179  	}
   180  
   181  	blobRef, exists := vars["blob_ref"]
   182  	if !exists {
   183  		http.NotFound(w, r)
   184  		return
   185  	}
   186  
   187  	// Just write back the blob reference; completely fake content, not even a tar.
   188  	io.WriteString(w, blobRef)
   189  }