Skip to content

Commit

Permalink
fix: removes registry mirror feature
Browse files Browse the repository at this point in the history
Signed-off-by: Jennifer Power <[email protected]>
  • Loading branch information
jpower432 committed Oct 20, 2022
1 parent baef487 commit 0d24517
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 269 deletions.
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,16 @@ uor-client-go version
### Registry Config

This registry config can be stored to individually configure each registry. It should be named `registry-config.yaml`.
The locations this can be stored in are the current working directory and at `$HOME/.uor/registry-config.yaml`. If mirror
endpoints are configured, a mirrored collection will attempt to be pulled before the user-provided reference.
The locations this can be stored in are the current working directory and at `$HOME/.uor/registry-config.yaml`.
As a special case, the prefix field can be missing; if so, it defaults to the value of the location field.

Example:
```bash
registries:
- prefix: "localhost*"
endpoint:
location: "localhost:5000"
skipTLS: false
plainHTTP: true
mirrors:
- location: "localhost:5001"
plainHTTP: true
- prefix: "localhost:5001/test"
location: localhost:5001
skipTLS: false
plainHTTP: true
```


Expand Down
6 changes: 1 addition & 5 deletions cmd/client/commands/options/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package options
import (
"errors"

"github.com/mitchellh/mapstructure"
"github.com/spf13/pflag"
"github.com/spf13/viper"

Expand Down Expand Up @@ -37,11 +36,8 @@ func (o *Remote) LoadRegistryConfig() error {
}
return err
}
option := viper.DecoderConfigOption(func(config *mapstructure.DecoderConfig) {
config.TagName = "json"
})

return viper.Unmarshal(&o.RegistryConfig, option)
return viper.Unmarshal(&o.RegistryConfig)
}

// RemoteAuth describes remote authentication configuration options that can be set.
Expand Down
9 changes: 5 additions & 4 deletions cmd/client/commands/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ func (o *PushOptions) Run(ctx context.Context) error {
return err
}

// Not adding a registry configuration when pushing since only
// one reference us being handled at a time.
// QUESTION(jpower432): Could this create a problem? I think it would
// be more unexpected to publish collection to declared mirrors.
if err := o.Remote.LoadRegistryConfig(); err != nil {
return err
}

client, err := orasclient.NewClient(
orasclient.SkipTLSVerify(o.Insecure),
orasclient.WithAuthConfigs(o.Configs),
orasclient.WithPlainHTTP(o.PlainHTTP),
orasclient.WithRegistryConfig(o.RegistryConfig),
)

if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion cmd/client/commands/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ func (o *ServeOptions) Run(ctx context.Context) error {
PullCache: cache,
RegistryConfig: o.RegistryConfig,
}
service := collectionmanager.FromManager(manager, opts)
service, err := collectionmanager.FromManager(manager, opts)
if err != nil {
return err
}

// Register the service with the gRPC server
managerapi.RegisterCollectionManagerServer(rpc, service)
Expand Down
79 changes: 15 additions & 64 deletions registryclient/orasclient/oras.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ func (c *orasClient) LoadCollection(ctx context.Context, reference string) (coll
return value.(collection.Collection), nil
}

repo, updatedRef, err := c.setupRepo(ctx, reference)
repo, err := c.setupRepo(ctx, reference)
if err != nil {
return collection.Collection{}, fmt.Errorf("could not create registry target: %w", err)
}

graph, err := loadCollection(ctx, repo, updatedRef)
graph, err := loadCollection(ctx, repo, reference)
if err != nil {
return collection.Collection{}, err
}
Expand All @@ -140,7 +140,7 @@ func (c *orasClient) Pull(ctx context.Context, reference string, store content.S
var allDescs []ocispec.Descriptor

var from oras.Target
repo, updatedRef, err := c.setupRepo(ctx, reference)
repo, err := c.setupRepo(ctx, reference)
if err != nil {
return ocispec.Descriptor{}, allDescs, fmt.Errorf("could not create registry target: %w", err)
}
Expand All @@ -157,7 +157,7 @@ func (c *orasClient) Pull(ctx context.Context, reference string, store content.S
if exists {
graph = value.(collection.Collection)
} else {
graph, err = loadCollection(ctx, repo, updatedRef)
graph, err = loadCollection(ctx, repo, reference)
if err != nil {
return ocispec.Descriptor{}, allDescs, err
}
Expand Down Expand Up @@ -235,7 +235,7 @@ func (c *orasClient) Pull(ctx context.Context, reference string, store content.S
cCopyOpts := c.copyOpts
cCopyOpts.FindSuccessors = successorFn

desc, err := oras.Copy(ctx, from, updatedRef, store, updatedRef, cCopyOpts)
desc, err := oras.Copy(ctx, from, reference, store, reference, cCopyOpts)
if err != nil {
return ocispec.Descriptor{}, allDescs, err
}
Expand All @@ -245,25 +245,25 @@ func (c *orasClient) Pull(ctx context.Context, reference string, store content.S

// Push performs a copy of OCI artifacts to a remote location.
func (c *orasClient) Push(ctx context.Context, store content.Store, reference string) (ocispec.Descriptor, error) {
repo, updatedRef, err := c.setupRepo(ctx, reference)
repo, err := c.setupRepo(ctx, reference)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("could not create registry target: %w", err)
}
return oras.Copy(ctx, store, updatedRef, repo, updatedRef, c.copyOpts)
return oras.Copy(ctx, store, reference, repo, reference, c.copyOpts)
}

// GetManifest returns the manifest the reference resolves to.
func (c *orasClient) GetManifest(ctx context.Context, reference string) (ocispec.Descriptor, io.ReadCloser, error) {
repo, updatedRef, err := c.setupRepo(ctx, reference)
repo, err := c.setupRepo(ctx, reference)
if err != nil {
return ocispec.Descriptor{}, nil, fmt.Errorf("could not create registry target: %w", err)
}
return repo.FetchReference(ctx, updatedRef)
return repo.FetchReference(ctx, reference)
}

// GetContent retrieves the content for a specified descriptor at a specified reference.
func (c *orasClient) GetContent(ctx context.Context, reference string, desc ocispec.Descriptor) ([]byte, error) {
repo, _, err := c.setupRepo(ctx, reference)
repo, err := c.setupRepo(ctx, reference)
if err != nil {
return nil, fmt.Errorf("could not create registry target: %w", err)
}
Expand Down Expand Up @@ -306,78 +306,29 @@ func (c *orasClient) authClient(insecure bool) *auth.Client {
}

// setupRepo configures the client to access the remote repository.
func (c *orasClient) setupRepo(ctx context.Context, reference string) (registry.Repository, string, error) {
func (c *orasClient) setupRepo(ctx context.Context, reference string) (registry.Repository, error) {
registryConfig, err := registryclient.FindRegistry(c.registryConf, reference)
if err != nil {
return nil, reference, err
return nil, err
}

repo, err := remote.NewRepository(reference)
if err != nil {
return nil, reference, fmt.Errorf("could not create registry target: %w", err)
return nil, fmt.Errorf("could not create registry target: %w", err)
}

// If the incoming reference does not match any registry prefixes,
// use the client configuration. If there is a match and the registry
// has mirrors configured, try each one before attempting to contact the
// input reference (the default).
switch {
case registryConfig == nil:
repo.PlainHTTP = c.plainHTTP
repo.Client = c.authClient(c.insecure)
return repo, reference, nil
case len(registryConfig.Mirrors) != 0:
mirrorRepo, mirrorReference, err := c.pickMirror(ctx, *registryConfig, reference)
if err == nil {
return mirrorRepo, mirrorReference, nil
}

var merr *registryclient.ErrNoAvailableMirrors
if err != nil && !errors.As(err, &merr) {
return nil, reference, err
}

fallthrough
return repo, nil
default:
repo.PlainHTTP = registryConfig.PlainHTTP
repo.Client = c.authClient(registryConfig.SkipTLS)
return repo, reference, nil
return repo, nil
}
}

// pickMirror is used if the reference is linked to a registry in the registry configuration that has mirrors. It returns
// a configured remote.Repository and rewritten reference per the mirror configuration.
func (c *orasClient) pickMirror(ctx context.Context, reg registryclient.Registry, ref string) (registry.Repository, string, error) {
pullSources, err := reg.PullSourceFromReference(ref)
if err != nil {
return nil, ref, err
}

for _, ps := range pullSources {
mirror := ps.Endpoint
mirrorReg, err := remote.NewRegistry(mirror.Location)
if err != nil {
return nil, ref, fmt.Errorf("could not create registry target: %w", err)
}
mirrorReg.PlainHTTP = mirror.PlainHTTP
mirrorReg.Client = c.authClient(mirror.SkipTLS)

if err := mirrorReg.Ping(ctx); err == nil {
mirrorReference, err := registry.ParseReference(ps.Reference)
if err != nil {
return nil, ref, fmt.Errorf("reference %q: %w", mirrorReference, err)
}
mirrorRepo, err := mirrorReg.Repository(ctx, mirrorReference.Repository)
if err != nil {
return nil, ref, err
}
return mirrorRepo, mirrorReference.String(), nil
}
}

return nil, ref, &registryclient.ErrNoAvailableMirrors{Registry: reg.Location}
}

// loadFiles stores files in a file store and creates descriptors representing each file in the store.
func loadFiles(ctx context.Context, store *file.Store, mediaType string, files ...string) ([]ocispec.Descriptor, error) {
var descs []ocispec.Descriptor
Expand Down
28 changes: 0 additions & 28 deletions registryclient/orasclient/oras_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,34 +259,6 @@ func TestPushPull(t *testing.T) {
require.NoError(t, c.Destroy())
})

t.Run("Success/PullWithRegistryConfigMirror", func(t *testing.T) {
expDigest := "sha256:98f36e12e9dbacfbb10b9d1f32a46641eb42de588e54cfd7e8627d950ae8140a"
config := registryclient.RegistryConfig{
Registries: []registryclient.Registry{
{
Prefix: "test.server.com",
Endpoint: registryclient.Endpoint{
PlainHTTP: false,
Location: "test.server.com",
},
Mirrors: []registryclient.Endpoint{
{
PlainHTTP: true,
Location: u.Host,
},
},
},
},
}
c, err := NewClient(WithRegistryConfig(config))
require.NoError(t, err)
root, descs, err := c.Pull(context.TODO(), "test.server.com/test:latest", memory.New())
require.NoError(t, err)
require.Equal(t, expDigest, root.Digest.String())
require.Len(t, descs, 4)
require.NoError(t, c.Destroy())
})

t.Run("Success/PullWithRegistryConfigNoMatch", func(t *testing.T) {
expDigest := "sha256:98f36e12e9dbacfbb10b9d1f32a46641eb42de588e54cfd7e8627d950ae8140a"
config := registryclient.RegistryConfig{
Expand Down
64 changes: 13 additions & 51 deletions registryclient/registries.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package registryclient

import (
"fmt"
"regexp"
"strings"

"oras.land/oras-go/v2/errdef"
)

// This configuration is slightly modified and paired down version of the registries.conf.
Expand All @@ -19,65 +16,26 @@ import (
// Endpoint describes a remote location of a registry.
type Endpoint struct {
// The endpoint's remote location.
Location string `json:"location"`
Location string `mapstructure:"location" json:"location"`
// If true, certs verification will be skipped.
SkipTLS bool `json:"skipTLS"`
SkipTLS bool `mapstructure:"skipTLS" json:"skipTLS"`
// If true, the client will use HTTP to
// connect to the registry.
PlainHTTP bool `json:"plainHTTP"`
}

// RewriteReference returns a reference for the endpoint given the original
// reference and registry prefix.
func (e Endpoint) RewriteReference(reference string) (string, error) {
if e.Location == "" {
return reference, nil
}

parts := strings.SplitN(reference, "/", 2)
if len(parts) == 1 {
return " ", fmt.Errorf("%w: missing repository", errdef.ErrInvalidReference)
}
path := parts[1]
return fmt.Sprintf("%s/%s", e.Location, path), nil
}

// PullSource is a reference that is associated with a
// specific endpoint. This is used to generate references
// for registry mirrors and correlate them the mirror endpoint
type PullSource struct {
Reference string
Endpoint
PlainHTTP bool `mapstructure:"plainHTTP" json:"plainHTTP"`
}

// Registry represents a registry.
type Registry struct {
// Prefix is used for endpoint matching.
Prefix string `json:"prefix"`
Prefix string `mapstructure:"prefix" json:"prefix"`
// A registry is an Endpoint too
Endpoint `json:"endpoint"`
// The registry mirrors
Mirrors []Endpoint `json:"mirrors,omitempty"`
}

// PullSourceFromReference returns all pull source for the registry mirrors from
// a given reference.
func (r *Registry) PullSourceFromReference(ref string) ([]PullSource, error) {
var sources []PullSource
for _, mirror := range r.Mirrors {
rewritten, err := mirror.RewriteReference(ref)
if err != nil {
return nil, err
}
sources = append(sources, PullSource{Endpoint: mirror, Reference: rewritten})
}
return sources, nil
Endpoint `mapstructure:",squash" json:",inline"`
}

// RegistryConfig is a configuration to configure multiple
// registry endpoints.
type RegistryConfig struct {
Registries []Registry `json:"registries"`
Registries []Registry `mapstructure:"registries" json:"registries"`
}

// FindRegistry returns the registry from the registry config that
Expand All @@ -87,14 +45,18 @@ func FindRegistry(registryConfig RegistryConfig, ref string) (*Registry, error)
prefixLen := 0

for _, r := range registryConfig.Registries {
prefixExp, err := regexp.Compile(validPrefix(r.Prefix))
match := r.Prefix
if match == "" {
match = r.Location
}
prefixExp, err := regexp.Compile(validPrefix(match))
if err != nil {
return nil, err
}
if prefixExp.MatchString(ref) {
if len(r.Prefix) > prefixLen {
if len(match) > prefixLen {
reg = r
prefixLen = len(r.Prefix)
prefixLen = len(match)
}
}
}
Expand Down
Loading

0 comments on commit 0d24517

Please sign in to comment.