From da398fefbfa2d22c4140290a20363ff48fb4da17 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Sat, 11 May 2024 16:44:08 +0800 Subject: [PATCH 01/14] fix: error message for dangling reference index Signed-off-by: Junjie Gao --- notation.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/notation.go b/notation.go index 8cb566a8..7e28c1bb 100644 --- a/notation.go +++ b/notation.go @@ -41,6 +41,9 @@ import ( ) var errDoneVerification = errors.New("done verification") + +const errReferrersTagSchemaDelete = "failed to delete dangling referrers index" + var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"} // SignerSignOptions contains parameters for Signer.Sign. @@ -166,7 +169,11 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", targetDesc, signOpts.SignatureMediaType) _, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations) if err != nil { - logger.Error("Failed to push the signature") + if strings.Contains(err.Error(), errReferrersTagSchemaDelete) { + logger.Warn("The signature has been attached to the artifact, but it failed to delete the dangling referrers index.") + } else { + logger.Error("Failed to push the signature") + } return ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()} } From 269845ca497690688f11f24bc96aa37ece8d6e16 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Sat, 11 May 2024 17:04:16 +0800 Subject: [PATCH 02/14] test: add unit test Signed-off-by: Junjie Gao --- internal/mock/mocks.go | 5 +++++ notation_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/internal/mock/mocks.go b/internal/mock/mocks.go index bc07c51f..4b4f0c4f 100644 --- a/internal/mock/mocks.go +++ b/internal/mock/mocks.go @@ -125,6 +125,7 @@ type Repository struct { FetchSignatureBlobError error MissMatchDigest bool ExceededNumOfSignatures bool + PushSignatureError error } func NewRepository() Repository { @@ -163,6 +164,10 @@ func (t Repository) FetchSignatureBlob(ctx context.Context, desc ocispec.Descrip } func (t Repository) PushSignature(ctx context.Context, mediaType string, blob []byte, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { + if t.PushSignatureError != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, t.PushSignatureError + } + return ocispec.Descriptor{}, ocispec.Descriptor{}, nil } diff --git a/notation_test.go b/notation_test.go index 72255f5c..44bc0992 100644 --- a/notation_test.go +++ b/notation_test.go @@ -155,6 +155,19 @@ func TestSignSuccessWithUserMetadata(t *testing.T) { } } +func TestSignWithDanglingReferrersIndex(t *testing.T) { + repo := mock.NewRepository() + repo.PushSignatureError = errors.New("failed to delete dangling referrers index") + opts := SignOptions{} + opts.ArtifactReference = mock.SampleArtifactUri + opts.SignatureMediaType = jws.MediaTypeEnvelope + + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) + if err == nil { + t.Fatalf("no error occurred, expected error: failed to delete dangling referrers index") + } +} + func TestSignWithInvalidExpiry(t *testing.T) { repo := mock.NewRepository() testCases := []struct { From d28f182cd402a768f28ed25c24154eb94f95ac72 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 13 May 2024 11:02:51 +0800 Subject: [PATCH 03/14] fix: use remote.ReferrersError type Signed-off-by: Junjie Gao --- notation.go | 6 +++--- notation_test.go | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/notation.go b/notation.go index 7e28c1bb..cc62709b 100644 --- a/notation.go +++ b/notation.go @@ -28,6 +28,7 @@ import ( "time" orasRegistry "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature/cose" @@ -42,8 +43,6 @@ import ( var errDoneVerification = errors.New("done verification") -const errReferrersTagSchemaDelete = "failed to delete dangling referrers index" - var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"} // SignerSignOptions contains parameters for Signer.Sign. @@ -169,7 +168,8 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", targetDesc, signOpts.SignatureMediaType) _, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations) if err != nil { - if strings.Contains(err.Error(), errReferrersTagSchemaDelete) { + var referrerError *remote.ReferrersError + if errors.As(err, &referrerError) && referrerError.IsReferrersIndexDelete() { logger.Warn("The signature has been attached to the artifact, but it failed to delete the dangling referrers index.") } else { logger.Error("Failed to push the signature") diff --git a/notation_test.go b/notation_test.go index 44bc0992..aefe54ee 100644 --- a/notation_test.go +++ b/notation_test.go @@ -34,6 +34,7 @@ import ( "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2/registry/remote" ) var expectedMetadata = map[string]string{"foo": "bar", "bar": "foo"} @@ -157,7 +158,10 @@ func TestSignSuccessWithUserMetadata(t *testing.T) { func TestSignWithDanglingReferrersIndex(t *testing.T) { repo := mock.NewRepository() - repo.PushSignatureError = errors.New("failed to delete dangling referrers index") + repo.PushSignatureError = &remote.ReferrersError{ + Op: "DeleteReferrersIndex", + Err: errors.New("error"), + } opts := SignOptions{} opts.ArtifactReference = mock.SampleArtifactUri opts.SignatureMediaType = jws.MediaTypeEnvelope From 6664b27d975dd1663e7c90b2ec77d4e530f81708 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 13 May 2024 13:27:34 +0800 Subject: [PATCH 04/14] test: add test cases Signed-off-by: Junjie Gao --- notation_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/notation_test.go b/notation_test.go index aefe54ee..bfd4b4b1 100644 --- a/notation_test.go +++ b/notation_test.go @@ -166,6 +166,55 @@ func TestSignWithDanglingReferrersIndex(t *testing.T) { opts.ArtifactReference = mock.SampleArtifactUri opts.SignatureMediaType = jws.MediaTypeEnvelope + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) + if err == nil { + t.Fatalf("no error occurred, expected error") + } +} + +func TestSignWithNilRepo(t *testing.T) { + opts := SignOptions{} + opts.ArtifactReference = mock.SampleArtifactUri + opts.SignatureMediaType = jws.MediaTypeEnvelope + + _, err := Sign(context.Background(), &dummySigner{}, nil, opts) + if err == nil { + t.Fatalf("no error occurred, expected error: repo cannot be nil") + } +} + +func TestSignResolveFailed(t *testing.T) { + repo := mock.NewRepository() + repo.ResolveError = errors.New("resolve error") + opts := SignOptions{} + opts.ArtifactReference = mock.SampleArtifactUri + opts.SignatureMediaType = jws.MediaTypeEnvelope + + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) + if err == nil { + t.Fatalf("no error occurred, expected resolve error") + } +} + +func TestSignArtifactRefIsTag(t *testing.T) { + repo := mock.NewRepository() + opts := SignOptions{} + opts.ArtifactReference = "registry.acme-rockets.io/software/net-monitor:v1" + opts.SignatureMediaType = jws.MediaTypeEnvelope + + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) + if err != nil { + t.Fatalf("expect no error, got %s", err) + } +} + +func TestSignWithPushSignatureError(t *testing.T) { + repo := mock.NewRepository() + repo.PushSignatureError = errors.New("error") + opts := SignOptions{} + opts.ArtifactReference = mock.SampleArtifactUri + opts.SignatureMediaType = jws.MediaTypeEnvelope + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) if err == nil { t.Fatalf("no error occurred, expected error: failed to delete dangling referrers index") @@ -205,7 +254,14 @@ func TestSignWithInvalidUserMetadata(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(b *testing.T) { - _, err := Sign(context.Background(), &dummySigner{}, repo, SignOptions{UserMetadata: tc.metadata}) + opts := SignOptions{ + UserMetadata: tc.metadata, + SignerSignOptions: SignerSignOptions{ + SignatureMediaType: jws.MediaTypeEnvelope, + }, + } + + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) if err == nil { b.Fatalf("Expected error but not found") } From 51a3e6b37e6be3c949972b7b61967e11aa073a04 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 13 May 2024 15:35:19 +0800 Subject: [PATCH 05/14] test: add test cases Signed-off-by: Junjie Gao --- errors_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ notation_test.go | 69 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 errors_test.go diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 00000000..62183ac9 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,80 @@ +package notation + +import "testing" + +func TestErrorMessages(t *testing.T) { + tests := []struct { + name string + err error + want string + }{ + { + name: "ErrorPushSignatureFailed with message", + err: ErrorPushSignatureFailed{Msg: "test message"}, + want: "failed to push signature to registry with error: test message", + }, + { + name: "ErrorPushSignatureFailed without message", + err: ErrorPushSignatureFailed{}, + want: "failed to push signature to registry", + }, + { + name: "ErrorVerificationInconclusive with message", + err: ErrorVerificationInconclusive{Msg: "test message"}, + want: "test message", + }, + { + name: "ErrorVerificationInconclusive without message", + err: ErrorVerificationInconclusive{}, + want: "signature verification was inclusive due to an unexpected error", + }, + { + name: "ErrorNoApplicableTrustPolicy with message", + err: ErrorNoApplicableTrustPolicy{Msg: "test message"}, + want: "test message", + }, + { + name: "ErrorNoApplicableTrustPolicy without message", + err: ErrorNoApplicableTrustPolicy{}, + want: "there is no applicable trust policy for the given artifact", + }, + { + name: "ErrorSignatureRetrievalFailed with message", + err: ErrorSignatureRetrievalFailed{Msg: "test message"}, + want: "test message", + }, + { + name: "ErrorSignatureRetrievalFailed without message", + err: ErrorSignatureRetrievalFailed{}, + want: "unable to retrieve the digital signature from the registry", + }, + { + name: "ErrorVerificationFailed with message", + err: ErrorVerificationFailed{Msg: "test message"}, + want: "test message", + }, + { + name: "ErrorVerificationFailed without message", + err: ErrorVerificationFailed{}, + want: "signature verification failed", + }, + { + name: "ErrorUserMetadataVerificationFailed with message", + err: ErrorUserMetadataVerificationFailed{Msg: "test message"}, + want: "test message", + }, + { + name: "ErrorUserMetadataVerificationFailed without message", + err: ErrorUserMetadataVerificationFailed{}, + want: "unable to find specified metadata in the signature", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.err.Error(); got != tt.want { + t.Errorf("Error() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/notation_test.go b/notation_test.go index bfd4b4b1..dd3105b1 100644 --- a/notation_test.go +++ b/notation_test.go @@ -15,6 +15,7 @@ package notation import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -28,6 +29,7 @@ import ( "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature/cose" "github.com/notaryproject/notation-core-go/signature/jws" + "github.com/notaryproject/notation-go/internal/envelope" "github.com/notaryproject/notation-go/internal/mock" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/registry" @@ -602,3 +604,70 @@ func TestVerifyLocalContent(t *testing.T) { t.Fatalf("failed to verify local content: %v", err) } } + +func TestUserMetadata(t *testing.T) { + t.Run("EnvelopeContent is nil", func(t *testing.T) { + outcome := &VerificationOutcome{} + _, err := outcome.UserMetadata() + if err == nil { + t.Fatal("expected an error, got nil") + } + if err.Error() != "unable to find envelope content for verification outcome" { + t.Fatalf("expected error message 'unable to find envelope content for verification outcome', got '%s'", err.Error()) + } + }) + + t.Run("EnvelopeContent is valid", func(t *testing.T) { + payload := envelope.Payload{ + TargetArtifact: ocispec.Descriptor{ + Annotations: map[string]string{ + "key": "value", + }, + }, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + t.Fatalf("unexpected error marshaling payload: %v", err) + } + + outcome := &VerificationOutcome{ + EnvelopeContent: &signature.EnvelopeContent{ + Payload: signature.Payload{ + Content: payloadBytes, + }, + }, + } + metadata, err := outcome.UserMetadata() + if err != nil { + t.Fatalf("unexpected error getting user metadata: %v", err) + } + if len(metadata) != 1 || metadata["key"] != "value" { + t.Fatalf("expected metadata map[key]=value, got %v", metadata) + } + }) + + t.Run("Annotation is nil", func(t *testing.T) { + payload := envelope.Payload{ + TargetArtifact: ocispec.Descriptor{}, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + t.Fatalf("unexpected error marshaling payload: %v", err) + } + + outcome := &VerificationOutcome{ + EnvelopeContent: &signature.EnvelopeContent{ + Payload: signature.Payload{ + Content: payloadBytes, + }, + }, + } + metadata, err := outcome.UserMetadata() + if err != nil { + t.Fatalf("unexpected error getting user metadata: %v", err) + } + if len(metadata) != 0 { + t.Fatalf("expected empty metadata, got %v", metadata) + } + }) +} From d8c8f29289d2332f20c3b3cdde06839cc3e411f5 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 13 May 2024 15:40:09 +0800 Subject: [PATCH 06/14] fix: add license Signed-off-by: Junjie Gao --- errors_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/errors_test.go b/errors_test.go index 62183ac9..29aed6ba 100644 --- a/errors_test.go +++ b/errors_test.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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 notation import "testing" From d444452ce8adbeafdbeebaed84575fbf3431e53c Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 13 May 2024 16:16:29 +0800 Subject: [PATCH 07/14] test: add test cases Signed-off-by: Junjie Gao --- config/config_test.go | 12 +++++++ dir/fs_test.go | 26 ++++++++++++-- notation_test.go | 80 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index ceb7ba83..db279c2b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -51,3 +51,15 @@ func TestSaveFile(t *testing.T) { t.Fatal("save config file failed.") } } + +func TestLoadNonExistedConfig(t *testing.T) { + dir.UserConfigDir = "./testdata/non-existed" + got, err := LoadConfig() + if err != nil { + t.Fatalf("LoadConfig() error. err = %v", err) + } + + if !reflect.DeepEqual(got, NewConfig()) { + t.Errorf("loadFile() = %v, want %v", got, NewConfig()) + } +} diff --git a/dir/fs_test.go b/dir/fs_test.go index 0011276c..d2c976cf 100644 --- a/dir/fs_test.go +++ b/dir/fs_test.go @@ -19,7 +19,7 @@ import ( "testing" ) -func Test_sysFS_SysPath(t *testing.T) { +func TestSysFS_SysPath(t *testing.T) { wantPath := filepath.FromSlash("/path/notation/config.json") fsys := NewSysFS("/path/notation") path, err := fsys.SysPath(PathConfigFile) @@ -31,7 +31,7 @@ func Test_sysFS_SysPath(t *testing.T) { } } -func Test_OsFs(t *testing.T) { +func TestOsFs(t *testing.T) { wantData := []byte("data") fsys := NewSysFS("./testdata") @@ -49,3 +49,25 @@ func Test_OsFs(t *testing.T) { t.Fatalf("SysFS read failed. got data = %v, want %v", data, wantData) } } + +func TestConfigFS(t *testing.T) { + configFS := ConfigFS() + path, err := configFS.SysPath(PathConfigFile) + if err != nil { + t.Fatalf("SysPath() failed. err = %v", err) + } + if path != filepath.Join(UserConfigDir, PathConfigFile) { + t.Fatalf(`SysPath() failed. got: %q, want: %q`, path, filepath.Join(UserConfigDir, PathConfigFile)) + } +} + +func TestPluginFS(t *testing.T) { + pluginFS := PluginFS() + path, err := pluginFS.SysPath("plugin") + if err != nil { + t.Fatalf("SysPath() failed. err = %v", err) + } + if path != filepath.Join(UserLibexecDir, PathPlugins, "plugin") { + t.Fatalf(`SysPath() failed. got: %q, want: %q`, path, filepath.Join(UserLibexecDir, PathPlugins, "plugin")) + } +} diff --git a/notation_test.go b/notation_test.go index dd3105b1..56bb5b91 100644 --- a/notation_test.go +++ b/notation_test.go @@ -271,6 +271,37 @@ func TestSignWithInvalidUserMetadata(t *testing.T) { } } +func TestSignOptsMissingSignatureMediaType(t *testing.T) { + repo := mock.NewRepository() + opts := SignOptions{ + SignerSignOptions: SignerSignOptions{ + SignatureMediaType: "", + }, + ArtifactReference: mock.SampleArtifactUri, + } + + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) + if err == nil { + t.Fatalf("expected error but not found") + } +} + +func TestSignOptsUnknownMediaType(t *testing.T) { + repo := mock.NewRepository() + opts := SignOptions{ + SignerSignOptions: SignerSignOptions{ + SignatureMediaType: "unknown", + }, + ArtifactReference: mock.SampleArtifactUri, + } + + _, err := Sign(context.Background(), &dummySigner{}, repo, opts) + if err == nil { + t.Fatalf("expected error but not found") + } + +} + func TestRegistryResolveError(t *testing.T) { policyDocument := dummyPolicyDocument() repo := mock.NewRepository() @@ -450,18 +481,47 @@ func TestExceededMaxSignatureAttempts(t *testing.T) { } func TestVerifyFailed(t *testing.T) { - policyDocument := dummyPolicyDocument() - repo := mock.NewRepository() - verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict} - expectedErr := ErrorVerificationFailed{} + t.Run("verification error", func(t *testing.T) { + policyDocument := dummyPolicyDocument() + repo := mock.NewRepository() + verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict} + expectedErr := ErrorVerificationFailed{} + + // mock the repository + opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50} + _, _, err := Verify(context.Background(), &verifier, repo, opts) + + if err == nil || !errors.Is(err, expectedErr) { + t.Fatalf("VerificationFailed expected: %v got: %v", expectedErr, err) + } + }) - // mock the repository - opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50} - _, _, err := Verify(context.Background(), &verifier, repo, opts) + t.Run("verifier is nil", func(t *testing.T) { + repo := mock.NewRepository() + expectedErr := errors.New("verifier cannot be nil") - if err == nil || !errors.Is(err, expectedErr) { - t.Fatalf("VerificationFailed expected: %v got: %v", expectedErr, err) - } + // mock the repository + opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50} + _, _, err := Verify(context.Background(), nil, repo, opts) + + if err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("VerificationFailed expected: %v got: %v", expectedErr, err) + } + }) + + t.Run("repo is nil", func(t *testing.T) { + policyDocument := dummyPolicyDocument() + verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} + expectedErr := errors.New("repo cannot be nil") + + // mock the repository + opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50} + _, _, err := Verify(context.Background(), &verifier, nil, opts) + + if err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("VerificationFailed expected: %v got: %v", expectedErr, err) + } + }) } func dummyPolicyDocument() (policyDoc trustpolicy.Document) { From 83b28aabaffe31cea7cb69639a43ff62a77ffd0b Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Tue, 14 May 2024 15:13:06 +0800 Subject: [PATCH 08/14] test: update test cases Signed-off-by: Junjie Gao --- .gitignore | 5 +- internal/mock/ocilayout/ocilayout.go | 37 +++++++ log/log_test.go | 41 ++++++++ notation_test.go | 64 +++++++----- plugin/testdata/plugins/foo/libfoo | 0 registry/repository_test.go | 142 ++++++++++++++++++--------- 6 files changed, 215 insertions(+), 74 deletions(-) create mode 100644 internal/mock/ocilayout/ocilayout.go create mode 100644 log/log_test.go create mode 100644 plugin/testdata/plugins/foo/libfoo diff --git a/.gitignore b/.gitignore index e9ac268a..c1644c70 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ *.sublime-workspace # Custom -coverage.txt \ No newline at end of file +coverage.txt + +# tmp directory was generated by example_remoteVerify_test.go +tmp/ \ No newline at end of file diff --git a/internal/mock/ocilayout/ocilayout.go b/internal/mock/ocilayout/ocilayout.go new file mode 100644 index 00000000..16eec21c --- /dev/null +++ b/internal/mock/ocilayout/ocilayout.go @@ -0,0 +1,37 @@ +package ocilayout + +import ( + "context" + "os" + "path/filepath" + "testing" + + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content/oci" +) + +// TempOCILayout creates a temporary OCI layout for testing +// and returns the path to the layout. +func TempOCILayout(t *testing.T, sourcePath string) (string, error) { + ctx := context.Background() + destPath := filepath.Join(t.TempDir(), "notation", "oci-layout") + + srcStore, err := oci.NewFromFS(ctx, os.DirFS(sourcePath)) + if err != nil { + return "", err + } + + // create a dest store for store the generated oci layout. + destStore, err := oci.New(destPath) + if err != nil { + return "", err + } + + // copy data + _, err = oras.ExtendedCopy(ctx, srcStore, "v2", destStore, "", oras.DefaultExtendedCopyOptions) + if err != nil { + return "", err + } + + return destPath, nil +} diff --git a/log/log_test.go b/log/log_test.go new file mode 100644 index 00000000..d09874db --- /dev/null +++ b/log/log_test.go @@ -0,0 +1,41 @@ +// Copyright The Notary Project 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 log provides logging functionality to notation. +// Users who want to enable logging option in notation should implement the +// log.Logger interface and include it in context by calling log.WithLogger. +// 3rd party loggers that implement log.Logger: github.com/uber-go/zap.SugaredLogger +// and github.com/sirupsen/logrus.Logger. +package log + +import ( + "context" + "testing" +) + +func TestWithLoggerAndGetLogger(t *testing.T) { + tl := &discardLogger{} + ctx := WithLogger(context.Background(), tl) + + if got := GetLogger(ctx); got != tl { + t.Errorf("GetLogger() = %v, want %v", got, tl) + } +} + +func TestGetLoggerWithNoLogger(t *testing.T) { + ctx := context.Background() + + if got := GetLogger(ctx); got != Discard { + t.Errorf("GetLogger() = %v, want Discard", got) + } +} diff --git a/notation_test.go b/notation_test.go index 56bb5b91..73ac80c8 100644 --- a/notation_test.go +++ b/notation_test.go @@ -31,6 +31,7 @@ import ( "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go/internal/envelope" "github.com/notaryproject/notation-go/internal/mock" + "github.com/notaryproject/notation-go/internal/mock/ocilayout" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/registry" "github.com/notaryproject/notation-go/verifier/trustpolicy" @@ -606,7 +607,6 @@ func (v *dummyVerifier) Verify(ctx context.Context, desc ocispec.Descriptor, sig } var ( - ociLayoutPath = filepath.FromSlash("./internal/testdata/oci-layout") reference = "sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0" artifactReference = "local/oci-layout@sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0" signaturePath = filepath.FromSlash("./internal/testdata/cose_signature.sig") @@ -630,39 +630,49 @@ func (s *ociDummySigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts return sigBlob, &content.SignerInfo, nil } -func TestSignLocalContent(t *testing.T) { - repo, err := registry.NewOCIRepository(ociLayoutPath, registry.RepositoryOptions{}) +func TestLocalContent(t *testing.T) { + // create a temp OCI layout + ociLayoutTestdataPath, err := filepath.Abs(filepath.Join("internal", "testdata", "oci-layout")) if err != nil { - t.Fatal(err) + t.Fatalf("failed to get oci layout path: %v", err) } - signOpts := SignOptions{ - SignerSignOptions: SignerSignOptions{ - SignatureMediaType: cose.MediaTypeEnvelope, - }, - ArtifactReference: reference, - } - _, err = Sign(context.Background(), &ociDummySigner{}, repo, signOpts) + ociLayoutPath, err := ocilayout.TempOCILayout(t, ociLayoutTestdataPath) if err != nil { - t.Fatalf("failed to Sign: %v", err) + t.Fatalf("failed to create temp oci layout: %v", err) } -} - -func TestVerifyLocalContent(t *testing.T) { repo, err := registry.NewOCIRepository(ociLayoutPath, registry.RepositoryOptions{}) if err != nil { - t.Fatalf("failed to create oci.Store as registry.Repository: %v", err) - } - verifyOpts := VerifyOptions{ - ArtifactReference: artifactReference, - MaxSignatureAttempts: math.MaxInt64, - } - policyDocument := dummyPolicyDocument() - verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} - // verify signatures inside the OCI layout folder - _, _, err = Verify(context.Background(), &verifier, repo, verifyOpts) - if err != nil { - t.Fatalf("failed to verify local content: %v", err) + t.Fatal(err) } + + t.Run("sign the local content", func(t *testing.T) { + // sign the artifact + signOpts := SignOptions{ + SignerSignOptions: SignerSignOptions{ + SignatureMediaType: cose.MediaTypeEnvelope, + }, + ArtifactReference: reference, + } + _, err = Sign(context.Background(), &ociDummySigner{}, repo, signOpts) + if err != nil { + t.Fatalf("failed to Sign: %v", err) + } + }) + + t.Run("verify local content", func(t *testing.T) { + // verify the artifact + verifyOpts := VerifyOptions{ + ArtifactReference: artifactReference, + MaxSignatureAttempts: math.MaxInt64, + } + policyDocument := dummyPolicyDocument() + verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} + // verify signatures inside the OCI layout folder + _, _, err = Verify(context.Background(), &verifier, repo, verifyOpts) + if err != nil { + t.Fatalf("failed to verify local content: %v", err) + } + }) } func TestUserMetadata(t *testing.T) { diff --git a/plugin/testdata/plugins/foo/libfoo b/plugin/testdata/plugins/foo/libfoo new file mode 100644 index 00000000..e69de29b diff --git a/registry/repository_test.go b/registry/repository_test.go index 9224f875..8c1dac60 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -27,11 +27,13 @@ import ( "testing" "github.com/notaryproject/notation-go/internal/envelope" + "github.com/notaryproject/notation-go/internal/mock/ocilayout" "github.com/notaryproject/notation-go/internal/slices" "github.com/notaryproject/notation-go/registry/internal/artifactspec" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/content/oci" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" ) @@ -464,7 +466,6 @@ func newRepositoryClientWithImageManifest(client remote.Client, ref registry.Ref } var ( - ociLayoutPath = filepath.FromSlash("../internal/testdata/oci-layout") reference = "sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0" expectedTargetDesc = ocispec.Descriptor{ MediaType: "application/vnd.oci.image.manifest.v1+json", @@ -487,11 +488,23 @@ var ( } ) -func TestOciLayoutRepositoryResolveAndPush(t *testing.T) { +func TestOciLayoutRepositoryPushAndFetch(t *testing.T) { + // create a temp OCI layout + ociLayoutTestdataPath, err := filepath.Abs(filepath.Join("..", "internal", "testdata", "oci-layout")) + if err != nil { + t.Fatalf("failed to get oci layout path: %v", err) + } + + ociLayoutPath, err := ocilayout.TempOCILayout(t, ociLayoutTestdataPath) + if err != nil { + t.Fatalf("failed to create temp oci layout: %v", err) + } repo, err := NewOCIRepository(ociLayoutPath, RepositoryOptions{}) if err != nil { t.Fatalf("failed to create oci.Store as registry.Repository: %v", err) } + + // test resolve targetDesc, err := repo.Resolve(context.Background(), reference) if err != nil { t.Fatalf("failed to resolve reference: %v", err) @@ -499,59 +512,96 @@ func TestOciLayoutRepositoryResolveAndPush(t *testing.T) { if !content.Equal(targetDesc, expectedTargetDesc) { t.Fatalf("failed to resolve reference. expected descriptor: %v, but got: %v", expectedTargetDesc, targetDesc) } - signature, err := os.ReadFile(signaturePath) - if err != nil { - t.Fatalf("failed to read signature: %v", err) - } - _, signatureManifestDesc, err := repo.PushSignature(context.Background(), joseTag, signature, targetDesc, annotations) + + t.Run("oci layout push", func(t *testing.T) { + signature, err := os.ReadFile(signaturePath) + if err != nil { + t.Fatalf("failed to read signature: %v", err) + } + _, signatureManifestDesc, err := repo.PushSignature(context.Background(), joseTag, signature, targetDesc, annotations) + if err != nil { + t.Fatalf("failed to push signature: %v", err) + } + if !content.Equal(expectedSignatureManifestDesc, signatureManifestDesc) { + t.Fatalf("expected desc: %v, got: %v", expectedSignatureManifestDesc, signatureManifestDesc) + } + expectedAnnotations := map[string]string{ + envelope.AnnotationX509ChainThumbprint: "[\"9f5f5aecee24b5cfdc7a91f6d5ac5c3a5348feb17c934d403f59ac251549ea0d\"]", + ocispec.AnnotationCreated: "2023-03-14T08:10:02Z", + } + if !reflect.DeepEqual(expectedAnnotations, signatureManifestDesc.Annotations) { + t.Fatalf("expected annotations: %v, but got: %v", expectedAnnotations, signatureManifestDesc.Annotations) + } + }) + + t.Run("oci layout fetch", func(t *testing.T) { + err = repo.ListSignatures(context.Background(), targetDesc, func(signatureManifests []ocispec.Descriptor) error { + if len(signatureManifests) == 0 { + return fmt.Errorf("expected to find signature in the OCI layout folder, but got none") + } + var found bool + for _, sigManifestDesc := range signatureManifests { + if !content.Equal(sigManifestDesc, expectedSignatureManifestDesc) { + continue + } + _, sigDesc, err := repo.FetchSignatureBlob(context.Background(), sigManifestDesc) + if err != nil { + return fmt.Errorf("failed to fetch blob: %w", err) + } + if !content.Equal(expectedSignatureBlobDesc, sigDesc) { + return fmt.Errorf("expected to get signature blob desc: %v, got: %v", expectedSignatureBlobDesc, sigDesc) + } + found = true + } + if !found { + return fmt.Errorf("expected to find the signature with manifest desc: %v, but failed", expectedSignatureManifestDesc) + } + return nil + }) + if err != nil { + t.Fatal(err) + } + }) +} + +func TestNewRepository(t *testing.T) { + target, err := oci.New(t.TempDir()) if err != nil { - t.Fatalf("failed to push signature: %v", err) + t.Fatalf("failed to create oci.Store as registry.Repository: %v", err) } - if !content.Equal(expectedSignatureManifestDesc, signatureManifestDesc) { - t.Fatalf("expected desc: %v, got: %v", expectedSignatureManifestDesc, signatureManifestDesc) + repo := NewRepository(target) + if repo == nil { + t.Fatalf("failed to create repository") } - expectedAnnotations := map[string]string{ - envelope.AnnotationX509ChainThumbprint: "[\"9f5f5aecee24b5cfdc7a91f6d5ac5c3a5348feb17c934d403f59ac251549ea0d\"]", - ocispec.AnnotationCreated: "2023-03-14T08:10:02Z", + repoClient, ok := repo.(*repositoryClient) + if !ok { + t.Fatalf("failed to create repositoryClient") } - if !reflect.DeepEqual(expectedAnnotations, signatureManifestDesc.Annotations) { - t.Fatalf("expected annotations: %v, but got: %v", expectedAnnotations, signatureManifestDesc.Annotations) + if target != repoClient.GraphTarget { + t.Fatalf("expected target: %v, got: %v", target, repoClient.GraphTarget) } } -func TestOciLayoutRepositoryListAndFetchBlob(t *testing.T) { - repo, err := NewOCIRepository(ociLayoutPath, RepositoryOptions{}) - if err != nil { - t.Fatalf("failed to create oci.Store as registry.Repository: %v", err) - } - targetDesc, err := repo.Resolve(context.Background(), reference) - if err != nil { - t.Fatalf("failed to resolve reference: %v", err) - } - err = repo.ListSignatures(context.Background(), targetDesc, func(signatureManifests []ocispec.Descriptor) error { - if len(signatureManifests) == 0 { - return fmt.Errorf("expected to find signature in the OCI layout folder, but got none") +func TestNewOCIRepositoryFailed(t *testing.T) { + t.Run("os stat failed", func(t *testing.T) { + _, err := NewOCIRepository("invalid-path", RepositoryOptions{}) + if err == nil { + t.Fatalf("expected to fail with invalid path") } - var found bool - for _, sigManifestDesc := range signatureManifests { - if !content.Equal(sigManifestDesc, expectedSignatureManifestDesc) { - continue - } - _, sigDesc, err := repo.FetchSignatureBlob(context.Background(), sigManifestDesc) - if err != nil { - return fmt.Errorf("failed to fetch blob: %w", err) - } - if !content.Equal(expectedSignatureBlobDesc, sigDesc) { - return fmt.Errorf("expected to get signature blob desc: %v, got: %v", expectedSignatureBlobDesc, sigDesc) - } - found = true + }) + + t.Run("path is regular file", func(t *testing.T) { + // create a regular file in the temp dir + filePath := filepath.Join(t.TempDir(), "file") + file, err := os.Create(filePath) + if err != nil { + t.Fatalf("failed to create file: %v", err) } - if !found { - return fmt.Errorf("expected to find the signature with manifest desc: %v, but failed", expectedSignatureManifestDesc) + file.Close() + + _, err = NewOCIRepository(filePath, RepositoryOptions{}) + if err == nil { + t.Fatalf("expected to fail with regular file") } - return nil }) - if err != nil { - t.Fatal(err) - } } From 12dfced0e4ed9faf12f6743353e57b35a7b47b8a Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 22 May 2024 16:25:37 +0800 Subject: [PATCH 09/14] fix: update Signed-off-by: Junjie Gao --- internal/mock/ocilayout/ocilayout.go | 7 +++---- notation_test.go | 2 +- registry/repository_test.go | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/mock/ocilayout/ocilayout.go b/internal/mock/ocilayout/ocilayout.go index 16eec21c..7f61e48f 100644 --- a/internal/mock/ocilayout/ocilayout.go +++ b/internal/mock/ocilayout/ocilayout.go @@ -4,17 +4,16 @@ import ( "context" "os" "path/filepath" - "testing" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/oci" ) -// TempOCILayout creates a temporary OCI layout for testing +// Copy creates a temporary OCI layout for testing // and returns the path to the layout. -func TempOCILayout(t *testing.T, sourcePath string) (string, error) { +func Copy(sourcePath string, destinationPath string) (string, error) { ctx := context.Background() - destPath := filepath.Join(t.TempDir(), "notation", "oci-layout") + destPath := filepath.Join(destinationPath, "notation", "oci-layout") srcStore, err := oci.NewFromFS(ctx, os.DirFS(sourcePath)) if err != nil { diff --git a/notation_test.go b/notation_test.go index 73ac80c8..49e3966f 100644 --- a/notation_test.go +++ b/notation_test.go @@ -636,7 +636,7 @@ func TestLocalContent(t *testing.T) { if err != nil { t.Fatalf("failed to get oci layout path: %v", err) } - ociLayoutPath, err := ocilayout.TempOCILayout(t, ociLayoutTestdataPath) + ociLayoutPath, err := ocilayout.Copy(ociLayoutTestdataPath, t.TempDir()) if err != nil { t.Fatalf("failed to create temp oci layout: %v", err) } diff --git a/registry/repository_test.go b/registry/repository_test.go index 8c1dac60..eff03023 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -495,7 +495,7 @@ func TestOciLayoutRepositoryPushAndFetch(t *testing.T) { t.Fatalf("failed to get oci layout path: %v", err) } - ociLayoutPath, err := ocilayout.TempOCILayout(t, ociLayoutTestdataPath) + ociLayoutPath, err := ocilayout.Copy(ociLayoutTestdataPath, t.TempDir()) if err != nil { t.Fatalf("failed to create temp oci layout: %v", err) } From 764725359ca04566f7e977eb3537fa87d7e90452 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 22 May 2024 16:57:04 +0800 Subject: [PATCH 10/14] fix: update code Signed-off-by: Junjie Gao --- internal/mock/ocilayout/ocilayout.go | 4 +- internal/mock/ocilayout/ocilayout_test.go | 47 +++++++++++++++++++++++ notation_test.go | 2 +- registry/repository_test.go | 2 +- 4 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 internal/mock/ocilayout/ocilayout_test.go diff --git a/internal/mock/ocilayout/ocilayout.go b/internal/mock/ocilayout/ocilayout.go index 7f61e48f..4c771711 100644 --- a/internal/mock/ocilayout/ocilayout.go +++ b/internal/mock/ocilayout/ocilayout.go @@ -11,7 +11,7 @@ import ( // Copy creates a temporary OCI layout for testing // and returns the path to the layout. -func Copy(sourcePath string, destinationPath string) (string, error) { +func Copy(sourcePath, destinationPath, tag string) (string, error) { ctx := context.Background() destPath := filepath.Join(destinationPath, "notation", "oci-layout") @@ -27,7 +27,7 @@ func Copy(sourcePath string, destinationPath string) (string, error) { } // copy data - _, err = oras.ExtendedCopy(ctx, srcStore, "v2", destStore, "", oras.DefaultExtendedCopyOptions) + _, err = oras.ExtendedCopy(ctx, srcStore, tag, destStore, "", oras.DefaultExtendedCopyOptions) if err != nil { return "", err } diff --git a/internal/mock/ocilayout/ocilayout_test.go b/internal/mock/ocilayout/ocilayout_test.go new file mode 100644 index 00000000..a9828d03 --- /dev/null +++ b/internal/mock/ocilayout/ocilayout_test.go @@ -0,0 +1,47 @@ +package ocilayout + +import ( + "os" + "testing" +) + +func TestCopy(t *testing.T) { + t.Run("empty oci layout", func(t *testing.T) { + _, err := Copy("", "", "v2") + if err == nil { + t.Errorf("expected error, got nil") + } + }) + + t.Run("invalid target path", func(t *testing.T) { + tempDir := t.TempDir() + // change the permission of the tempDir to make it invalid + if err := os.Chmod(tempDir, 0); err != nil { + t.Fatalf("failed to change the permission of the tempDir: %v", err) + } + _, err := Copy("../../testdata/oci-layout", tempDir, "v2") + if err == nil { + t.Errorf("expected error, got nil") + } + // clean tempDir + if err := os.Chmod(tempDir, 0755); err != nil { + t.Fatalf("failed to change the permission of the tempDir: %v", err) + } + }) + + t.Run("copy failed", func(t *testing.T) { + tempDir := t.TempDir() + _, err := Copy("../../testdata/oci-layout", tempDir, "v3") + if err == nil { + t.Errorf("expected error, got nil") + } + }) + + t.Run("copy success", func(t *testing.T) { + tempDir := t.TempDir() + _, err := Copy("../../testdata/oci-layout", tempDir, "v2") + if err != nil { + t.Errorf("expected nil, got %v", err) + } + }) +} diff --git a/notation_test.go b/notation_test.go index 49e3966f..2324b84c 100644 --- a/notation_test.go +++ b/notation_test.go @@ -636,7 +636,7 @@ func TestLocalContent(t *testing.T) { if err != nil { t.Fatalf("failed to get oci layout path: %v", err) } - ociLayoutPath, err := ocilayout.Copy(ociLayoutTestdataPath, t.TempDir()) + ociLayoutPath, err := ocilayout.Copy(ociLayoutTestdataPath, t.TempDir(), "v2") if err != nil { t.Fatalf("failed to create temp oci layout: %v", err) } diff --git a/registry/repository_test.go b/registry/repository_test.go index eff03023..0d3b6acc 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -495,7 +495,7 @@ func TestOciLayoutRepositoryPushAndFetch(t *testing.T) { t.Fatalf("failed to get oci layout path: %v", err) } - ociLayoutPath, err := ocilayout.Copy(ociLayoutTestdataPath, t.TempDir()) + ociLayoutPath, err := ocilayout.Copy(ociLayoutTestdataPath, t.TempDir(), "v2") if err != nil { t.Fatalf("failed to create temp oci layout: %v", err) } From 519099d19e2a1b39697d63f8f58e5b24113c213a Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 23 May 2024 16:46:19 +0800 Subject: [PATCH 11/14] fiX: update code Signed-off-by: Junjie Gao --- internal/mock/ocilayout/ocilayout.go | 13 ++ internal/mock/ocilayout/ocilayout_test.go | 15 +- registry/repository_test.go | 177 ++++++++++++++++++++++ 3 files changed, 204 insertions(+), 1 deletion(-) diff --git a/internal/mock/ocilayout/ocilayout.go b/internal/mock/ocilayout/ocilayout.go index 4c771711..358a85c4 100644 --- a/internal/mock/ocilayout/ocilayout.go +++ b/internal/mock/ocilayout/ocilayout.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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 ocilayout import ( diff --git a/internal/mock/ocilayout/ocilayout_test.go b/internal/mock/ocilayout/ocilayout_test.go index a9828d03..5da86764 100644 --- a/internal/mock/ocilayout/ocilayout_test.go +++ b/internal/mock/ocilayout/ocilayout_test.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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 ocilayout import ( @@ -23,7 +36,7 @@ func TestCopy(t *testing.T) { if err == nil { t.Errorf("expected error, got nil") } - // clean tempDir + if err := os.Chmod(tempDir, 0755); err != nil { t.Fatalf("failed to change the permission of the tempDir: %v", err) } diff --git a/registry/repository_test.go b/registry/repository_test.go index 0d3b6acc..6a714917 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -33,6 +33,7 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/content/memory" "oras.land/oras-go/v2/content/oci" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" @@ -604,4 +605,180 @@ func TestNewOCIRepositoryFailed(t *testing.T) { t.Fatalf("expected to fail with regular file") } }) + + t.Run("no permission to create new path", func(t *testing.T) { + // create a directory in the temp dir + dirPath := filepath.Join(t.TempDir(), "dir") + err := os.Mkdir(dirPath, 0000) + if err != nil { + t.Fatalf("failed to create dir: %v", err) + } + + _, err = NewOCIRepository(dirPath, RepositoryOptions{}) + if err == nil { + t.Fatalf("expected to fail with no permission to create new path") + } + }) +} + +// testStorage implements content.ReadOnlyGraphStorage +type testStorage struct { + store *memory.Store + FetchError error + FetchContent []byte + PredecessorsError error + PredecessorsDesc []ocispec.Descriptor +} + +func (s *testStorage) Push(ctx context.Context, expected ocispec.Descriptor, reader io.Reader) error { + return s.store.Push(ctx, expected, reader) +} + +func (s *testStorage) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) { + if s.FetchError != nil { + return nil, s.FetchError + } + return io.NopCloser(bytes.NewReader(s.FetchContent)), nil +} + +func (s *testStorage) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) { + return s.store.Exists(ctx, target) +} + +func (s *testStorage) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) { + if s.PredecessorsError != nil { + return nil, s.PredecessorsError + } + return s.PredecessorsDesc, nil +} + +func TestSignatureReferrers(t *testing.T) { + t.Run("get predecessors failed", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsError: fmt.Errorf("failed to get predecessors"), + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{}) + if err == nil { + t.Fatalf("expected to fail with getting predecessors") + } + }) + + t.Run("artifact manifest exceds max blob size", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsDesc: []ocispec.Descriptor{ + { + Digest: validDigestWithAlgo2, + MediaType: "application/vnd.oci.artifact.manifest.v1+json", + Size: 4*1024*1024 + 1, + }, + }, + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{ + Digest: validDigestWithAlgo2, + }) + if err == nil { + t.Fatalf("expected to fail with artifact manifest exceds max blob size") + } + }) + + t.Run("image manifest exceds max blob size", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsDesc: []ocispec.Descriptor{ + { + Digest: validDigestWithAlgo2, + MediaType: "application/vnd.oci.image.manifest.v1+json", + Size: 4*1024*1024 + 1, + }, + }, + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{ + Digest: validDigestWithAlgo2, + }) + if err == nil { + t.Fatalf("expected to fail with image manifest exceds max blob size") + } + }) + + t.Run("artifact manifest fetchAll failed", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsDesc: []ocispec.Descriptor{ + { + Digest: validDigestWithAlgo, + MediaType: "application/vnd.oci.artifact.manifest.v1+json", + Size: 481, + }, + }, + FetchError: fmt.Errorf("failed to fetch all"), + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{ + Digest: validDigestWithAlgo, + }) + if err == nil { + t.Fatalf("expected to fail with fetchAll failed") + } + }) + + t.Run("image manifest fetchAll failed", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsDesc: []ocispec.Descriptor{ + { + Digest: validDigestWithAlgo, + MediaType: "application/vnd.oci.image.manifest.v1+json", + Size: 481, + }, + }, + FetchError: fmt.Errorf("failed to fetch all"), + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{ + Digest: validDigestWithAlgo, + }) + if err == nil { + t.Fatalf("expected to fail with fetchAll failed") + } + }) + + t.Run("artifact manifest marshal failed", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsDesc: []ocispec.Descriptor{ + { + Digest: "sha256:24aafc739daae02bcd33471a1b28bcfaaef0bb5e530ef44cd4e5d2445e606690", + MediaType: "application/vnd.oci.artifact.manifest.v1+json", + Size: 15, + }, + }, + FetchContent: []byte("invalid content"), + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{ + Digest: "sha256:24aafc739daae02bcd33471a1b28bcfaaef0bb5e530ef44cd4e5d2445e606690", + }) + if err == nil { + t.Fatalf("expected to fail with marshal failed") + } + }) + + t.Run("image manifest marshal failed", func(t *testing.T) { + store := &testStorage{ + store: &memory.Store{}, + PredecessorsDesc: []ocispec.Descriptor{ + { + Digest: "sha256:24aafc739daae02bcd33471a1b28bcfaaef0bb5e530ef44cd4e5d2445e606690", + MediaType: "application/vnd.oci.image.manifest.v1+json", + Size: 15, + }, + }, + FetchContent: []byte("invalid content"), + } + _, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{ + Digest: "sha256:24aafc739daae02bcd33471a1b28bcfaaef0bb5e530ef44cd4e5d2445e606690", + }) + if err == nil { + t.Fatalf("expected to fail with marshal failed") + } + }) } From cdde21fbfacc2cf3bdaaa97087bb8382ac8af473 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 29 May 2024 08:48:45 +0800 Subject: [PATCH 12/14] fix: update Signed-off-by: Junjie Gao --- notation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notation.go b/notation.go index cc62709b..7117e36e 100644 --- a/notation.go +++ b/notation.go @@ -170,7 +170,7 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts if err != nil { var referrerError *remote.ReferrersError if errors.As(err, &referrerError) && referrerError.IsReferrersIndexDelete() { - logger.Warn("The signature has been attached to the artifact, but it failed to delete the dangling referrers index.") + logger.Warn("The signature has been attached to the artifact, but it failed to delete the dangling referrers index") } else { logger.Error("Failed to push the signature") } From 42636053942c7d2b2ff9828535149228266ed4a9 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 29 May 2024 10:11:19 +0800 Subject: [PATCH 13/14] fix: update Signed-off-by: Junjie Gao --- notation.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/notation.go b/notation.go index 7117e36e..48ed23ec 100644 --- a/notation.go +++ b/notation.go @@ -169,9 +169,8 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts _, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations) if err != nil { var referrerError *remote.ReferrersError - if errors.As(err, &referrerError) && referrerError.IsReferrersIndexDelete() { - logger.Warn("The signature has been attached to the artifact, but it failed to delete the dangling referrers index") - } else { + // do not log an error for failing to delete referral index. + if !errors.As(err, &referrerError) || !referrerError.IsReferrersIndexDelete() { logger.Error("Failed to push the signature") } return ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()} From a42ab40a33af7a3d65c8209d580e239f8cc6fd9b Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 29 May 2024 10:12:32 +0800 Subject: [PATCH 14/14] fix: update Signed-off-by: Junjie Gao --- notation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notation.go b/notation.go index 48ed23ec..e5738062 100644 --- a/notation.go +++ b/notation.go @@ -169,7 +169,7 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts _, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations) if err != nil { var referrerError *remote.ReferrersError - // do not log an error for failing to delete referral index. + // do not log an error for failing to delete referral index if !errors.As(err, &referrerError) || !referrerError.IsReferrersIndexDelete() { logger.Error("Failed to push the signature") }