1
0
mirror of https://github.com/docker/buildx.git synced 2026-10-23 17:29:03 +03:00
Files
buildx/replay/errors_test.go
Tonis Tiigi bd6c7fda50 replay: add replay build command
Add an experimental `buildx replay build` command that rebuilds an image
from its SLSA provenance. By default, every source is pinned to the digest
recorded in the provenance through a session source policy callback.
--replay-mode=frontend replays the recorded frontend and options without
pinning sources.

Replay requires mode=max provenance, a Git or HTTP(S) build context and
BuildKit v0.27 or later, and rebuilds one platform at a time. Subjects can
be registry images, OCI layouts or attestation files. Sigstore signatures
on the provenance are verified when present. --dry-run prints the replay
plan and runs the same checks as a real replay.

`replay snapshot` and `replay verify` are included as hidden commands until
snapshots can be consumed by replay build and verification results can be
trusted.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2026-09-30 09:44:47 -07:00

90 lines
3.5 KiB
Go

package replay
import (
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
// TestErrorTypes asserts that each replay constructor returns the typed error
// expected by errors.As-based consumers.
func TestErrorTypes(t *testing.T) {
tests := []struct {
name string
err error
want any
}{
{"local context", ErrUnreplayableLocalContext([]string{"default"}), &UnreplayableLocalContextError{}},
{"missing secret", ErrMissingSecret([]string{"mysecret"}), &MissingSecretError{}},
{"extra secret", ErrExtraSecret([]string{"mysecret"}), &ExtraSecretError{}},
{"missing ssh", ErrMissingSSH([]string{"default"}), &MissingSSHError{}},
{"extra ssh", ErrExtraSSH([]string{"default"}), &ExtraSSHError{}},
{"material not found", ErrMaterialNotFound("docker-image://foo", "sha256:aa"), &MaterialNotFoundError{}},
{"compare mismatch", ErrCompareMismatch("digest differs", nil), &CompareMismatchError{}},
{"not implemented", ErrNotImplemented("registry snapshot output"), &NotImplementedError{}},
{"unsupported subject", ErrUnsupportedSubject("attestation-file"), &UnsupportedSubjectError{}},
{"no provenance", ErrNoProvenance("foo:latest"), &NoProvenanceError{}},
{"unsupported predicate", ErrUnsupportedPredicate("https://slsa.dev/provenance/v0.2"), &UnsupportedPredicateError{}},
{"min mode provenance", ErrMinModeProvenance(), &MinModeProvenanceError{}},
{"unpinned context", ErrUnpinnedContext("https://example.com/repo.git#main:sub"), &UnpinnedContextError{}},
{"signature verification required", ErrSignatureVerificationRequired("./att.json", "dsse"), &SignatureVerificationRequiredError{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Error(t, tt.err)
switch want := tt.want.(type) {
case *UnreplayableLocalContextError:
require.ErrorAs(t, tt.err, &want)
case *MissingSecretError:
require.ErrorAs(t, tt.err, &want)
case *ExtraSecretError:
require.ErrorAs(t, tt.err, &want)
case *MissingSSHError:
require.ErrorAs(t, tt.err, &want)
case *ExtraSSHError:
require.ErrorAs(t, tt.err, &want)
case *MaterialNotFoundError:
require.ErrorAs(t, tt.err, &want)
case *CompareMismatchError:
require.ErrorAs(t, tt.err, &want)
case *NotImplementedError:
require.ErrorAs(t, tt.err, &want)
case *UnsupportedSubjectError:
require.ErrorAs(t, tt.err, &want)
case *NoProvenanceError:
require.ErrorAs(t, tt.err, &want)
case *UnsupportedPredicateError:
require.ErrorAs(t, tt.err, &want)
case *MinModeProvenanceError:
require.ErrorAs(t, tt.err, &want)
case *UnpinnedContextError:
require.ErrorAs(t, tt.err, &want)
case *SignatureVerificationRequiredError:
require.ErrorAs(t, tt.err, &want)
default:
t.Fatalf("unexpected want type %T", tt.want)
}
})
}
}
// TestErrorsAsWrapped asserts that pkg/errors stack-wrapped and fmt-wrapped
// replay errors remain errors.As-matchable.
func TestErrorsAsWrapped(t *testing.T) {
e := ErrMaterialNotFound("foo", "sha256:a")
wrapped := errors.Wrap(e, "resolver failed")
var mnf *MaterialNotFoundError
require.ErrorAs(t, wrapped, &mnf)
require.Equal(t, "foo", mnf.URI)
}
// TestSignatureVerificationRequiredMessage asserts the error message carries
// both the source and envelope kind so users know what was rejected.
func TestSignatureVerificationRequiredMessage(t *testing.T) {
e := ErrSignatureVerificationRequired("/tmp/att.json", "dsse")
require.Contains(t, e.Error(), "/tmp/att.json")
require.Contains(t, e.Error(), "dsse")
}