k8s.io/registry.k8s.io@v0.3.1/cmd/archeio/internal/app/handlers_test.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package app
    18  
    19  import (
    20  	"errors"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"testing"
    24  )
    25  
    26  func TestMakeHandler(t *testing.T) {
    27  	registryConfig := RegistryConfig{
    28  		// the v2 test below tests being redirected to k8s.gcr.io as that one doesn't have UpstreamRegistryPath
    29  		UpstreamRegistryEndpoint: "https://us.gcr.io",
    30  		UpstreamRegistryPath:     "k8s-artifacts-prod",
    31  		InfoURL:                  "https://github.com/kubernetes/k8s.io/tree/main/registry.k8s.io",
    32  		PrivacyURL:               "https://www.linuxfoundation.org/privacy-policy/",
    33  	}
    34  	handler := MakeHandler(registryConfig)
    35  	testCases := []struct {
    36  		Name           string
    37  		Request        *http.Request
    38  		ExpectedStatus int
    39  		ExpectedURL    string
    40  	}{
    41  		{
    42  			Name:           "/",
    43  			Request:        httptest.NewRequest("GET", "http://localhost:8080/", nil),
    44  			ExpectedStatus: http.StatusTemporaryRedirect,
    45  			ExpectedURL:    registryConfig.InfoURL,
    46  		},
    47  		{
    48  			Name:           "/privacy",
    49  			Request:        httptest.NewRequest("GET", "http://localhost:8080/privacy", nil),
    50  			ExpectedStatus: http.StatusTemporaryRedirect,
    51  			ExpectedURL:    registryConfig.PrivacyURL,
    52  		},
    53  		{
    54  			Name:           "/v3/",
    55  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v3/", nil),
    56  			ExpectedStatus: http.StatusNotFound,
    57  		},
    58  		{
    59  			Name:           "/v2/",
    60  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2/", nil),
    61  			ExpectedStatus: http.StatusOK,
    62  		},
    63  		{
    64  			Name:           "/v2",
    65  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2", nil),
    66  			ExpectedStatus: http.StatusOK,
    67  		},
    68  		{
    69  			Name:           "/v2",
    70  			Request:        httptest.NewRequest("HEAD", "http://localhost:8080/v2", nil),
    71  			ExpectedStatus: http.StatusOK,
    72  		},
    73  		{
    74  			Name:           "/v2/",
    75  			Request:        httptest.NewRequest("POST", "http://localhost:8080/v2/", nil),
    76  			ExpectedStatus: http.StatusMethodNotAllowed,
    77  		},
    78  		{
    79  			Name:           "/v2/pause/manifests/latest",
    80  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2/pause/manifests/latest", nil),
    81  			ExpectedStatus: http.StatusTemporaryRedirect,
    82  			ExpectedURL:    "https://us.gcr.io/v2/k8s-artifacts-prod/pause/manifests/latest",
    83  		},
    84  		{
    85  			Name:           "/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
    86  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", nil),
    87  			ExpectedStatus: http.StatusTemporaryRedirect,
    88  			ExpectedURL:    "https://us.gcr.io/v2/k8s-artifacts-prod/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
    89  		},
    90  		{
    91  			Name: "AWS IP, /v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
    92  			Request: func() *http.Request {
    93  				r := httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", nil)
    94  				r.RemoteAddr = "35.180.1.1:888"
    95  				return r
    96  			}(),
    97  			ExpectedStatus: http.StatusTemporaryRedirect,
    98  			ExpectedURL:    "https://prod-registry-k8s-io-eu-west-1.s3.dualstack.eu-west-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
    99  		},
   100  		{
   101  			Name: "GCP IP, /v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   102  			Request: func() *http.Request {
   103  				r := httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", nil)
   104  				r.RemoteAddr = "35.220.26.1:888"
   105  				return r
   106  			}(),
   107  			ExpectedStatus: http.StatusTemporaryRedirect,
   108  			ExpectedURL:    "https://us.gcr.io/v2/k8s-artifacts-prod/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   109  		},
   110  	}
   111  	for i := range testCases {
   112  		tc := testCases[i]
   113  		t.Run(tc.Name, func(t *testing.T) {
   114  			t.Parallel()
   115  			recorder := httptest.NewRecorder()
   116  			handler.ServeHTTP(recorder, tc.Request)
   117  			response := recorder.Result()
   118  			if response == nil {
   119  				t.Fatalf("nil response")
   120  			}
   121  			if response.StatusCode != tc.ExpectedStatus {
   122  				t.Fatalf(
   123  					"expected status: %v, but got status: %v",
   124  					http.StatusText(tc.ExpectedStatus),
   125  					http.StatusText(response.StatusCode),
   126  				)
   127  			}
   128  			location, err := response.Location()
   129  			if err != nil {
   130  				if !errors.Is(err, http.ErrNoLocation) {
   131  					t.Fatalf("failed to get response location with error: %v", err)
   132  				} else if tc.ExpectedURL != "" {
   133  					t.Fatalf("expected url: %q but no location was available", tc.ExpectedURL)
   134  				}
   135  			} else if location.String() != tc.ExpectedURL {
   136  				t.Fatalf(
   137  					"expected url: %q, but got: %q",
   138  					tc.ExpectedURL,
   139  					location,
   140  				)
   141  			}
   142  		})
   143  	}
   144  }
   145  
   146  type fakeBlobsChecker struct {
   147  	knownURLs map[string]bool
   148  }
   149  
   150  func (f *fakeBlobsChecker) BlobExists(blobURL string) bool {
   151  	return f.knownURLs[blobURL]
   152  }
   153  
   154  func TestMakeV2Handler(t *testing.T) {
   155  	registryConfig := RegistryConfig{
   156  		UpstreamRegistryEndpoint: "https://k8s.gcr.io",
   157  		UpstreamRegistryPath:     "",
   158  		InfoURL:                  "https://github.com/kubernetes/k8s.io/tree/main/registry.k8s.io",
   159  		PrivacyURL:               "https://www.linuxfoundation.org/privacy-policy/",
   160  	}
   161  	blobs := fakeBlobsChecker{
   162  		knownURLs: map[string]bool{
   163  			"https://prod-registry-k8s-io-ap-south-1.s3.dualstack.ap-south-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e":         true,
   164  			"https://prod-registry-k8s-io-ap-southeast-1.s3.dualstack.ap-southeast-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e": true,
   165  			"https://prod-registry-k8s-io-eu-central-1.s3.dualstack.eu-central-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e":     true,
   166  			"https://prod-registry-k8s-io-eu-west-1.s3.dualstack.eu-west-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e":           true,
   167  			"https://prod-registry-k8s-io-us-east-1.s3.dualstack.us-east-2.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e":           true,
   168  			"https://prod-registry-k8s-io-us-east-2.s3.dualstack.us-east-2.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e":           true,
   169  			"https://prod-registry-k8s-io-us-west-1.s3.dualstack.us-west-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e":           true,
   170  		},
   171  	}
   172  	handler := makeV2Handler(registryConfig, &blobs)
   173  	testCases := []struct {
   174  		Name           string
   175  		Request        *http.Request
   176  		ExpectedStatus int
   177  		ExpectedURL    string
   178  	}{
   179  		{
   180  			Name:           "/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   181  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", nil),
   182  			ExpectedStatus: http.StatusTemporaryRedirect,
   183  			ExpectedURL:    "https://k8s.gcr.io/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   184  		},
   185  		{
   186  			// future-proofing tests for other digest algorithms, even though we only have sha256 content as of March 2023
   187  			Name:           "/v2/pause/blobs/sha512:3b0998121425143be7164ea1555efbdf5b8a02ceedaa26e01910e7d017ff78ddbba27877bd42510a06cc14ac1bc6c451128ca3f0d0afba28b695e29b2702c9c7",
   188  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:3b0998121425143be7164ea1555efbdf5b8a02ceedaa26e01910e7d017ff78ddbba27877bd42510a06cc14ac1bc6c451128ca3f0d0afba28b695e29b2702c9c7", nil),
   189  			ExpectedStatus: http.StatusTemporaryRedirect,
   190  			ExpectedURL:    "https://k8s.gcr.io/v2/pause/blobs/sha256:3b0998121425143be7164ea1555efbdf5b8a02ceedaa26e01910e7d017ff78ddbba27877bd42510a06cc14ac1bc6c451128ca3f0d0afba28b695e29b2702c9c7",
   191  		},
   192  		{
   193  			Name: "Somehow bogus remote addr, /v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   194  			Request: func() *http.Request {
   195  				r := httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", nil)
   196  				r.RemoteAddr = "35.180.1.1asdfasdfsd:888"
   197  				return r
   198  			}(),
   199  			// NOTE: this one really shouldn't happen, but we want full test coverage
   200  			// This should only happen with a bug in the stdlib http server ...
   201  			ExpectedStatus: http.StatusBadRequest,
   202  		},
   203  		{
   204  			Name: "/v2/_catalog",
   205  			Request: func() *http.Request {
   206  				r := httptest.NewRequest("GET", "http://localhost:8080/v2/_catalog", nil)
   207  				r.RemoteAddr = "35.180.1.1:888"
   208  				return r
   209  			}(),
   210  			ExpectedStatus: http.StatusNotFound,
   211  		},
   212  		{
   213  			Name: "AWS IP, /v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   214  			Request: func() *http.Request {
   215  				r := httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", nil)
   216  				r.RemoteAddr = "35.180.1.1:888"
   217  				return r
   218  			}(),
   219  			ExpectedStatus: http.StatusTemporaryRedirect,
   220  			ExpectedURL:    "https://prod-registry-k8s-io-eu-west-1.s3.dualstack.eu-west-1.amazonaws.com/containers/images/sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e",
   221  		},
   222  		{
   223  			Name:           "AWS IP, /v2/pause/manifests/latest",
   224  			Request:        httptest.NewRequest("GET", "http://localhost:8080/v2/pause/manifests/latest", nil),
   225  			ExpectedStatus: http.StatusTemporaryRedirect,
   226  			ExpectedURL:    "https://k8s.gcr.io/v2/pause/manifests/latest",
   227  		},
   228  		{
   229  			Name: "AWS IP, /v2/pause/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1234567",
   230  			Request: func() *http.Request {
   231  				r := httptest.NewRequest("GET", "http://localhost:8080/v2/pause/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1234567", nil)
   232  				r.RemoteAddr = "35.180.1.1:888"
   233  				return r
   234  			}(),
   235  			ExpectedStatus: http.StatusTemporaryRedirect,
   236  			ExpectedURL:    "https://k8s.gcr.io/v2/pause/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1234567",
   237  		},
   238  	}
   239  	for i := range testCases {
   240  		tc := testCases[i]
   241  		t.Run(tc.Name, func(t *testing.T) {
   242  			t.Parallel()
   243  			recorder := httptest.NewRecorder()
   244  			handler(recorder, tc.Request)
   245  			response := recorder.Result()
   246  			if response == nil {
   247  				t.Fatalf("nil response")
   248  			}
   249  			if response.StatusCode != tc.ExpectedStatus {
   250  				t.Fatalf(
   251  					"expected status: %v, but got status: %v",
   252  					http.StatusText(tc.ExpectedStatus),
   253  					http.StatusText(response.StatusCode),
   254  				)
   255  			}
   256  			location, err := response.Location()
   257  			if err != nil {
   258  				if !errors.Is(err, http.ErrNoLocation) {
   259  					t.Fatalf("failed to get response location with error: %v", err)
   260  				} else if tc.ExpectedURL != "" {
   261  					t.Fatalf("expected url: %q but no location was available", tc.ExpectedURL)
   262  				}
   263  			} else if location.String() != tc.ExpectedURL {
   264  				t.Fatalf(
   265  					"expected url: %q, but got: %q",
   266  					tc.ExpectedURL,
   267  					location,
   268  				)
   269  			}
   270  		})
   271  	}
   272  }