github.com/resin-io/docker@v1.13.1/registry/registry_mock_test.go (about)

     1  // +build !solaris
     2  
     3  package registry
     4  
     5  import (
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"net/url"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	registrytypes "github.com/docker/docker/api/types/registry"
    21  	"github.com/docker/docker/reference"
    22  	"github.com/gorilla/mux"
    23  
    24  	"github.com/Sirupsen/logrus"
    25  )
    26  
    27  var (
    28  	testHTTPServer  *httptest.Server
    29  	testHTTPSServer *httptest.Server
    30  	testLayers      = map[string]map[string]string{
    31  		"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
    32  			"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
    33  				"comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00",
    34  				"container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
    35  				"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
    36  				"Tty":false,"OpenStdin":false,"StdinOnce":false,
    37  				"Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
    38  				"VolumesFrom":"","Entrypoint":null},"Size":424242}`,
    39  			"checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
    40  			"checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c",
    41  			"ancestry":        `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
    42  			"layer": string([]byte{
    43  				0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
    44  				0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
    45  				0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66,
    46  				0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78,
    47  				0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31,
    48  				0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8,
    49  				0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1,
    50  				0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6,
    51  				0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb,
    52  				0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce,
    53  				0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00,
    54  			}),
    55  		},
    56  		"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": {
    57  			"json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
    58  				"parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
    59  				"comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00",
    60  				"container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
    61  				"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
    62  				"Tty":false,"OpenStdin":false,"StdinOnce":false,
    63  				"Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
    64  				"VolumesFrom":"","Entrypoint":null},"Size":424242}`,
    65  			"checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
    66  			"checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2",
    67  			"ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
    68  				"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
    69  			"layer": string([]byte{
    70  				0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
    71  				0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
    72  				0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56,
    73  				0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5,
    74  				0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e,
    75  				0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93,
    76  				0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee,
    77  				0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9,
    78  				0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55,
    79  				0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17,
    80  				0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00,
    81  			}),
    82  		},
    83  	}
    84  	testRepositories = map[string]map[string]string{
    85  		"foo42/bar": {
    86  			"latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
    87  			"test":   "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
    88  		},
    89  	}
    90  	mockHosts = map[string][]net.IP{
    91  		"":            {net.ParseIP("0.0.0.0")},
    92  		"localhost":   {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
    93  		"example.com": {net.ParseIP("42.42.42.42")},
    94  		"other.com":   {net.ParseIP("43.43.43.43")},
    95  	}
    96  )
    97  
    98  func init() {
    99  	r := mux.NewRouter()
   100  
   101  	// /v1/
   102  	r.HandleFunc("/v1/_ping", handlerGetPing).Methods("GET")
   103  	r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods("GET")
   104  	r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods("PUT")
   105  	r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods("GET", "DELETE")
   106  	r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods("GET")
   107  	r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods("PUT")
   108  	r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods("GET", "POST", "PUT")
   109  	r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods("GET", "PUT", "DELETE")
   110  	r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT")
   111  	r.HandleFunc("/v1/search", handlerSearch).Methods("GET")
   112  
   113  	// /v2/
   114  	r.HandleFunc("/v2/version", handlerGetPing).Methods("GET")
   115  
   116  	testHTTPServer = httptest.NewServer(handlerAccessLog(r))
   117  	testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r))
   118  
   119  	// override net.LookupIP
   120  	lookupIP = func(host string) ([]net.IP, error) {
   121  		if host == "127.0.0.1" {
   122  			// I believe in future Go versions this will fail, so let's fix it later
   123  			return net.LookupIP(host)
   124  		}
   125  		for h, addrs := range mockHosts {
   126  			if host == h {
   127  				return addrs, nil
   128  			}
   129  			for _, addr := range addrs {
   130  				if addr.String() == host {
   131  					return []net.IP{addr}, nil
   132  				}
   133  			}
   134  		}
   135  		return nil, errors.New("lookup: no such host")
   136  	}
   137  }
   138  
   139  func handlerAccessLog(handler http.Handler) http.Handler {
   140  	logHandler := func(w http.ResponseWriter, r *http.Request) {
   141  		logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL)
   142  		handler.ServeHTTP(w, r)
   143  	}
   144  	return http.HandlerFunc(logHandler)
   145  }
   146  
   147  func makeURL(req string) string {
   148  	return testHTTPServer.URL + req
   149  }
   150  
   151  func makeHTTPSURL(req string) string {
   152  	return testHTTPSServer.URL + req
   153  }
   154  
   155  func makeIndex(req string) *registrytypes.IndexInfo {
   156  	index := &registrytypes.IndexInfo{
   157  		Name: makeURL(req),
   158  	}
   159  	return index
   160  }
   161  
   162  func makeHTTPSIndex(req string) *registrytypes.IndexInfo {
   163  	index := &registrytypes.IndexInfo{
   164  		Name: makeHTTPSURL(req),
   165  	}
   166  	return index
   167  }
   168  
   169  func makePublicIndex() *registrytypes.IndexInfo {
   170  	index := &registrytypes.IndexInfo{
   171  		Name:     IndexServer,
   172  		Secure:   true,
   173  		Official: true,
   174  	}
   175  	return index
   176  }
   177  
   178  func makeServiceConfig(mirrors []string, insecureRegistries []string) *serviceConfig {
   179  	options := ServiceOptions{
   180  		Mirrors:            mirrors,
   181  		InsecureRegistries: insecureRegistries,
   182  	}
   183  
   184  	return newServiceConfig(options)
   185  }
   186  
   187  func writeHeaders(w http.ResponseWriter) {
   188  	h := w.Header()
   189  	h.Add("Server", "docker-tests/mock")
   190  	h.Add("Expires", "-1")
   191  	h.Add("Content-Type", "application/json")
   192  	h.Add("Pragma", "no-cache")
   193  	h.Add("Cache-Control", "no-cache")
   194  	h.Add("X-Docker-Registry-Version", "0.0.0")
   195  	h.Add("X-Docker-Registry-Config", "mock")
   196  }
   197  
   198  func writeResponse(w http.ResponseWriter, message interface{}, code int) {
   199  	writeHeaders(w)
   200  	w.WriteHeader(code)
   201  	body, err := json.Marshal(message)
   202  	if err != nil {
   203  		io.WriteString(w, err.Error())
   204  		return
   205  	}
   206  	w.Write(body)
   207  }
   208  
   209  func readJSON(r *http.Request, dest interface{}) error {
   210  	body, err := ioutil.ReadAll(r.Body)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	return json.Unmarshal(body, dest)
   215  }
   216  
   217  func apiError(w http.ResponseWriter, message string, code int) {
   218  	body := map[string]string{
   219  		"error": message,
   220  	}
   221  	writeResponse(w, body, code)
   222  }
   223  
   224  func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
   225  	if a == b {
   226  		return
   227  	}
   228  	if len(message) == 0 {
   229  		message = fmt.Sprintf("%v != %v", a, b)
   230  	}
   231  	t.Fatal(message)
   232  }
   233  
   234  func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
   235  	if a != b {
   236  		return
   237  	}
   238  	if len(message) == 0 {
   239  		message = fmt.Sprintf("%v == %v", a, b)
   240  	}
   241  	t.Fatal(message)
   242  }
   243  
   244  // Similar to assertEqual, but does not stop test
   245  func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
   246  	if a == b {
   247  		return
   248  	}
   249  	message := fmt.Sprintf("%v != %v", a, b)
   250  	if len(messagePrefix) != 0 {
   251  		message = messagePrefix + ": " + message
   252  	}
   253  	t.Error(message)
   254  }
   255  
   256  // Similar to assertNotEqual, but does not stop test
   257  func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
   258  	if a != b {
   259  		return
   260  	}
   261  	message := fmt.Sprintf("%v == %v", a, b)
   262  	if len(messagePrefix) != 0 {
   263  		message = messagePrefix + ": " + message
   264  	}
   265  	t.Error(message)
   266  }
   267  
   268  func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
   269  	writeCookie := func() {
   270  		value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
   271  		cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600}
   272  		http.SetCookie(w, cookie)
   273  		//FIXME(sam): this should be sent only on Index routes
   274  		value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano())
   275  		w.Header().Add("X-Docker-Token", value)
   276  	}
   277  	if len(r.Cookies()) > 0 {
   278  		writeCookie()
   279  		return true
   280  	}
   281  	if len(r.Header.Get("Authorization")) > 0 {
   282  		writeCookie()
   283  		return true
   284  	}
   285  	w.Header().Add("WWW-Authenticate", "token")
   286  	apiError(w, "Wrong auth", 401)
   287  	return false
   288  }
   289  
   290  func handlerGetPing(w http.ResponseWriter, r *http.Request) {
   291  	writeResponse(w, true, 200)
   292  }
   293  
   294  func handlerGetImage(w http.ResponseWriter, r *http.Request) {
   295  	if !requiresAuth(w, r) {
   296  		return
   297  	}
   298  	vars := mux.Vars(r)
   299  	layer, exists := testLayers[vars["image_id"]]
   300  	if !exists {
   301  		http.NotFound(w, r)
   302  		return
   303  	}
   304  	writeHeaders(w)
   305  	layerSize := len(layer["layer"])
   306  	w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize))
   307  	io.WriteString(w, layer[vars["action"]])
   308  }
   309  
   310  func handlerPutImage(w http.ResponseWriter, r *http.Request) {
   311  	if !requiresAuth(w, r) {
   312  		return
   313  	}
   314  	vars := mux.Vars(r)
   315  	imageID := vars["image_id"]
   316  	action := vars["action"]
   317  	layer, exists := testLayers[imageID]
   318  	if !exists {
   319  		if action != "json" {
   320  			http.NotFound(w, r)
   321  			return
   322  		}
   323  		layer = make(map[string]string)
   324  		testLayers[imageID] = layer
   325  	}
   326  	if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" {
   327  		if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] {
   328  			apiError(w, "Wrong checksum", 400)
   329  			return
   330  		}
   331  	}
   332  	body, err := ioutil.ReadAll(r.Body)
   333  	if err != nil {
   334  		apiError(w, fmt.Sprintf("Error: %s", err), 500)
   335  		return
   336  	}
   337  	layer[action] = string(body)
   338  	writeResponse(w, true, 200)
   339  }
   340  
   341  func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
   342  	if !requiresAuth(w, r) {
   343  		return
   344  	}
   345  	repositoryName, err := reference.WithName(mux.Vars(r)["repository"])
   346  	if err != nil {
   347  		apiError(w, "Could not parse repository", 400)
   348  		return
   349  	}
   350  	tags, exists := testRepositories[repositoryName.String()]
   351  	if !exists {
   352  		apiError(w, "Repository not found", 404)
   353  		return
   354  	}
   355  	if r.Method == "DELETE" {
   356  		delete(testRepositories, repositoryName.String())
   357  		writeResponse(w, true, 200)
   358  		return
   359  	}
   360  	writeResponse(w, tags, 200)
   361  }
   362  
   363  func handlerGetTag(w http.ResponseWriter, r *http.Request) {
   364  	if !requiresAuth(w, r) {
   365  		return
   366  	}
   367  	vars := mux.Vars(r)
   368  	repositoryName, err := reference.WithName(vars["repository"])
   369  	if err != nil {
   370  		apiError(w, "Could not parse repository", 400)
   371  		return
   372  	}
   373  	tagName := vars["tag"]
   374  	tags, exists := testRepositories[repositoryName.String()]
   375  	if !exists {
   376  		apiError(w, "Repository not found", 404)
   377  		return
   378  	}
   379  	tag, exists := tags[tagName]
   380  	if !exists {
   381  		apiError(w, "Tag not found", 404)
   382  		return
   383  	}
   384  	writeResponse(w, tag, 200)
   385  }
   386  
   387  func handlerPutTag(w http.ResponseWriter, r *http.Request) {
   388  	if !requiresAuth(w, r) {
   389  		return
   390  	}
   391  	vars := mux.Vars(r)
   392  	repositoryName, err := reference.WithName(vars["repository"])
   393  	if err != nil {
   394  		apiError(w, "Could not parse repository", 400)
   395  		return
   396  	}
   397  	tagName := vars["tag"]
   398  	tags, exists := testRepositories[repositoryName.String()]
   399  	if !exists {
   400  		tags = make(map[string]string)
   401  		testRepositories[repositoryName.String()] = tags
   402  	}
   403  	tagValue := ""
   404  	readJSON(r, tagValue)
   405  	tags[tagName] = tagValue
   406  	writeResponse(w, true, 200)
   407  }
   408  
   409  func handlerUsers(w http.ResponseWriter, r *http.Request) {
   410  	code := 200
   411  	if r.Method == "POST" {
   412  		code = 201
   413  	} else if r.Method == "PUT" {
   414  		code = 204
   415  	}
   416  	writeResponse(w, "", code)
   417  }
   418  
   419  func handlerImages(w http.ResponseWriter, r *http.Request) {
   420  	u, _ := url.Parse(testHTTPServer.URL)
   421  	w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s 	,  %s ", u.Host, "test.example.com"))
   422  	w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()))
   423  	if r.Method == "PUT" {
   424  		if strings.HasSuffix(r.URL.Path, "images") {
   425  			writeResponse(w, "", 204)
   426  			return
   427  		}
   428  		writeResponse(w, "", 200)
   429  		return
   430  	}
   431  	if r.Method == "DELETE" {
   432  		writeResponse(w, "", 204)
   433  		return
   434  	}
   435  	images := []map[string]string{}
   436  	for imageID, layer := range testLayers {
   437  		image := make(map[string]string)
   438  		image["id"] = imageID
   439  		image["checksum"] = layer["checksum_tarsum"]
   440  		image["Tag"] = "latest"
   441  		images = append(images, image)
   442  	}
   443  	writeResponse(w, images, 200)
   444  }
   445  
   446  func handlerAuth(w http.ResponseWriter, r *http.Request) {
   447  	writeResponse(w, "OK", 200)
   448  }
   449  
   450  func handlerSearch(w http.ResponseWriter, r *http.Request) {
   451  	result := &registrytypes.SearchResults{
   452  		Query:      "fakequery",
   453  		NumResults: 1,
   454  		Results:    []registrytypes.SearchResult{{Name: "fakeimage", StarCount: 42}},
   455  	}
   456  	writeResponse(w, result, 200)
   457  }
   458  
   459  func TestPing(t *testing.T) {
   460  	res, err := http.Get(makeURL("/v1/_ping"))
   461  	if err != nil {
   462  		t.Fatal(err)
   463  	}
   464  	assertEqual(t, res.StatusCode, 200, "")
   465  	assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock",
   466  		"This is not a Mocked Registry")
   467  }
   468  
   469  /* Uncomment this to test Mocked Registry locally with curl
   470   * WARNING: Don't push on the repos uncommented, it'll block the tests
   471   *
   472  func TestWait(t *testing.T) {
   473  	logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL)
   474  	c := make(chan int)
   475  	<-c
   476  }
   477  
   478  //*/