mirror of
https://github.com/docker/buildx.git
synced 2026-10-23 17:29:03 +03:00
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>
90 lines
3.5 KiB
Go
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")
|
|
}
|