cuelang.org/go@v0.10.1/internal/mod/modresolve/resolve_test.go (about) 1 package modresolve 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "strings" 7 "testing" 8 9 "cuelang.org/go/cue" 10 "cuelang.org/go/cue/cuecontext" 11 "github.com/go-quicktest/qt" 12 ) 13 14 func TestRegistryConfigSchema(t *testing.T) { 15 schema := RegistryConfigSchema() 16 // Sanity check that it parses OK as CUE and can 17 // validate a legitimate schema. 18 ctx := cuecontext.New() 19 v := ctx.CompileString(schema) 20 fileSchema := v.LookupPath(cue.MakePath(cue.Def("#file"))) 21 qt.Assert(t, qt.IsNil(fileSchema.Err())) 22 cfgVal := ctx.CompileString(`defaultRegistry: registry: "something.example"`) 23 qt.Assert(t, qt.IsNil(cfgVal.Err())) 24 cfgVal = cfgVal.Unify(fileSchema) 25 qt.Assert(t, qt.IsNil(cfgVal.Err())) 26 } 27 28 func TestParseCUERegistry(t *testing.T) { 29 testCases := []struct { 30 testName string 31 in string 32 catchAllDefault string 33 err string 34 wantAllHosts []Host 35 lookups map[string]*Location 36 }{{ 37 testName: "MultipleFallbacks", 38 in: "registry.somewhere,registry.other", 39 err: "duplicate catch-all registry", 40 }, { 41 testName: "NoRegistryOrDefault", 42 catchAllDefault: "", 43 err: "no catch-all registry or default", 44 }, { 45 testName: "InvalidRegistry", 46 in: "$#foo", 47 err: `invalid registry "\$#foo": invalid host name "\$#foo" in registry`, 48 }, { 49 testName: "InvalidSecuritySuffix", 50 in: "foo.com+bogus", 51 err: `invalid registry "foo.com\+bogus": unknown suffix \("\+bogus"\), need \+insecure, \+secure or no suffix\)`, 52 }, { 53 testName: "IPV6AddrWithoutBrackets", 54 in: "::1", 55 err: `invalid registry "::1": invalid host name "::1" in registry`, 56 }, { 57 testName: "EmptyElement", 58 in: "foo.com,", 59 err: `empty registry part`, 60 }, { 61 testName: "MissingPrefix", 62 in: "=foo.com", 63 err: `empty module prefix`, 64 }, { 65 testName: "MissingRegistry", 66 in: "x.com=", 67 err: `empty registry reference`, 68 }, { 69 testName: "InvalidModulePrefix", 70 in: "foo#=foo.com", 71 err: `invalid module path "foo#": invalid char '#'`, 72 }, { 73 testName: "DuplicateModulePrefix", 74 in: "x.com=r.org,x.com=q.org", 75 err: `duplicate module prefix "x.com"`, 76 }, { 77 testName: "NoDefaultCatchAll", 78 in: "x.com=r.org", 79 err: `no default catch-all registry provided`, 80 }, { 81 testName: "InvalidCatchAll", 82 in: "x.com=r.org", 83 catchAllDefault: "bogus", 84 err: `invalid catch-all registry "bogus": invalid host name "bogus" in registry`, 85 }, { 86 testName: "InvalidRegistryRef", 87 in: "foo.com//bar", 88 err: `invalid registry "foo.com//bar": invalid reference syntax \("foo.com//bar"\)`, 89 }, { 90 testName: "RegistryRefWithDigest", 91 in: "foo.com/bar@sha256:f3c16f525a1b7c204fc953d6d7db7168d84ebf4902f83c3a37d113b18c28981f", 92 err: `invalid registry "foo.com/bar@sha256:f3c16f525a1b7c204fc953d6d7db7168d84ebf4902f83c3a37d113b18c28981f": cannot have an associated tag or digest`, 93 }, { 94 testName: "RegistryRefWithTag", 95 in: "foo.com/bar:sometag", 96 err: `invalid registry "foo.com/bar:sometag": cannot have an associated tag or digest`, 97 }, { 98 testName: "MismatchedSecurity", 99 in: "foo.com/bar+secure,other.example=foo.com/bar+insecure", 100 err: `registry host "foo.com" is specified both as secure and insecure`, 101 }, { 102 testName: "SingleCatchAll", 103 catchAllDefault: "registry.somewhere", 104 wantAllHosts: []Host{{"registry.somewhere", false}}, 105 lookups: map[string]*Location{ 106 "fruit.com/apple": { 107 Host: "registry.somewhere", 108 Repository: "fruit.com/apple", 109 }, 110 }, 111 }, { 112 testName: "CatchAllWithNoDefault", 113 in: "registry.somewhere", 114 wantAllHosts: []Host{{"registry.somewhere", false}}, 115 lookups: map[string]*Location{ 116 "fruit.com/apple": { 117 Host: "registry.somewhere", 118 Repository: "fruit.com/apple", 119 }, 120 }, 121 }, { 122 testName: "CatchAllWithDefault", 123 in: "registry.somewhere", 124 catchAllDefault: "other.cue.somewhere", 125 wantAllHosts: []Host{{"registry.somewhere", false}}, 126 lookups: map[string]*Location{ 127 "fruit.com/apple": { 128 Host: "registry.somewhere", 129 Repository: "fruit.com/apple", 130 }, 131 "": nil, 132 }, 133 }, { 134 testName: "PrefixWithCatchAllNoDefault", 135 in: "example.com=registry.example.com/offset,registry.somewhere", 136 wantAllHosts: []Host{{"registry.example.com", false}, {"registry.somewhere", false}}, 137 lookups: map[string]*Location{ 138 "fruit.com/apple": { 139 Host: "registry.somewhere", 140 Repository: "fruit.com/apple", 141 }, 142 "example.com/blah": { 143 Host: "registry.example.com", 144 Repository: "offset/example.com/blah", 145 }, 146 "example.com": { 147 Host: "registry.example.com", 148 Repository: "offset/example.com", 149 }, 150 }, 151 }, { 152 testName: "PrefixWithCatchAllDefault", 153 in: "example.com=registry.example.com/offset", 154 catchAllDefault: "registry.somewhere", 155 wantAllHosts: []Host{{"registry.example.com", false}, {"registry.somewhere", false}}, 156 lookups: map[string]*Location{ 157 "fruit.com/apple": { 158 Host: "registry.somewhere", 159 Repository: "fruit.com/apple", 160 }, 161 "example.com/blah": { 162 Host: "registry.example.com", 163 Repository: "offset/example.com/blah", 164 }, 165 }, 166 }, { 167 testName: "PrefixWithCatchAllDefaultAndExplicitNoneFallback", 168 in: "example.com=registry.example.com/offset,none", 169 catchAllDefault: "registry.somewhere", 170 wantAllHosts: []Host{{"registry.example.com", false}}, 171 lookups: map[string]*Location{ 172 "fruit.com/apple": nil, 173 "example.com/blah": { 174 Host: "registry.example.com", 175 Repository: "offset/example.com/blah", 176 }, 177 }, 178 }, { 179 testName: "PrefixWithExplicitNone", 180 in: "example.com=none", 181 catchAllDefault: "registry.somewhere", 182 wantAllHosts: []Host{{"registry.somewhere", false}}, 183 lookups: map[string]*Location{ 184 "fruit.com/apple": { 185 Host: "registry.somewhere", 186 Repository: "fruit.com/apple", 187 }, 188 "example.com/blah": nil, 189 }, 190 }, { 191 testName: "LocalhostIsInsecure", 192 in: "localhost:5000", 193 wantAllHosts: []Host{{"localhost:5000", true}}, 194 lookups: map[string]*Location{ 195 "fruit.com/apple": { 196 Host: "localhost:5000", 197 Insecure: true, 198 Repository: "fruit.com/apple", 199 }, 200 }, 201 }, { 202 testName: "SecureLocalhost", 203 in: "localhost:1234+secure", 204 wantAllHosts: []Host{{"localhost:1234", false}}, 205 lookups: map[string]*Location{ 206 "fruit.com/apple": { 207 Host: "localhost:1234", 208 Repository: "fruit.com/apple", 209 }, 210 }, 211 }, { 212 testName: "127.0.0.1IsInsecure", 213 in: "127.0.0.1", 214 wantAllHosts: []Host{{"127.0.0.1", true}}, 215 lookups: map[string]*Location{ 216 "fruit.com/apple": { 217 Host: "127.0.0.1", 218 Insecure: true, 219 Repository: "fruit.com/apple", 220 }, 221 }, 222 }, { 223 testName: "[::1]IsInsecure", 224 in: "[::1]", 225 wantAllHosts: []Host{{"[::1]", true}}, 226 lookups: map[string]*Location{ 227 "fruit.com/apple": { 228 Host: "[::1]", 229 Insecure: true, 230 Repository: "fruit.com/apple", 231 }, 232 }, 233 }, { 234 testName: "[0:0::1]IsInsecure", 235 in: "[0:0::1]", 236 wantAllHosts: []Host{{"[0:0::1]", true}}, 237 lookups: map[string]*Location{ 238 "fruit.com/apple": { 239 Host: "[0:0::1]", 240 Insecure: true, 241 Repository: "fruit.com/apple", 242 }, 243 }, 244 }} 245 246 for _, tc := range testCases { 247 t.Run(tc.testName, func(t *testing.T) { 248 r, err := ParseCUERegistry(tc.in, tc.catchAllDefault) 249 if tc.err != "" { 250 qt.Assert(t, qt.ErrorMatches(err, tc.err)) 251 return 252 } 253 qt.Assert(t, qt.IsNil(err)) 254 qt.Check(t, qt.DeepEquals(r.AllHosts(), tc.wantAllHosts)) 255 testLookups(t, r, tc.lookups) 256 }) 257 } 258 } 259 260 func TestParseConfig(t *testing.T) { 261 testCases := []struct { 262 testName string 263 in string 264 catchAllDefault string 265 err string 266 wantAllHosts []Host 267 lookups map[string]*Location 268 }{{ 269 testName: "NoRegistryOrDefault", 270 catchAllDefault: "", 271 err: "no default catch-all registry provided", 272 }, { 273 testName: "InvalidRegistry", 274 in: ` 275 defaultRegistry: registry: "$#foo" 276 `, 277 err: `invalid default registry configuration: invalid host name "\$#foo" in registry`, 278 }, { 279 testName: "EncHashAsRepo", 280 in: ` 281 defaultRegistry: { 282 registry: "registry.somewhere/hello" 283 pathEncoding: "hashAsRepo" 284 prefixForTags: "mod-" 285 } 286 `, 287 wantAllHosts: []Host{{"registry.somewhere", false}}, 288 lookups: map[string]*Location{ 289 "foo.com/bar v1.2.3": { 290 Host: "registry.somewhere", 291 Repository: "hello/" + hashOf("foo.com/bar"), 292 Tag: "mod-v1.2.3", 293 }, 294 }, 295 }, { 296 testName: "EncHashAsTag", 297 in: ` 298 defaultRegistry: { 299 registry: "registry.somewhere/hello" 300 pathEncoding: "hashAsTag" 301 prefixForTags: "mod-" 302 } 303 `, 304 wantAllHosts: []Host{{"registry.somewhere", false}}, 305 lookups: map[string]*Location{ 306 "foo.com/bar v1.2.3": { 307 Host: "registry.somewhere", 308 Repository: "hello", 309 Tag: "mod-" + hashOf("foo.com/bar") + "-v1.2.3", 310 }, 311 }, 312 }, { 313 testName: "DefaultRegistryWithModuleRegistries", 314 in: ` 315 defaultRegistry: { 316 registry: "registry.somewhere" 317 } 318 moduleRegistries: { 319 "a.com": { 320 registry: "registry.otherwhere" 321 } 322 } 323 `, 324 wantAllHosts: []Host{{"registry.otherwhere", false}, {"registry.somewhere", false}}, 325 lookups: map[string]*Location{ 326 "a.com v0.0.1": { 327 Host: "registry.otherwhere", 328 Repository: "a.com", 329 Tag: "v0.0.1", 330 }, 331 "b.com v0.0.1": { 332 Host: "registry.somewhere", 333 Repository: "b.com", 334 Tag: "v0.0.1", 335 }, 336 }, 337 }, { 338 testName: "DiverseRegistries", 339 catchAllDefault: "default.example/foo", 340 in: ` 341 moduleRegistries: { 342 "a.com": { 343 registry: "r1.example/a/b+insecure" 344 } 345 "a.com/foo/bar": { 346 registry: "r2.example/xxx" 347 pathEncoding: "hashAsRepo" 348 prefixForTags: "cue-" 349 } 350 "a.com/foo": { 351 registry: "r1.example/hello+insecure" 352 } 353 "stripped.org/bar": { 354 registry: "r3.example/repo" 355 stripPrefix: true 356 } 357 "badmodules.org": { 358 registry: "none" 359 } 360 } 361 `, 362 wantAllHosts: []Host{{ 363 Name: "default.example", 364 }, { 365 Name: "r1.example", 366 Insecure: true, 367 }, { 368 Name: "r2.example", 369 }, { 370 Name: "r3.example", 371 }}, 372 lookups: map[string]*Location{ 373 "a.com/other/bar/baz v0.0.1": { 374 Host: "r1.example", 375 Insecure: true, 376 Repository: "a/b/a.com/other/bar/baz", 377 Tag: "v0.0.1", 378 }, 379 "a.com/foo/bar v0.0.1": { 380 Host: "r2.example", 381 Repository: "xxx/" + hashOf("a.com/foo/bar"), 382 Tag: "cue-v0.0.1", 383 }, 384 "a.com/foo/bar": { 385 Host: "r2.example", 386 Repository: "xxx/" + hashOf("a.com/foo/bar"), 387 Tag: "cue-", 388 }, 389 "a.com/foo/baz v0.0.1": { 390 Host: "r1.example", 391 Insecure: true, 392 Repository: "hello/a.com/foo/baz", 393 Tag: "v0.0.1", 394 }, 395 "a.com/food v0.0.1": { 396 Host: "r1.example", 397 Insecure: true, 398 Repository: "a/b/a.com/food", 399 Tag: "v0.0.1", 400 }, 401 "b.com v0.0.1": { 402 Host: "default.example", 403 Repository: "foo/b.com", 404 Tag: "v0.0.1", 405 }, 406 "stripped.org/bar/one/two/three v0.0.1": { 407 Host: "r3.example", 408 Repository: "repo/one/two/three", 409 Tag: "v0.0.1", 410 }, 411 "stripped.org/bar v0.0.1": { 412 Host: "r3.example", 413 Repository: "repo", 414 Tag: "v0.0.1", 415 }, 416 "badmodules.org/something v1.2.3": nil, 417 "badmodules.org v1.2.3": nil, 418 }, 419 }, { 420 testName: "InvalidModulePath", 421 in: ` 422 moduleRegistries: "bad+module": { 423 registry: "foo.com" 424 } 425 `, 426 err: `invalid module path "bad\+module": invalid char '\+'`, 427 }, { 428 testName: "InvalidHost", 429 in: ` 430 moduleRegistries: "foo.example": { 431 registry: "badhost:" 432 } 433 `, 434 err: `invalid registry configuration in "foo.example": invalid host name "badhost:" in registry`, 435 }, { 436 testName: "InvalidRepository", 437 in: ` 438 moduleRegistries: "foo.example": { 439 registry: "ok.com/A" 440 } 441 `, 442 err: `invalid registry configuration in "foo.example": invalid reference syntax \("ok.com/A"\)`, 443 }, { 444 testName: "UnknownField", 445 in: ` 446 registiries: "foo.example": { 447 registry: "ok.com/A", 448 } 449 `, 450 err: `invalid configuration file: registiries: field not allowed`, 451 }, { 452 testName: "MismatchedSecurity", 453 catchAllDefault: "c.example", 454 in: ` 455 moduleRegistries: { 456 "a.example": { 457 registry: "ok.com+insecure" 458 } 459 "b.example": { 460 registry: "ok.com" 461 } 462 } 463 `, 464 err: `registry host "ok.com" is specified both as secure and insecure`, 465 }, { 466 testName: "StripPrefixWithNoRepo", 467 catchAllDefault: "c.example", 468 in: ` 469 moduleRegistries: { 470 "a.example/foo": { 471 registry: "foo.example" 472 stripPrefix: true 473 } 474 } 475 `, 476 err: `invalid registry configuration in "a.example/foo": use of stripPrefix requires a non-empty repository within the registry`, 477 }} 478 479 for _, tc := range testCases { 480 t.Run(tc.testName, func(t *testing.T) { 481 r, err := ParseConfig([]byte(tc.in), "somefile.cue", tc.catchAllDefault) 482 if tc.err != "" { 483 qt.Assert(t, qt.ErrorMatches(err, tc.err)) 484 return 485 } 486 qt.Assert(t, qt.IsNil(err)) 487 qt.Check(t, qt.DeepEquals(r.AllHosts(), tc.wantAllHosts)) 488 testLookups(t, r, tc.lookups) 489 }) 490 } 491 } 492 493 func testLookups(t *testing.T, r LocationResolver, lookups map[string]*Location) { 494 for key, want := range lookups { 495 t.Run(key, func(t *testing.T) { 496 m, v, _ := strings.Cut(key, " ") 497 got, ok := r.ResolveToLocation(m, v) 498 if want == nil { 499 qt.Assert(t, qt.IsFalse(ok)) 500 } else { 501 qt.Assert(t, qt.DeepEquals(&got, want)) 502 } 503 }) 504 } 505 } 506 507 func hashOf(s string) string { 508 return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) 509 }