diff --git a/cmd/cue/cmd/help.go b/cmd/cue/cmd/help.go index e901ef197..ac807c0f6 100644 --- a/cmd/cue/cmd/help.go +++ b/cmd/cue/cmd/help.go @@ -255,6 +255,9 @@ Examples: CUE_REGISTRY=localhost:5000 CUE_REGISTRY='[::1]:5000' +The special name "none" can be used to indicate that no registry +should be used. + If a path is present too, all modules will be stored under that path. For example: @@ -291,6 +294,18 @@ Note that the syntax above implies that the ordering of the elements in CUE_REGISTRY isn't important because the resolution algorithm is order-independent. +To specify that no registry should be used for a given module prefix, +the special name "none" can be used. + +For example: + + CUE_REGISTRY='foo.example/bar=none,myregistry.example' + +In the above example, any attempt to fetch a module under +"foo.example/bar" will result in a failure. Note that this will not +take effect if the module is already present in the on-disk cache, +which is consulted before looking at CUE_REGISTRY. + Customizing Name Resolution diff --git a/cmd/cue/cmd/testdata/script/help_registryconfig.txtar b/cmd/cue/cmd/testdata/script/help_registryconfig.txtar index ecc6a7e17..2b28ffc85 100644 --- a/cmd/cue/cmd/testdata/script/help_registryconfig.txtar +++ b/cmd/cue/cmd/testdata/script/help_registryconfig.txtar @@ -23,6 +23,9 @@ Examples: CUE_REGISTRY=localhost:5000 CUE_REGISTRY='[::1]:5000' +The special name "none" can be used to indicate that no registry +should be used. + If a path is present too, all modules will be stored under that path. For example: @@ -59,6 +62,18 @@ Note that the syntax above implies that the ordering of the elements in CUE_REGISTRY isn't important because the resolution algorithm is order-independent. +To specify that no registry should be used for a given module prefix, +the special name "none" can be used. + +For example: + + CUE_REGISTRY='foo.example/bar=none,myregistry.example' + +In the above example, any attempt to fetch a module under +"foo.example/bar" will result in a failure. Note that this will not +take effect if the module is already present in the on-disk cache, +which is consulted before looking at CUE_REGISTRY. + Customizing Name Resolution @@ -125,9 +140,15 @@ definition: // repoPrefix is used to determine the repository to use for a // specific module. // + // As a special case, the registry may be "none", indicating + // that there is no registry for its associated modules. + // If a module resolves to a "none" registry, the resolver + // will return an error. + // // Examples: // "localhost:1234" // "myregistry.example/my-modules+secure" + // "none" registry!: string // pathEncoding specifies how module versions map to diff --git a/cmd/cue/cmd/testdata/script/registry_nofallback.txtar b/cmd/cue/cmd/testdata/script/registry_nofallback.txtar new file mode 100644 index 000000000..305e380ce --- /dev/null +++ b/cmd/cue/cmd/testdata/script/registry_nofallback.txtar @@ -0,0 +1,28 @@ +# Check that we can explicitly avoid falling back to the default registry. +env CUE_REGISTRY=foo.com=$CUE_REGISTRY1,none +! exec cue eval . +cmp stderr expect-stderr + +-- expect-stderr -- +import failed: cannot find package "example.com@v0": cannot fetch example.com@v0.0.1: cannot resolve example.com (version v0.0.1) to registry: + ./main.cue:2:8 +-- cue.mod/module.cue -- +module: "main.org@v0" +language: version: "v0.8.0" +deps: { + "example.com@v0": { + v: "v0.0.1" + } +} +-- main.cue -- +package main +import "example.com@v0:main" + +main + +-- _registry1/example.com_v0.0.1/cue.mod/module.cue -- +module: "example.com@v0" +language: version: "v0.8.0" + +-- _registry1/example.com_v0.0.1/top.cue -- +package main diff --git a/internal/mod/modresolve/resolve.go b/internal/mod/modresolve/resolve.go index bf37fbccc..878777029 100644 --- a/internal/mod/modresolve/resolve.go +++ b/internal/mod/modresolve/resolve.go @@ -134,6 +134,7 @@ type registryConfig struct { StripPrefix bool `json:"stripPrefix,omitempty"` // The following fields are filled in from Registry after parsing. + none bool host string repository string insecure bool @@ -144,7 +145,7 @@ func (r *registryConfig) init() error { if err != nil { return err } - r.host, r.repository, r.insecure = r1.host, r1.repository, r1.insecure + r.none, r.host, r.repository, r.insecure = r1.none, r1.host, r1.repository, r1.insecure if r.PrefixForTags != "" { if !ociref.IsValidTag(r.PrefixForTags) { @@ -348,6 +349,9 @@ type resolver struct { func (r *resolver) initHosts() error { hosts := make(map[string]bool) addHost := func(reg *registryConfig) error { + if reg.none { + return nil + } if insecure, ok := hosts[reg.host]; ok { if insecure != reg.insecure { return fmt.Errorf("registry host %q is specified both as secure and insecure", reg.host) @@ -416,10 +420,10 @@ func (r *resolver) ResolveToLocation(mpath, vers string) (Location, bool) { // It's a possible match but not necessarily the longest one. bestMatch, bestMatchReg = pat, reg } - if bestMatchReg == nil { + reg := bestMatchReg + if reg == nil || reg.none { return Location{}, false } - reg := bestMatchReg loc := Location{ Host: reg.host, Insecure: reg.insecure, @@ -448,6 +452,12 @@ func (r *resolver) ResolveToLocation(mpath, vers string) (Location, bool) { } func parseRegistry(env0 string) (*registryConfig, error) { + if env0 == "none" { + return ®istryConfig{ + Registry: env0, + none: true, + }, nil + } env := env0 var suffix string if i := strings.LastIndex(env, "+"); i > 0 { diff --git a/internal/mod/modresolve/resolve_test.go b/internal/mod/modresolve/resolve_test.go index 70329be8f..7289e523b 100644 --- a/internal/mod/modresolve/resolve_test.go +++ b/internal/mod/modresolve/resolve_test.go @@ -163,6 +163,30 @@ func TestParseCUERegistry(t *testing.T) { Repository: "offset/example.com/blah", }, }, + }, { + testName: "PrefixWithCatchAllDefaultAndExplicitNoneFallback", + in: "example.com=registry.example.com/offset,none", + catchAllDefault: "registry.somewhere", + wantAllHosts: []Host{{"registry.example.com", false}}, + lookups: map[string]*Location{ + "fruit.com/apple": nil, + "example.com/blah": { + Host: "registry.example.com", + Repository: "offset/example.com/blah", + }, + }, + }, { + testName: "PrefixWithExplicitNone", + in: "example.com=none", + catchAllDefault: "registry.somewhere", + wantAllHosts: []Host{{"registry.somewhere", false}}, + lookups: map[string]*Location{ + "fruit.com/apple": { + Host: "registry.somewhere", + Repository: "fruit.com/apple", + }, + "example.com/blah": nil, + }, }, { testName: "LocalhostIsInsecure", in: "localhost:5000", @@ -330,6 +354,9 @@ moduleRegistries: { registry: "r3.example/repo" stripPrefix: true } + "badmodules.org": { + registry: "none" + } } `, wantAllHosts: []Host{{ @@ -386,6 +413,8 @@ moduleRegistries: { Repository: "repo", Tag: "v0.0.1", }, + "badmodules.org/something v1.2.3": nil, + "badmodules.org v1.2.3": nil, }, }, { testName: "InvalidModulePath", diff --git a/internal/mod/modresolve/schema.cue b/internal/mod/modresolve/schema.cue index a6bfdeacf..706b84b0e 100644 --- a/internal/mod/modresolve/schema.cue +++ b/internal/mod/modresolve/schema.cue @@ -65,9 +65,15 @@ // repoPrefix is used to determine the repository to use for a // specific module. // + // As a special case, the registry may be "none", indicating + // that there is no registry for its associated modules. + // If a module resolves to a "none" registry, the resolver + // will return an error. + // // Examples: // "localhost:1234" // "myregistry.example/my-modules+secure" + // "none" registry!: string // pathEncoding specifies how module versions map to diff --git a/mod/modconfig/modconfig.go b/mod/modconfig/modconfig.go index d8f1b9449..2122ec816 100644 --- a/mod/modconfig/modconfig.go +++ b/mod/modconfig/modconfig.go @@ -156,9 +156,12 @@ func (r *Resolver) ResolveToLocation(mpath string, version string) (HostLocation func (r *Resolver) ResolveToRegistry(mpath string, version string) (modregistry.RegistryLocation, error) { loc, ok := r.resolver.ResolveToLocation(mpath, version) if !ok { - // This can only happen when mpath is invalid, which should not + // This can happen when mpath is invalid, which should not // happen in practice, as the only caller is modregistry which // vets module paths before calling Resolve. + // + // It can also happen when the user has explicitly configured a "none" + // registry to avoid falling back to a default registry. return modregistry.RegistryLocation{}, fmt.Errorf("cannot resolve %s (version %s) to registry", mpath, version) } r.mu.Lock()