From 24a088584b72cf9064e9836458a4c50f5398f1d1 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 4 Jan 2023 21:12:36 +0800 Subject: [PATCH] test: update to use oras-go copy Signed-off-by: Junjie Gao --- Makefile | 2 +- test/e2e/go.mod | 4 +- test/e2e/go.sum | 11 +- test/e2e/internal/notation/file.go | 17 +- test/e2e/internal/notation/host.go | 75 +++++--- test/e2e/internal/notation/init.go | 73 ++++---- test/e2e/internal/notation/key.go | 24 +-- test/e2e/internal/notation/registry.go | 170 ++++++++++-------- test/e2e/internal/utils/host.go | 17 +- test/e2e/internal/utils/matcher.go | 6 + .../{checker.go => validator/validator.go} | 2 +- test/e2e/run.sh | 101 ++++++++--- test/e2e/scripts/zot.sh | 22 +++ test/e2e/suite/command/sign.go | 16 +- ...20bc465c1a6242cc68e82e12c152bc10d541cce578 | 1 + ...aa8f43b6ba805e677a561558143b6e92981c487339 | 1 + ...f1077d7663cdda573940d89fdb10b750788250d775 | 1 + ...bf4711de48e5fa3ebdacad3403e61777a9e1a53b6f | 1 + .../oci_layout/e2e-valid-signature/index.json | 1 + .../oci_layout/e2e-valid-signature/oci-layout | 1 + 20 files changed, 327 insertions(+), 219 deletions(-) rename test/e2e/internal/utils/{checker.go => validator/validator.go} (95%) create mode 100644 test/e2e/scripts/zot.sh create mode 100644 test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/0d10372ee4448bf319929720bc465c1a6242cc68e82e12c152bc10d541cce578 create mode 100644 test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/273243a7a64e9312761ca0aa8f43b6ba805e677a561558143b6e92981c487339 create mode 100644 test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/a10cfe44db01fffab281c3f1077d7663cdda573940d89fdb10b750788250d775 create mode 100644 test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/cc2ae4e91a31a77086edbdbf4711de48e5fa3ebdacad3403e61777a9e1a53b6f create mode 100644 test/e2e/testdata/registry/oci_layout/e2e-valid-signature/index.json create mode 100644 test/e2e/testdata/registry/oci_layout/e2e-valid-signature/oci-layout diff --git a/Makefile b/Makefile index decdbdaff..8cb73b83d 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ test: vendor check-line-endings ## run unit tests e2e: build ## build notation cli and run e2e test NOTATION_BIN_PATH=`pwd`/bin/$(COMMANDS); \ cd ./test/e2e; \ - ./run.sh $$NOTATION_BIN_PATH + ./run.sh zot $$NOTATION_BIN_PATH .PHONY: clean clean: diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 44c2bbe77..5b75e9e8d 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/onsi/ginkgo/v2 v2.3.0 github.com/onsi/gomega v1.22.1 - oras.land/oras-go/v2 v2.0.0-rc.5 + oras.land/oras-go/v2 v2.0.0-rc.6 ) require ( @@ -13,7 +13,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect golang.org/x/net v0.1.0 // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 904cc7e9f..e518d8e54 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -1,8 +1,6 @@ -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/onsi/ginkgo/v2 v2.3.0 h1:kUMoxMoQG3ogk/QWyKh3zibV7BKZ+xBpWil1cTylVqc= github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= @@ -13,17 +11,16 @@ github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7X github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.0.0-rc.5 h1:enT2ZMNo383bH3INm1/+mw4d09AaMbqx0BMhsgEDUSg= -oras.land/oras-go/v2 v2.0.0-rc.5/go.mod h1:YGHvWBGuqRlZgUyXUIoKsR3lcuCOb3DAtG0SEsEw1iY= +oras.land/oras-go/v2 v2.0.0-rc.6 h1:jGWysqm8flq+X0Vj8bZ6rkASAqTab5k18Mx9hEjFc8g= +oras.land/oras-go/v2 v2.0.0-rc.6/go.mod h1:iVExH1NxrccIxjsiq17L91WCZ4KIw6jVQyCLsZsu1gc= diff --git a/test/e2e/internal/notation/file.go b/test/e2e/internal/notation/file.go index 7913d66b3..88f28382d 100644 --- a/test/e2e/internal/notation/file.go +++ b/test/e2e/internal/notation/file.go @@ -22,7 +22,7 @@ func copyDir(src, dst string) error { dstPath := filepath.Join(dst, relPath) if d.IsDir() { - return os.MkdirAll(dstPath, os.ModePerm) + return os.MkdirAll(dstPath, 0731) } return copyFile(srcPath, dstPath) }) @@ -36,6 +36,11 @@ func copyFile(src, dst string) error { } defer in.Close() + si, err := in.Stat() + if err != nil { + return err + } + out, err := os.Create(dst) if err != nil { return err @@ -49,20 +54,14 @@ func copyFile(src, dst string) error { if err := out.Sync(); err != nil { return err } - - si, err := in.Stat() - if err != nil { - return err - } return out.Chmod(si.Mode()) } // saveJSON marshals the data and save to the given path. func saveJSON(data any, path string) error { - b, err := json.Marshal(data) + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return err } - - return os.WriteFile(path, b, 0644) + return json.NewEncoder(f).Encode(data) } diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index 63474a6b4..90421c150 100644 --- a/test/e2e/internal/notation/host.go +++ b/test/e2e/internal/notation/host.go @@ -7,45 +7,70 @@ import ( "github.com/notaryproject/notation/test/e2e/internal/utils" ) +// CoreTestFunc is the test function running in a VirtualHost. +// +// notation is an Executor isolated by $XDG_CONFIG_HOME. +// artifact is a generated artifact in a new repository. +// vhost is the VirtualHost instance. +type CoreTestFunc func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) + // Host creates a virtualized notation testing host by modify // the "XDG_CONFIG_HOME" environment variable of the Executor. // // options is the required testing environment options // fn is the callback function containing the testing logic. -func Host(options []utils.HostOption, fn func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost)) { - // create a vhost - vhost, err := utils.NewVirtualHost(NotationBinPath, CreateNotationDirOption()) +func Host(options []utils.HostOption, fn CoreTestFunc) { + // create a notation vhost + vhost, err := createNotationHost(NotationBinPath, options...) if err != nil { panic(err) } - // set additional options - vhost.SetOption(options...) - // generate a repository with an artifact - artifact := GenerateArtifact() + artifact := GenerateArtifact("", "") // run the main logic fn(vhost.Executor, artifact, vhost) +} + +// OldNotation create an old version notation ExecOpts in a VirtualHost +// for testing forward compatibility. +func OldNotation(options ...utils.HostOption) *utils.ExecOpts { + if len(options) == 0 { + options = BaseOptions() + } - // remove the generated repository and artifact - if err := artifact.Remove(); err != nil { + vhost, err := createNotationHost(NotationOldBinPath, options...) + if err != nil { panic(err) } + + return vhost.Executor } -// Opts is a grammar sugar to generate a list of HostOption +func createNotationHost(path string, options ...utils.HostOption) (*utils.VirtualHost, error) { + vhost, err := utils.NewVirtualHost(path, CreateNotationDirOption()) + if err != nil { + return nil, err + } + + // set additional options + vhost.SetOption(options...) + return vhost, nil +} + +// Opts is a grammar sugar to generate a list of HostOption. func Opts(options ...utils.HostOption) []utils.HostOption { return options } -// BaseOptions returns a list of base Options for a valid notation +// BaseOptions returns a list of base Options for a valid notation. // testing environment. func BaseOptions() []utils.HostOption { return Opts( AuthOption("", ""), - AddTestKeyOption(), - AddTestTrustStoreOption(), + AddKeyOption("e2e.key", "e2e.crt"), + AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustPolicyOption("trustpolicy.json"), ) } @@ -53,7 +78,7 @@ func BaseOptions() []utils.HostOption { // CreateNotationDirOption creates the notation directory in temp user dir. func CreateNotationDirOption() utils.HostOption { return func(vhost *utils.VirtualHost) error { - return os.MkdirAll(vhost.UserPath(NotationDirName), os.ModePerm) + return os.MkdirAll(vhost.AbsolutePath(NotationDirName), os.ModePerm) } } @@ -71,19 +96,19 @@ func AuthOption(username, password string) utils.HostOption { } } -// AddTestKeyOption adds the test signingkeys.json, key and cert files to +// AddKeyOption adds the test signingkeys.json, key and cert files to // the notation directory. -func AddTestKeyOption() utils.HostOption { +func AddKeyOption(keyName, certName string) utils.HostOption { return func(vhost *utils.VirtualHost) error { - return AddTestKeyPairs(vhost.UserPath(NotationDirName)) + return AddTestKeyPairs(vhost.AbsolutePath(NotationDirName), keyName, certName) } } -// AddTestTrustStoreOption added the test cert to the trust store. -func AddTestTrustStoreOption() utils.HostOption { +// AddTrustStoreOption added the test cert to the trust store. +func AddTrustStoreOption(namedstore string, srcCertPath string) utils.HostOption { return func(vhost *utils.VirtualHost) error { vhost.Executor. - Exec("cert", "add", "--type", "ca", "--store", "e2e", NotationE2ECertPath). + Exec("cert", "add", "--type", "ca", "--store", namedstore, srcCertPath). MatchKeyWords("Successfully added following certificates") return nil } @@ -94,7 +119,7 @@ func AddTrustPolicyOption(trustpolicyName string) utils.HostOption { return func(vhost *utils.VirtualHost) error { return copyFile( filepath.Join(NotationE2ETrustPolicyDir, trustpolicyName), - vhost.UserPath(NotationDirName, NotationTrustPolicyName), + vhost.AbsolutePath(NotationDirName, TrustPolicyName), ) } } @@ -102,8 +127,8 @@ func AddTrustPolicyOption(trustpolicyName string) utils.HostOption { // authEnv creates an auth info // (By setting $NOTATION_USERNAME and $NOTATION_PASSWORD) func authEnv(username, password string) map[string]string { - env := make(map[string]string) - env["NOTATION_USERNAME"] = username - env["NOTATION_PASSWORD"] = password - return env + return map[string]string{ + "NOTATION_USERNAME": username, + "NOTATION_PASSWORD": password, + } } diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index 641f9782c..60eef8552 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -10,35 +10,38 @@ import ( ) const ( - NotationDirName = "notation" - NotationTrustPolicyName = "trustpolicy.json" + NotationDirName = "notation" + TrustPolicyName = "trustpolicy.json" + TrustStoreDirName = "truststore" + TrustStoreTypeCA = "ca" ) const ( - envRegistryHost = "NOTATION_E2E_REGISTRY_HOST" - envRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" - envRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" - envNotationBinPath = "NOTATION_E2E_BINARY_PATH" - envNotationE2EKeyPath = "NOTATION_E2E_KEY_PATH" - envNotationE2ECertPath = "NOTATION_E2E_CERT_PATH" - envNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" - envOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" - envTestRepo = "NOTATION_E2E_TEST_REPO" - envTestTag = "NOTATION_E2E_TEST_TAG" - envRegistryStoragePath = "REGISTRY_STORAGE_PATH" + envKeyRegistryHost = "NOTATION_E2E_REGISTRY_HOST" + envKeyRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" + envKeyRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" + envKeyNotationBinPath = "NOTATION_E2E_BINARY_PATH" + envKeyNotationOldBinPath = "NOTATION_E2E_OLD_BINARY_PATH" + envKeyNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" + envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" + envKeyTestRepo = "NOTATION_E2E_TEST_REPO" + envKeyTestTag = "NOTATION_E2E_TEST_TAG" ) var ( - NotationBinPath string - NotationE2EKeyPath string - NotationE2ECertPath string + // NotationBinPath is the notation binary path. + NotationBinPath string + // NotationOldBinPath is the path of an old version notation binary for + // testing forward compatibility. + NotationOldBinPath string NotationE2EConfigPath string + NotationE2ELocalKeysDir string NotationE2ETrustPolicyDir string ) var ( OCILayoutPath string - TestRepo string + TestRepoUri string TestTag string RegistryStoragePath string ) @@ -50,41 +53,35 @@ func init() { } func setUpRegistry() { - setValue(envRegistryHost, &TestRegistry.Host) - fmt.Printf("Testing using registry host: %s\n", TestRegistry.Host) + setValue(envKeyRegistryHost, &TestRegistry.Host) + setValue(envKeyRegistryUsername, &TestRegistry.Username) + setValue(envKeyRegistryPassword, &TestRegistry.Password) - setValue(envRegistryUsername, &TestRegistry.Username) - fmt.Printf("Testing using registry username: %s\n", TestRegistry.Username) - - setValue(envRegistryPassword, &TestRegistry.Password) - fmt.Printf("Testing using registry password: %s\n", TestRegistry.Password) + setPathValue(envKeyOCILayoutPath, &OCILayoutPath) + setValue(envKeyTestRepo, &TestRepoUri) + setValue(envKeyTestTag, &TestTag) testImage := &Artifact{ Registry: &TestRegistry, - Repo: testRepo, - Tag: testTag, + Repo: TestRepoUri, + Tag: TestTag, } if err := testImage.Validate(); err != nil { - panic(fmt.Sprintf("E2E setup failed: %v", err)) + panic(fmt.Sprintf("registry setup failed: %v", err)) } } func setUpNotationValues() { // set Notation binary path - setPathValue(envNotationBinPath, &NotationBinPath) + setPathValue(envKeyNotationBinPath, &NotationBinPath) + setPathValue(envKeyNotationOldBinPath, &NotationOldBinPath) // set Notation configuration paths - setPathValue(envNotationE2EKeyPath, &NotationE2EKeyPath) - setPathValue(envNotationE2ECertPath, &NotationE2ECertPath) - setPathValue(envNotationConfigPath, &NotationE2EConfigPath) + setPathValue(envKeyNotationConfigPath, &NotationE2EConfigPath) NotationE2ETrustPolicyDir = filepath.Join(NotationE2EConfigPath, "trustpolicys") + NotationE2ELocalKeysDir = filepath.Join(NotationE2EConfigPath, LocalKeysDirName) - // set registry values - setPathValue(envRegistryStoragePath, &RegistryStoragePath) - setPathValue(envOCILayoutPath, &OCILayoutPath) - setValue(envTestRepo, &TestRepo) - setValue(envTestTag, &TestTag) } func setPathValue(envKey string, value *string) { @@ -95,8 +92,8 @@ func setPathValue(envKey string, value *string) { } func setValue(envKey string, value *string) { - *value = os.Getenv(envKey) - if *value == "" { + if *value = os.Getenv(envKey); *value == "" { panic(fmt.Sprintf("env %s is empty", envKey)) } + fmt.Printf("set test value $%s=%s\n", envKey, *value) } diff --git a/test/e2e/internal/notation/key.go b/test/e2e/internal/notation/key.go index 8252a5abf..86b4568dd 100644 --- a/test/e2e/internal/notation/key.go +++ b/test/e2e/internal/notation/key.go @@ -6,14 +6,14 @@ import ( ) const ( - signingKeysName = "signingkeys.json" - localkeysDirName = "localkeys" + SigningKeysFileName = "signingkeys.json" + LocalKeysDirName = "localkeys" ) // X509KeyPair contains the paths of a public/private key pair files. type X509KeyPair struct { - KeyPath string `json:"keyPath,omitempty"` - CertificatePath string `json:"certPath,omitempty"` + KeyPath string `json:"keyPath"` + CertificatePath string `json:"certPath"` } // KeySuite is a named key suite. @@ -30,23 +30,23 @@ type SigningKeys struct { // AddTestKeyPairs creates the signingkeys.json file and the localkeys directory // with e2e.key and e2e.crt -func AddTestKeyPairs(dir string) error { +func AddTestKeyPairs(dir, keyName, certName string) error { // create signingkeys.json files if err := saveJSON( genTestSigningKey(dir), - filepath.Join(dir, signingKeysName)); err != nil { + filepath.Join(dir, SigningKeysFileName)); err != nil { return err } // create localkeys directory - localKeysDir := filepath.Join(dir, localkeysDirName) - if err := os.MkdirAll(localKeysDir, os.ModePerm); err != nil { - return err - } - if err := copyFile(NotationE2EKeyPath, filepath.Join(localKeysDir, "e2e.key")); err != nil { + localKeysDir := filepath.Join(dir, LocalKeysDirName) + os.MkdirAll(localKeysDir, 0731) + + // copy key and cert files + if err := copyFile(filepath.Join(NotationE2ELocalKeysDir, keyName), filepath.Join(localKeysDir, "e2e.key")); err != nil { return err } - return copyFile(NotationE2ECertPath, filepath.Join(localKeysDir, "e2e.crt")) + return copyFile(filepath.Join(NotationE2ELocalKeysDir, certName), filepath.Join(localKeysDir, "e2e.crt")) } func genTestSigningKey(dir string) *SigningKeys { diff --git a/test/e2e/internal/notation/registry.go b/test/e2e/internal/notation/registry.go index 3581132f2..4d073fa78 100644 --- a/test/e2e/internal/notation/registry.go +++ b/test/e2e/internal/notation/registry.go @@ -3,32 +3,65 @@ package notation import ( "context" "fmt" + "math" + "math/rand" "net/url" "os" "path/filepath" - "sync/atomic" + "time" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content/oci" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" ) -var ( - repoId int64 = 0 -) - -const ( - testRepo = "e2e" - testTag = "v1" -) - type Registry struct { Host string Username string Password string } -var TestRegistry Registry +// CreateArtifact copies a local OCI layout to the a registry to create +// a new artifact with a new repository. +// +// srcRepoName is the repo name in ./testdata/registry/oci_layout folder. +// destRepoName is the repo name to be created in the registry. +func (r *Registry) CreateArtifact(srcRepoName, destRepoName string) (*Artifact, error) { + ctx := context.Background() + // create a local store from OCI layout directory. + srcStore, err := oci.NewFromFS(ctx, os.DirFS(filepath.Join(OCILayoutPath, srcRepoName))) + if err != nil { + return nil, err + } + + // create the artifact struct + artifact := &Artifact{ + Registry: r, + Repo: destRepoName, + Tag: TestTag, + } + if err := artifact.Validate(); err != nil { + panic(err) + } + + // create the remote.repository + destRepo, err := newRepository(artifact.ReferenceWithTag()) + if err != nil { + return nil, err + } + + // copy data + desc, err := oras.ExtendedCopy(ctx, srcStore, artifact.Tag, destRepo, "", oras.DefaultExtendedCopyOptions) + if err != nil { + return nil, err + } + artifact.Digest = desc.Digest.String() + return artifact, err +} + +var TestRegistry = Registry{} type Artifact struct { *Registry @@ -37,34 +70,22 @@ type Artifact struct { Digest string } -// GenerateArtifact generates a new image with a new repository. -func GenerateArtifact() *Artifact { - // generate new newRepo - newRepo := fmt.Sprintf("%s-%d", testRepo, genRepoId()) - - // copy oci layout to the new repo - if err := copyDir(filepath.Join(OCILayoutPath, testRepo), filepath.Join(RegistryStoragePath, newRepo)); err != nil { - panic(err) - } - - artifact := &Artifact{ - Registry: &Registry{ - Host: TestRegistry.Host, - Username: TestRegistry.Username, - Password: TestRegistry.Password, - }, - Repo: newRepo, - Tag: "v1", +// GenerateArtifact generates a new artifact with a new repository by copying +// the source repository in the OCILayoutPath to be a new repository. +func GenerateArtifact(srcRepo, newRepo string) *Artifact { + if srcRepo == "" { + srcRepo = TestRepoUri } - if err := artifact.Validate(); err != nil { - panic(err) + if newRepo == "" { + // generate new repo + newRepo = newRepoName() } - if err := artifact.fetchDigest(); err != nil { + artifact, err := TestRegistry.CreateArtifact(srcRepo, newRepo) + if err != nil { panic(err) } - return artifact } @@ -83,14 +104,50 @@ func (r *Artifact) Validate() error { return nil } -func (r *Artifact) fetchDigest() error { - // create repository - ref, err := registry.ParseReference(r.ReferenceWithTag()) - if err != nil { - return err +// ReferenceWithTag returns the /: +func (r *Artifact) ReferenceWithTag() string { + return fmt.Sprintf("%s/%s:%s", r.Host, r.Repo, r.Tag) +} +// ReferenceWithDigest returns the /@: +func (r *Artifact) ReferenceWithDigest() string { + return fmt.Sprintf("%s/%s@%s", r.Host, r.Repo, r.Digest) +} + +func newRepoName() string { + var newRepo string + for { + // set the seed with nanosecond precision. + rand.Seed(time.Now().UnixNano()) + newRepo = fmt.Sprintf("%s-%d", TestRepoUri, rand.Intn(math.MaxInt32)) + + _, err := os.Stat(filepath.Join(RegistryStoragePath, newRepo)) + if err != nil { + if os.IsNotExist(err) { + // newRepo doesn't exist. + break + } + panic(err) + } } - authClient := &auth.Client{ + return newRepo +} + +func newRepository(reference string) (*remote.Repository, error) { + ref, err := registry.ParseReference(reference) + if err != nil { + return nil, err + } + + return &remote.Repository{ + Client: authClient(ref), + Reference: ref, + PlainHTTP: true, + }, nil +} + +func authClient(ref registry.Reference) *auth.Client { + return &auth.Client{ Credential: func(ctx context.Context, registry string) (auth.Credential, error) { switch registry { case ref.Host(): @@ -105,39 +162,4 @@ func (r *Artifact) fetchDigest() error { Cache: auth.NewCache(), ClientID: "notation", } - repo := &remote.Repository{ - Client: authClient, - Reference: ref, - PlainHTTP: true, - } - - // resolve descriptor - descriptor, err := repo.Resolve(context.Background(), r.ReferenceWithTag()) - if err != nil { - return err - } - - // set digest - r.Digest = descriptor.Digest.String() - return nil -} - -// ReferenceWithTag returns the /: -func (r *Artifact) ReferenceWithTag() string { - return fmt.Sprintf("%s/%s:%s", r.Host, r.Repo, r.Tag) -} - -// ReferenceWithDigest returns the /@: -func (r *Artifact) ReferenceWithDigest() string { - return fmt.Sprintf("%s/%s@%s", r.Host, r.Repo, r.Digest) -} - -// Reference removes the the repository of the artifact. -func (r *Artifact) Remove() error { - return os.RemoveAll(filepath.Join(OCILayoutPath, r.Repo)) -} - -// genRepoId returns a new repoId -func genRepoId() int64 { - return atomic.AddInt64(&repoId, 1) } diff --git a/test/e2e/internal/utils/host.go b/test/e2e/internal/utils/host.go index a420d300f..32a65f71a 100644 --- a/test/e2e/internal/utils/host.go +++ b/test/e2e/internal/utils/host.go @@ -21,12 +21,8 @@ func NewVirtualHost(binPath string, options ...HostOption) (*VirtualHost, error) Executor: Binary(binPath), } - var err error // setup a temp user directory vhost.userDir = ginkgo.GinkgoT().TempDir() - if err != nil { - return nil, err - } // set user dir environment variables vhost.UpdateEnv(UserConfigEnv(vhost.userDir)) @@ -36,9 +32,9 @@ func NewVirtualHost(binPath string, options ...HostOption) (*VirtualHost, error) return vhost, nil } -// UserPath returns the path of the absolute path for the given path +// AbsolutePath returns the path of the absolute path for the given path // elements that are relative to the user directory. -func (h *VirtualHost) UserPath(elem ...string) string { +func (h *VirtualHost) AbsolutePath(elem ...string) string { userElem := []string{h.userDir} userElem = append(userElem, elem...) return filepath.Join(userElem...) @@ -46,9 +42,6 @@ func (h *VirtualHost) UserPath(elem ...string) string { // UpdateEnv updates the environment variables for the VirtualHost. func (h *VirtualHost) UpdateEnv(env map[string]string) { - if env == nil { - return - } if h.env == nil { h.env = make(map[string]string) } @@ -75,7 +68,7 @@ type HostOption func(vhost *VirtualHost) error // user config dir (By setting $XDG_CONFIG_HOME). func UserConfigEnv(dir string) map[string]string { // create and set user dir for linux - env := make(map[string]string) - env["XDG_CONFIG_HOME"] = dir - return env + return map[string]string{ + "XDG_CONFIG_HOME": dir, + } } diff --git a/test/e2e/internal/utils/matcher.go b/test/e2e/internal/utils/matcher.go index 0cfc03816..103d8d9dc 100644 --- a/test/e2e/internal/utils/matcher.go +++ b/test/e2e/internal/utils/matcher.go @@ -42,6 +42,12 @@ func (m *Matcher) MatchKeyWords(keywords ...string) *Matcher { return m } +// MatchErrKeyWords matches given keywords with the stderr. +func (m *Matcher) MatchErrKeyWords(keywords ...string) *Matcher { + matchKeyWords(m.stderr, keywords) + return m +} + // MatchErrKeyWords matches given keywords with the stderr. func matchKeyWords(content string, keywords []string) { var missed []string diff --git a/test/e2e/internal/utils/checker.go b/test/e2e/internal/utils/validator/validator.go similarity index 95% rename from test/e2e/internal/utils/checker.go rename to test/e2e/internal/utils/validator/validator.go index 8729ccec9..e3e1932f2 100644 --- a/test/e2e/internal/utils/checker.go +++ b/test/e2e/internal/utils/validator/validator.go @@ -1,4 +1,4 @@ -package utils +package validator import ( "os" diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 2a645b5bd..2908a16c7 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -1,45 +1,90 @@ #!/bin/bash -e -export NOTATION_E2E_BINARY_PATH=$(realpath $1) +SUPPORTED_REGISTRY=("zot") + +function help { + echo "Usage" + echo " run.sh [old-notation-binary-path]" + echo "" + echo "Arguments" + echo " registry-name is the registry to use for running the E2E test. Currently support: ${SUPPORTED_REGISTRY[@]}." + echo " notation-binary-path is the path of the notation executable binary file." + echo " old-notation-binary-path is the path of an old notation executable bianry file. If it is not set, an RC.1 Notation will be downloaded automatically." +} + +# check registry name +REGISTRY_NAME=$1 +if [ -z "$REGISTRY_NAME" ]; then + echo "registry name is missing." + help + exit 1 +fi + +# check notation binary path. +export NOTATION_E2E_BINARY_PATH=$(if [ ! -z "$2" ]; then realpath $2; fi) if [ ! -f "$NOTATION_E2E_BINARY_PATH" ];then - echo "run.sh " + echo "notation binary path doesn't exist." + help exit 1 fi +# check old notation binary path for forward compatibility test. +export NOTATION_E2E_OLD_BINARY_PATH=$(if [ ! -z "$3" ]; then realpath $3; fi) +if [ ! -f "$NOTATION_E2E_OLD_BINARY_PATH" ];then + OLD_NOTATION_DIR=/tmp/notation_old + export NOTATION_E2E_OLD_BINARY_PATH=$OLD_NOTATION_DIR/notation + mkdir -p $OLD_NOTATION_DIR + + echo "Old notation binary path doesn't exist." + echo "Try to use old notation binary at $NOTATION_E2E_OLD_BINARY_PATH" + + if [ ! -f $NOTATION_E2E_OLD_BINARY_PATH ]; then + TAG=1.0.0-rc.1 # without 'v' + echo "Didn't find old notation binary locally. Try to download notation v$TAG." + + TAR_NAME=notation_${TAG}_linux_amd64.tar.gz + URL=https://github.com/notaryproject/notation/releases/download/v${TAG}/$TAR_NAME + wget $URL -P $OLD_NOTATION_DIR + tar -xf $OLD_NOTATION_DIR/$TAR_NAME -C $OLD_NOTATION_DIR + + if [ ! -f $NOTATION_E2E_OLD_BINARY_PATH ]; then + echo "Failed to download old notation binary for forward compatibility test." + exit 1 + fi + echo "Downloaded notation v$TAG at $NOTATION_E2E_OLD_BINARY_PATH" + fi +fi + # install dependency -go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo +go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@v2.3.0 -# set environment variable for E2E testing -REG_HOST=localhost -REG_PORT=5000 -ZOT_CONTAINER_NAME=zot - -export NOTATION_E2E_REGISTRY_HOST=$REG_HOST:$REG_PORT -export NOTATION_E2E_REGISTRY_USERNAME=testuser -export NOTATION_E2E_REGISTRY_PASSWORD=testpassword -export NOTATION_E2E_KEY_PATH=`pwd`/testdata/config/localkeys/e2e.key -export NOTATION_E2E_CERT_PATH=`pwd`/testdata/config/localkeys/e2e.crt -export NOTATION_E2E_CONFIG_PATH=`pwd`/testdata/config -export NOTATION_E2E_OCI_LAYOUT_PATH=`pwd`/testdata/registry/oci_layout -export NOTATION_E2E_TEST_REPO=e2e -export NOTATION_E2E_TEST_TAG=v1 -export REGISTRY_STORAGE_PATH=/tmp/zot-registry +# setup registry +case $REGISTRY_NAME in + + "zot") + source ./scripts/zot.sh + ;; -# create temperory directory for Zot storage -mkdir -p /tmp/zot-registry && echo "Zot storage path: $REGISTRY_STORAGE_PATH created" + *) + echo "invalid registry" + help + exit 1 + ;; +esac -# start zot -docker run -d -p $REG_PORT:$REG_PORT -it --name $ZOT_CONTAINER_NAME \ - --mount type=bind,source=`pwd`/testdata/registry/zot/,target=/etc/zot \ - --mount type=bind,source=$REGISTRY_STORAGE_PATH,target=/var/lib/registry \ - --rm ghcr.io/project-zot/zot-minimal-linux-amd64:latest +setup_registry -# stop container and clean zot storage directory when exit +# defer cleanup registry function cleanup { - docker container stop $ZOT_CONTAINER_NAME 1>/dev/null && echo "Zot stopped" - rm -rf $REGISTRY_STORAGE_PATH && echo "Zot storage path: $REGISTRY_STORAGE_PATH deleted" + cleanup_registry } trap cleanup EXIT +# set environment variable for E2E testing +export NOTATION_E2E_CONFIG_PATH=`pwd`/testdata/config +export NOTATION_E2E_OCI_LAYOUT_PATH=`pwd`/testdata/registry/oci_layout +export NOTATION_E2E_TEST_REPO=e2e +export NOTATION_E2E_TEST_TAG=v1 + # run tests ginkgo -r -p -v diff --git a/test/e2e/scripts/zot.sh b/test/e2e/scripts/zot.sh new file mode 100644 index 000000000..787016b0b --- /dev/null +++ b/test/e2e/scripts/zot.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e +# this script called by ../run.sh + +REG_HOST=localhost +REG_PORT=5000 +ZOT_CONTAINER_NAME=zot + +# set environment variables for E2E testing +export NOTATION_E2E_REGISTRY_HOST=$REG_HOST:$REG_PORT +export NOTATION_E2E_REGISTRY_USERNAME=testuser +export NOTATION_E2E_REGISTRY_PASSWORD=testpassword + +function setup_registry { + # start zot + docker run -d -p $REG_PORT:$REG_PORT -it --name $ZOT_CONTAINER_NAME \ + --mount type=bind,source=`pwd`/testdata/registry/zot/,target=/etc/zot \ + --rm ghcr.io/project-zot/zot-minimal-linux-amd64:latest +} + +function cleanup_registry { + docker container stop $ZOT_CONTAINER_NAME 1>/dev/null && echo "Zot stopped" +} \ No newline at end of file diff --git a/test/e2e/suite/command/sign.go b/test/e2e/suite/command/sign.go index 421d6021d..3f40921df 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -7,26 +7,22 @@ import ( ) var _ = Describe("notation sign", func() { - It("sign in JWS signature format", func() { + It("with JWS signature format", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - notation.WithDescription("sign with JWS"). - Exec("sign", artifact.ReferenceWithTag(), "--signature-format", "jws"). + notation.Exec("sign", artifact.ReferenceWithTag(), "--signature-format", "jws"). MatchKeyWords("Successfully signed") - notation.WithDescription("verify JWS signature"). - Exec("verify", artifact.ReferenceWithTag()). + OldNotation().Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords("Successfully verified") }) }) - It("sign in COSE signature format", func() { + It("with COSE signature format", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - notation.WithDescription("sign with COSE"). - Exec("sign", artifact.ReferenceWithTag(), "--signature-format", "cose"). + notation.Exec("sign", artifact.ReferenceWithTag(), "--signature-format", "cose"). MatchKeyWords("Successfully signed") - notation.WithDescription("verify COSE signature"). - Exec("verify", artifact.ReferenceWithTag()). + OldNotation().Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords("Successfully verified") }) }) diff --git a/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/0d10372ee4448bf319929720bc465c1a6242cc68e82e12c152bc10d541cce578 b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/0d10372ee4448bf319929720bc465c1a6242cc68e82e12c152bc10d541cce578 new file mode 100644 index 000000000..765bc38c9 --- /dev/null +++ b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/0d10372ee4448bf319929720bc465c1a6242cc68e82e12c152bc10d541cce578 @@ -0,0 +1 @@ +Awesome Notation diff --git a/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/273243a7a64e9312761ca0aa8f43b6ba805e677a561558143b6e92981c487339 b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/273243a7a64e9312761ca0aa8f43b6ba805e677a561558143b6e92981c487339 new file mode 100644 index 000000000..f9aebfb13 --- /dev/null +++ b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/273243a7a64e9312761ca0aa8f43b6ba805e677a561558143b6e92981c487339 @@ -0,0 +1 @@ +{"mediaType":"application/vnd.oci.artifact.manifest.v1+json","artifactType":"application/vnd.cncf.notary.signature","blobs":[{"mediaType":"application/jose+json","digest":"sha256:a10cfe44db01fffab281c3f1077d7663cdda573940d89fdb10b750788250d775","size":2074}],"subject":{"mediaType":"application/vnd.oci.artifact.manifest.v1+json","digest":"sha256:cc2ae4e91a31a77086edbdbf4711de48e5fa3ebdacad3403e61777a9e1a53b6f","size":417},"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"e38aea13f2b051f7183cca64a51ee676f4d4456f41d9fecf15eb367efc7343b9\"]","org.opencontainers.artifact.created":"2022-12-30T03:11:19Z"}} \ No newline at end of file diff --git a/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/a10cfe44db01fffab281c3f1077d7663cdda573940d89fdb10b750788250d775 b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/a10cfe44db01fffab281c3f1077d7663cdda573940d89fdb10b750788250d775 new file mode 100644 index 000000000..cd054b936 --- /dev/null +++ b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/a10cfe44db01fffab281c3f1077d7663cdda573940d89fdb10b750788250d775 @@ -0,0 +1 @@ +{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6Y2MyYWU0ZTkxYTMxYTc3MDg2ZWRiZGJmNDcxMWRlNDhlNWZhM2ViZGFjYWQzNDAzZTYxNzc3YTllMWE1M2I2ZiIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5vY2kuYXJ0aWZhY3QubWFuaWZlc3QudjEranNvbiIsInNpemUiOjQxN319","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIyLTEyLTMwVDExOjExOjE5KzA4OjAwIn0","header":{"x5c":["MIIDOjCCAiKgAwIBAgIBUTANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMCAXDTIyMTIxOTAyNDMzOVoYDzIxMjIxMjE5MDI0MzM5WjBLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwNrKIzpESDkY7Kah+sWKeXDNE7yUBaqW58cyMtkpk9/Q6QLbndMxdt8VjD3y4bSBhZXLdUkW0S4hSNZXQwkYm3yooxJXj+uOI9NnEPt/gAkPYri4TdKpTFSBzE4uJ0HrYDBUir3Dars/CXiusmdCnSWKsgm05ItkHdHEGtor716aWdnGuFNvyzJyaC3XLFpo1OwEwTyxf4Yix4UtvwNDB4TOJRH84avSoFCua8xbRpiBJ0QoX6zY0Yr9qbvMBQIcmpQ3uWOiEeVMDjRE0XzhiWSevVfZTPJcYpsAyBTJGJLIcHU7JMYnwKP6IzUg5T589lvRVD7up4sAC7izOKGzPQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAKacaJ+O9rTIdfLHCdneLQ6RN92Id5dl8PGh/Xig3AWSOtLgIiRRsdxVH5PZkGOHosellQG5fCvQOwB0x8O+2YDKLOfgVIJWd6X85NdvyCdX2ElYRmvX9ON5WVguGLluwkOfJ26M8d8ftXrcc97qaKk4EHS8R/LCWqZNDRiRCA0OtNP9cUkKFaIG1hgWgEieVWnxrCyUDbTX3uCiJKNzSOmI3psF3WIhabU7/7gBm95nWhgwS91qAxavccVkY6hqAPj9tjJH5UPI5RR8kDB5rWiCIx1YHuH+z1eAYUHWVvZvneVniNBI8qGoGBz9HkrX5ecf+V7zaxyy0FoWlEX1z8k="],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"sgygIQ3vLgM8XGUm_iM1hEogNE53TgtH91mgqyRRmkUnj93rUCEcrUN2u1gDFvKe0Z6-zI-I60Z95HloOngZ1nSu2Nyezx5evWBDS-stYZ9m0jyzGKOruTXDY-a0V4Jk1louK3t5LrcSJ2tt_lY6SOvZ__5Gv3KQQDBBluA_R74F-m-OhosRA60mS4dkAHOePOiDIwIFUUZWKYF9Afrqv-uAsQtfH70RCisjLLIgFDYtmCIUd2rP7ZzrybLj2CtHLuflSfh6QK0UBqhJSH8FSTWcXUE263plfIbYx-mZ6KPNOOYmSsLLgYUhdc9dSZk56BVi7ZF_X6CefzIsw9_adA"} \ No newline at end of file diff --git a/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/cc2ae4e91a31a77086edbdbf4711de48e5fa3ebdacad3403e61777a9e1a53b6f b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/cc2ae4e91a31a77086edbdbf4711de48e5fa3ebdacad3403e61777a9e1a53b6f new file mode 100644 index 000000000..45028368d --- /dev/null +++ b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/cc2ae4e91a31a77086edbdbf4711de48e5fa3ebdacad3403e61777a9e1a53b6f @@ -0,0 +1 @@ +{"mediaType":"application/vnd.oci.artifact.manifest.v1+json","artifactType":"application/vnd.notation.config","blobs":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:0d10372ee4448bf319929720bc465c1a6242cc68e82e12c152bc10d541cce578","size":17,"annotations":{"org.opencontainers.image.title":"awesome-notation.txt"}}],"annotations":{"org.opencontainers.artifact.created":"2022-12-22T01:11:12Z"}} \ No newline at end of file diff --git a/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/index.json b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/index.json new file mode 100644 index 000000000..f39913580 --- /dev/null +++ b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/index.json @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.artifact.manifest.v1+json","digest":"sha256:cc2ae4e91a31a77086edbdbf4711de48e5fa3ebdacad3403e61777a9e1a53b6f","size":417,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.artifact.manifest.v1+json","digest":"sha256:273243a7a64e9312761ca0aa8f43b6ba805e677a561558143b6e92981c487339","size":618}]} \ No newline at end of file diff --git a/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/oci-layout b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/oci-layout new file mode 100644 index 000000000..1343d370f --- /dev/null +++ b/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion":"1.0.0"} \ No newline at end of file