From 4d4c80e59f2cd5054d8247c82ceb489b2011186a Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Mon, 27 Jan 2025 11:06:02 +0800 Subject: [PATCH 1/2] feat: implement store multiplexer Signed-off-by: Shiwei Zhang --- example_test.go | 86 ++++++++++ store_mux.go | 254 +++++++++++++++++++++++++++ store_mux_test.go | 427 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 767 insertions(+) create mode 100644 example_test.go create mode 100644 store_mux.go create mode 100644 store_mux_test.go diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..3ed445b --- /dev/null +++ b/example_test.go @@ -0,0 +1,86 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ratify_test + +import ( + "context" + "testing/fstest" + + "github.com/ratify-project/ratify-go" +) + +func ExampleStoreMux() { + // Create stores for demonstration. + // Each store is an OCI image layout with a single artifact with a different + // tag. + ctx := context.Background() + var stores []ratify.Store + for _, tag := range []string{"foo", "bar", "hello", "world"} { + fsys := fstest.MapFS{ + "blob/sha256/03c6d9dcb63b03c5489db13cedbc549f2de807f43440df549644866a24a3561b": &fstest.MapFile{ + Data: []byte(`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","artifactType":"application/vnd.unknown.artifact.v1","config":{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="},"layers":[{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="}],"annotations":{"org.opencontainers.image.created":"2025-01-26T07:32:59Z"}}`), + }, + "blob/sha256/44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a": &fstest.MapFile{ + Data: []byte(`{}`), + }, + "index.json": &fstest.MapFile{ + Data: []byte(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:03c6d9dcb63b03c5489db13cedbc549f2de807f43440df549644866a24a3561b","size":535,"annotations":{"org.opencontainers.image.created":"2025-01-26T07:32:59Z","org.opencontainers.image.ref.name":"` + tag + `"},"artifactType":"application/vnd.unknown.artifact.v1"}]}`), + }, + "oci-layout": &fstest.MapFile{ + Data: []byte(`{"imageLayoutVersion": "1.0.0"}`), + }, + } + store, err := ratify.NewOCIStoreFromFS(ctx, tag, fsys) + if err != nil { + panic(err) + } + stores = append(stores, store) + } + + // Create a new store multiplexer + store := ratify.NewStoreMux("multiplexer") + + // Register store with exact repository name match. + store.Register("registry.example/test", stores[0]) + + // Register store for an registry. + store.Register("registry.example", stores[1]) + + // Register store with wildcard DNS record. + store.Register("*.example", stores[2]) + + // Register a fallback store for unmatched requests. + store.RegisterFallback(stores[3]) + + // Reference should be resolvable by the matching store. + _, err := store.Resolve(ctx, "registry.example/test:foo") + if err != nil { + panic(err) + } + _, err = store.Resolve(ctx, "registry.example/another-test:bar") + if err != nil { + panic(err) + } + _, err = store.Resolve(ctx, "test.example/test:hello") + if err != nil { + panic(err) + } + _, err = store.Resolve(ctx, "another.registry.example/test:world") + if err != nil { + panic(err) + } + // Output: +} diff --git a/store_mux.go b/store_mux.go new file mode 100644 index 0000000..51c1d73 --- /dev/null +++ b/store_mux.go @@ -0,0 +1,254 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ratify + +import ( + "context" + "errors" + "fmt" + "strings" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2/registry" +) + +// StoreMux is a store multiplexer. +// It matches the registry name of each incoming request against a list of +// registered patterns and calls the store for the pattern that most closely +// matches the registry name. +// +// # Patterns +// +// Patterns can match the registry name and the repository name. +// If patterns are registered only with the registry name, wildcard DNS records +// (see RFC 1034, Section 4.3.3, RFC 4592, and RFC 6125, Section 6.4.3) are +// accepted. +// Some examples: +// +// - "registry.example" matches any request to the host "registry.example" +// - "registry.example/foo" matches any request to the repository "foo" on the +// host "registry.example" +// - "*.example" matches any request to any host in the "example" domain +// - "*.example:5000" matches any request to the host "*.example" on port 5000 +// +// Patterns with repository names does not support wildcard DNS records. +// For example, "*.example/foo" is not a valid pattern. +// Top level domain wildcard is also not supported. That is, "*" is not a valid +// pattern. +// +// # Precedence +// +// If two or more patterns match a request, then the most specific pattern takes +// precedence. For example, if both "registry.example/foo" and "registry.example" +// are registered, then the former takes precedence. +type StoreMux struct { + name string + wildcard map[string]Store + registry map[string]Store + repository map[string]Store + fallback Store +} + +// NewStoreMux creates a new [StoreMux]. +func NewStoreMux(name string) *StoreMux { + return &StoreMux{ + name: name, + } +} + +// Name is the name of the store. +func (s *StoreMux) Name() string { + return s.name +} + +// Resolve resolves to a descriptor for the given artifact reference. +func (s *StoreMux) Resolve(ctx context.Context, ref string) (ocispec.Descriptor, error) { + store, err := s.storeFromReference(ref) + if err != nil { + return ocispec.Descriptor{}, err + } + return store.Resolve(ctx, ref) +} + +// ListReferrers returns the immediate set of supply chain artifacts for the +// given subject reference. +// Note: This API supports pagination. fn should be set to handle the +// underlying pagination. +func (s *StoreMux) ListReferrers(ctx context.Context, ref string, artifactTypes []string, fn func(referrers []ocispec.Descriptor) error) error { + store, err := s.storeFromReference(ref) + if err != nil { + return err + } + return store.ListReferrers(ctx, ref, artifactTypes, fn) +} + +// FetchBlob returns the blob by the given reference. +// WARNING: This API is intended to use for small objects like signatures, +// SBoMs. +func (s *StoreMux) FetchBlob(ctx context.Context, repo string, desc ocispec.Descriptor) ([]byte, error) { + store, err := s.storeFromRepository(repo) + if err != nil { + return nil, err + } + return store.FetchBlob(ctx, repo, desc) +} + +// FetchManifest returns the referenced manifest as given by the descriptor. +func (s *StoreMux) FetchManifest(ctx context.Context, repo string, desc ocispec.Descriptor) ([]byte, error) { + store, err := s.storeFromRepository(repo) + if err != nil { + return nil, err + } + return store.FetchManifest(ctx, repo, desc) +} + +// Register registers a store for the given pattern. +func (s *StoreMux) Register(pattern string, store Store) error { + if pattern == "" { + return errors.New("pattern is required") + } + if store == nil { + return errors.New("store is required") + } + + // check for FQDN pattern + if strings.Contains(pattern, "/") { + return s.registerRepository(pattern, store) + } + + // check for registry pattern + return s.registerRegistry(pattern, store) +} + +// registerRegistry registers a store for the given registry pattern. +func (s *StoreMux) registerRegistry(pattern string, store Store) error { + ref := registry.Reference{ + Registry: pattern, + } + if err := ref.ValidateRegistry(); err != nil { + return fmt.Errorf("invalid pattern %q: %v", pattern, err) + } + + switch strings.Count(pattern, "*") { + case 0: + if s.registry == nil { + s.registry = map[string]Store{ + pattern: store, + } + } else { + s.registry[pattern] = store + } + case 1: + if !strings.HasPrefix(pattern, "*.") { + return fmt.Errorf("invalid pattern %q: wildcard DNS records must start with '*.'", pattern) + } + pattern = pattern[2:] + if s.wildcard == nil { + s.wildcard = map[string]Store{ + pattern: store, + } + } else { + s.wildcard[pattern] = store + } + default: + return fmt.Errorf("invalid pattern %q: too many wildcards", pattern) + } + + return nil +} + +// registerRepository registers a store for the given repository pattern. +func (s *StoreMux) registerRepository(pattern string, store Store) error { + if strings.Contains(pattern, "*") { + return fmt.Errorf("invalid pattern %q: wildcard DNS records not supported", pattern) + } + ref, err := registry.ParseReference(pattern) + if err != nil { + return fmt.Errorf("invalid pattern %q: %v", pattern, err) + } + if ref.Reference != "" { + return fmt.Errorf("invalid pattern %q: tag or digest not supported", pattern) + } + + if s.repository == nil { + s.repository = map[string]Store{ + pattern: store, + } + } else { + s.repository[pattern] = store + } + + return nil +} + +// RegisterFallback registers a fallback store, which is used if no other store +// matches. +func (s *StoreMux) RegisterFallback(store Store) error { + if store == nil { + return errors.New("store is required") + } + s.fallback = store + return nil +} + +// storeFromReference returns the store for the given reference. +func (s *StoreMux) storeFromReference(ref string) (Store, error) { + // optimize if no repository patterns are registered. + if len(s.repository) == 0 { + registry, _, _ := strings.Cut(ref, "/") + return s.storeFromRegistry(registry) + } + + reference, err := registry.ParseReference(ref) + if err != nil { + return nil, err + } + repository := reference.Registry + "/" + reference.Repository + return s.storeFromRepository(repository) +} + +// storeFromRepository returns the store for the given repository. +func (s *StoreMux) storeFromRepository(repo string) (Store, error) { + // check for exact match. + if store, ok := s.repository[repo]; ok { + return store, nil + } + + // check for registry. + registry, _, _ := strings.Cut(repo, "/") + return s.storeFromRegistry(registry) +} + +// storeFromRegistry returns the store for the given registry. +func (s *StoreMux) storeFromRegistry(registry string) (Store, error) { + // check for exact match. + if store, ok := s.registry[registry]; ok { + return store, nil + } + + // check for wildcard match. + if _, zone, ok := strings.Cut(registry, "."); ok { + if store, ok := s.wildcard[zone]; ok { + return store, nil + } + } + // check for fallback. + if s.fallback != nil { + return s.fallback, nil + } + + return nil, errors.New("no matching store found") +} diff --git a/store_mux_test.go b/store_mux_test.go new file mode 100644 index 0000000..1dab29d --- /dev/null +++ b/store_mux_test.go @@ -0,0 +1,427 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ratify + +import ( + "context" + "reflect" + "testing" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +type testStore struct { + t *testing.T + name string + resolve func(*testing.T, string) (ocispec.Descriptor, error) + listReferrers func(*testing.T, string, []string, func([]ocispec.Descriptor) error) error + fetchBlob func(*testing.T, string, ocispec.Descriptor) ([]byte, error) + fetchManifest func(*testing.T, string, ocispec.Descriptor) ([]byte, error) +} + +func (s *testStore) Name() string { + return s.name +} + +func (s *testStore) Resolve(ctx context.Context, ref string) (ocispec.Descriptor, error) { + if s.resolve == nil { + s.t.Fatal("unexpected call to Store.Resolve") + } + return s.resolve(s.t, ref) +} + +func (s *testStore) ListReferrers(ctx context.Context, ref string, artifactTypes []string, fn func([]ocispec.Descriptor) error) error { + if s.listReferrers == nil { + s.t.Fatal("unexpected call to Store.ListReferrers") + } + return s.listReferrers(s.t, ref, artifactTypes, fn) +} + +func (s *testStore) FetchBlob(ctx context.Context, repo string, desc ocispec.Descriptor) ([]byte, error) { + if s.fetchBlob == nil { + s.t.Fatal("unexpected call to Store.FetchBlob") + } + return s.fetchBlob(s.t, repo, desc) +} + +func (s *testStore) FetchManifest(ctx context.Context, repo string, desc ocispec.Descriptor) ([]byte, error) { + if s.fetchManifest == nil { + s.t.Fatal("unexpected call to Store.FetchManifest") + } + return s.fetchManifest(s.t, repo, desc) +} + +func TestStoreMux(t *testing.T) { + var store any = &StoreMux{} + if _, ok := store.(Store); !ok { + t.Error("*StoreMux does not implement Store") + } +} + +func TestStoreMux_Name(t *testing.T) { + want := "test" + s := NewStoreMux(want) + if got := s.Name(); got != want { + t.Errorf("StoreMux.Name() = %v, want %v", got, want) + } +} + +func TestStoreMux_Resolve(t *testing.T) { + ctx := context.Background() + const pattern = "registry.test" + + tests := []struct { + name string + ref string + resolve func(*testing.T, string) (ocispec.Descriptor, error) + want ocispec.Descriptor + wantErr bool + }{ + { + name: "match store", + ref: "registry.test/foo:v1", + resolve: func(t *testing.T, ref string) (ocispec.Descriptor, error) { + return ocispec.DescriptorEmptyJSON, nil + }, + want: ocispec.DescriptorEmptyJSON, + }, + { + name: "no matching store found", + ref: "null.test/foo:v1", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewStoreMux("hello") + if err := store.Register(pattern, &testStore{ + t: t, + resolve: tt.resolve, + }); err != nil { + t.Fatalf("StoreMux.Register() error = %s, want nil", err) + } + got, err := store.Resolve(ctx, tt.ref) + if (err != nil) != tt.wantErr { + t.Errorf("StoreMux.Resolve() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StoreMux.Resolve() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStoreMux_ListReferrers(t *testing.T) { + ctx := context.Background() + const pattern = "registry.test" + + tests := []struct { + name string + ref string + listReferrers func(*testing.T, string, []string, func([]ocispec.Descriptor) error) error + want []ocispec.Descriptor + wantErr bool + }{ + { + name: "match store", + ref: "registry.test/foo:v1", + listReferrers: func(t *testing.T, _ string, _ []string, fn func([]ocispec.Descriptor) error) error { + return fn([]ocispec.Descriptor{ocispec.DescriptorEmptyJSON}) + }, + want: []ocispec.Descriptor{ocispec.DescriptorEmptyJSON}, + }, + { + name: "no matching store found", + ref: "null.test/foo:v1", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewStoreMux("hello") + if err := store.Register(pattern, &testStore{ + t: t, + listReferrers: tt.listReferrers, + }); err != nil { + t.Fatalf("StoreMux.Register() error = %s, want nil", err) + } + var got []ocispec.Descriptor + fn := func(referrers []ocispec.Descriptor) error { + got = append(got, referrers...) + return nil + } + if err := store.ListReferrers(ctx, tt.ref, nil, fn); (err != nil) != tt.wantErr { + t.Errorf("StoreMux.ListReferrers() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StoreMux.ListReferrers() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStoreMux_FetchBlob(t *testing.T) { + ctx := context.Background() + const pattern = "registry.test" + + tests := []struct { + name string + repo string + fetchBlob func(*testing.T, string, ocispec.Descriptor) ([]byte, error) + want []byte + wantErr bool + }{ + { + name: "match store", + repo: "registry.test/foo", + fetchBlob: func(t *testing.T, _ string, _ ocispec.Descriptor) ([]byte, error) { + return []byte("foo"), nil + }, + want: []byte("foo"), + }, + { + name: "no matching store found", + repo: "null.test/foo", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewStoreMux("hello") + if err := store.Register(pattern, &testStore{ + t: t, + fetchBlob: tt.fetchBlob, + }); err != nil { + t.Fatalf("StoreMux.Register() error = %s, want nil", err) + } + got, err := store.FetchBlob(ctx, tt.repo, ocispec.Descriptor{}) + if (err != nil) != tt.wantErr { + t.Errorf("StoreMux.FetchBlob() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StoreMux.FetchBlob() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStoreMux_FetchManifest(t *testing.T) { + ctx := context.Background() + const pattern = "registry.test" + + tests := []struct { + name string + repo string + fetchManifest func(*testing.T, string, ocispec.Descriptor) ([]byte, error) + want []byte + wantErr bool + }{ + { + name: "match store", + repo: "registry.test/foo", + fetchManifest: func(t *testing.T, _ string, _ ocispec.Descriptor) ([]byte, error) { + return []byte("foo"), nil + }, + want: []byte("foo"), + }, + { + name: "no matching store found", + repo: "null.test/foo", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewStoreMux("hello") + if err := store.Register(pattern, &testStore{ + t: t, + fetchManifest: tt.fetchManifest, + }); err != nil { + t.Fatalf("StoreMux.Register() error = %s, want nil", err) + } + got, err := store.FetchManifest(ctx, tt.repo, ocispec.Descriptor{}) + if (err != nil) != tt.wantErr { + t.Errorf("StoreMux.FetchManifest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StoreMux.FetchManifest() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStoreMux_Register(t *testing.T) { + type subTest struct { + name string + ref string + wantErr bool + } + tests := []struct { + name string + pattern string + tests []subTest + wantErr bool + }{ + { + name: "empty pattern", + pattern: "", + wantErr: true, + }, + { + name: "exact repository name", + pattern: "registry.test/foo", + tests: []subTest{ + { + name: "match", + ref: "registry.test/foo:v1", + }, + { + name: "no match", + ref: "registry.test/bar:v1", + wantErr: true, + }, + }, + }, + { + name: "exact registry name", + pattern: "registry.test", + tests: []subTest{ + { + name: "match", + ref: "registry.test/foo:v1", + }, + { + name: "no match", + ref: "registry.example/foo:v1", + wantErr: true, + }, + }, + }, + { + name: "wildcard registry name", + pattern: "*.test", + tests: []subTest{ + { + name: "match", + ref: "registry.test/foo:v1", + }, + { + name: "no match", + ref: "registry.example/foo:v1", + wantErr: true, + }, + { + name: "wildcard match in the middle not allowed", + ref: "another.registry.test/foo:v1", + wantErr: true, + }, + }, + }, + { + name: "top level wildcard registry name not allowed", + pattern: "*", + wantErr: true, + }, + { + name: "multiple wildcard registry name not allowed", + pattern: "*.*.example", + wantErr: true, + }, + { + name: "invalid wildcard registry name not allowed", + pattern: "example.*.test", + wantErr: true, + }, + { + name: "wildcard registry name with exact repository name not allowed", + pattern: "*.test/foo", + wantErr: true, + }, + { + name: "invalid registry name", + pattern: "example:abc", + wantErr: true, + }, + { + name: "invalid repository name", + pattern: "registry.test/FOO", + wantErr: true, + }, + { + name: "register full reference", + pattern: "registry.test/foo:v1", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewStoreMux("hello") + want := &testStore{t: t} + if err := store.Register(tt.pattern, want); (err != nil) != tt.wantErr { + t.Errorf("StoreMux.Register() error = %v, wantErr %v", err, tt.wantErr) + return + } + for _, ttt := range tt.tests { + t.Run(ttt.name, func(t *testing.T) { + got, err := store.storeFromReference(ttt.ref) + if (err != nil) != ttt.wantErr { + t.Errorf("StoreMux.storeFromReference() error = %v, wantErr %v", err, ttt.wantErr) + return + } + if !ttt.wantErr && got != want { + t.Errorf("StoreMux.storeFromReference() = %v, want %v", got, want) + } + }) + } + }) + } + + t.Run("nil store", func(t *testing.T) { + store := NewStoreMux("hello") + if err := store.Register("registry.test", nil); err == nil { + t.Error("StoreMux.Register() error = nil, wantErr") + } + }) +} + +func TestStoreMux_RegisterFallback(t *testing.T) { + t.Run("success fallback", func(t *testing.T) { + store := NewStoreMux("hello") + want := &testStore{t: t} + if err := store.RegisterFallback(want); err != nil { + t.Errorf("StoreMux.RegisterFallback() error = %v, wantErr nil", err) + return + } + got, err := store.storeFromReference("registry.test/foo:v1") + if err != nil { + t.Errorf("StoreMux.storeFromReference() error = %v, wantErr nil", err) + return + } + if got != want { + t.Errorf("StoreMux.storeFromReference() = %v, want %v", got, want) + } + }) + + t.Run("nil store", func(t *testing.T) { + store := NewStoreMux("hello") + if err := store.RegisterFallback(nil); err == nil { + t.Error("StoreMux.RegisterFallback() error = nil, wantErr") + } + }) +} From 50e777296faa9b7a122f6e818b571cf88224af7d Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Wed, 5 Feb 2025 18:48:01 +0800 Subject: [PATCH 2/2] docs: add one more example Signed-off-by: Shiwei Zhang --- example_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/example_test.go b/example_test.go index 3ed445b..95989fd 100644 --- a/example_test.go +++ b/example_test.go @@ -17,6 +17,8 @@ package ratify_test import ( "context" + "crypto/tls" + "net/http" "testing/fstest" "github.com/ratify-project/ratify-go" @@ -84,3 +86,70 @@ func ExampleStoreMux() { } // Output: } + +// ExampleStoreMux_mixRegistryStore demonstrates how to access different +// registries with different network configurations using a single store by +// multiplexing. +func ExampleStoreMux_mixRegistryStore() { + // Create a new store multiplexer + mux := ratify.NewStoreMux("multiplexer") + + // Create a global registry store with default options. + // Developers should replace the default options with actual configuration. + store, err := ratify.NewRegistryStore("registry", ratify.RegistryStoreOptions{}) + if err != nil { + panic(err) + } + if err := mux.RegisterFallback(store); err != nil { + panic(err) + } + + // Create a registry store for local registry. + // A local registry is accessed over plain HTTP unlike the global registry. + store, err = ratify.NewRegistryStore("local", ratify.RegistryStoreOptions{ + PlainHTTP: true, + }) + if err != nil { + panic(err) + } + if err := mux.Register("localhost:5000", store); err != nil { + panic(err) + } + + // Create a registry store with client certificate authentication. + store, err = ratify.NewRegistryStore("cert", ratify.RegistryStoreOptions{ + HTTPClient: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{ + // add client certificate with private key + }, + }, + }, + }, + }) + if err != nil { + panic(err) + } + if err := mux.Register("private.registry.example", store); err != nil { + panic(err) + } + + // Create a registry store for an insecure registry. + store, err = ratify.NewRegistryStore("insecure", ratify.RegistryStoreOptions{ + HTTPClient: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + }, + }) + if err != nil { + panic(err) + } + if err := mux.Register("insecure.registry.example", store); err != nil { + panic(err) + } + // Output: +}