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 }