Accept equivalent checkout paths in build receipts
This commit is contained in:
@@ -14,10 +14,12 @@ func TestBuildAndPrebuiltDeliveryUseSameDirectoryBase(t *testing.T) {
|
|||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name, module string
|
name, module string
|
||||||
absolute bool
|
absolute bool
|
||||||
|
alias bool
|
||||||
}{
|
}{
|
||||||
{name: "root relative"},
|
{name: "root relative"},
|
||||||
{name: "nested relative", module: "apps/widget"},
|
{name: "nested relative", module: "apps/widget"},
|
||||||
{name: "nested absolute", module: "apps/widget", absolute: true},
|
{name: "nested absolute", module: "apps/widget", absolute: true},
|
||||||
|
{name: "nested checkout alias", module: "apps/widget", absolute: true, alias: true},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
repository := newDeliveryBuildRepository(t)
|
repository := newDeliveryBuildRepository(t)
|
||||||
@@ -53,7 +55,14 @@ func TestBuildAndPrebuiltDeliveryUseSameDirectoryBase(t *testing.T) {
|
|||||||
resolved = output
|
resolved = output
|
||||||
}
|
}
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
if status := run(context.Background(), []string{"build", "--repo", repository, "--output", output, "v1.2.3"}, &stdout, &stderr); status != 0 {
|
invocationRepository := repository
|
||||||
|
if tt.alias {
|
||||||
|
invocationRepository = filepath.Join(t.TempDir(), "checkout-alias")
|
||||||
|
if err := os.Symlink(repository, invocationRepository); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if status := run(context.Background(), []string{"build", "--repo", invocationRepository, "--output", output, "v1.2.3"}, &stdout, &stderr); status != 0 {
|
||||||
t.Fatalf("build status=%d, stderr=%s", status, stderr.String())
|
t.Fatalf("build status=%d, stderr=%s", status, stderr.String())
|
||||||
}
|
}
|
||||||
receipt, err := cli.LoadBuildReceipt(filepath.Join(resolved, cli.BuildReceiptName))
|
receipt, err := cli.LoadBuildReceipt(filepath.Join(resolved, cli.BuildReceiptName))
|
||||||
@@ -61,12 +70,17 @@ func TestBuildAndPrebuiltDeliveryUseSameDirectoryBase(t *testing.T) {
|
|||||||
t.Fatalf("build receipt=%#v, error=%v", receipt, err)
|
t.Fatalf("build receipt=%#v, error=%v", receipt, err)
|
||||||
}
|
}
|
||||||
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
|
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
|
||||||
args := []string{"deliver", "--repo", repository, "--assets", output, "v1.2.3"}
|
args := []string{"deliver", "--repo", invocationRepository, "--assets", output, "v1.2.3"}
|
||||||
stdout.Reset()
|
stdout.Reset()
|
||||||
stderr.Reset()
|
stderr.Reset()
|
||||||
if status := run(context.Background(), args, &stdout, &stderr); status != 0 || state.releaseCreates != 1 || state.assetUploads != 2 {
|
if status := run(context.Background(), args, &stdout, &stderr); status != 0 || state.releaseCreates != 1 || state.assetUploads != 2 {
|
||||||
t.Fatalf("delivery status=%d, creates=%d, uploads=%d, stderr=%s", status, state.releaseCreates, state.assetUploads, stderr.String())
|
t.Fatalf("delivery status=%d, creates=%d, uploads=%d, stderr=%s", status, state.releaseCreates, state.assetUploads, stderr.String())
|
||||||
}
|
}
|
||||||
|
if tt.alias {
|
||||||
|
if status := run(context.Background(), []string{"deliver", "--repo", repository, "--assets", output, "v1.2.3"}, &stdout, &stderr); status != 0 || state.assetUploads != 2 {
|
||||||
|
t.Fatalf("physical-path retry status=%d, uploads=%d, stderr=%s", status, state.assetUploads, stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
if tt.module != "" {
|
if tt.module != "" {
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
wrongModule := settings
|
wrongModule := settings
|
||||||
|
|||||||
@@ -122,15 +122,14 @@ func LoadBuildReceipt(path string) (BuildReceipt, error) {
|
|||||||
}
|
}
|
||||||
return BuildReceipt{}, err
|
return BuildReceipt{}, err
|
||||||
}
|
}
|
||||||
if filepath.Clean(receipt.OutputDir) != filepath.Dir(resolvedPath) {
|
if !sameDirectory(receipt.OutputDir, filepath.Dir(resolvedPath)) {
|
||||||
return BuildReceipt{}, errors.New("build receipt output directory does not match its location")
|
return BuildReceipt{}, errors.New("build receipt output directory does not match its location")
|
||||||
}
|
}
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (receipt BuildReceipt) MatchesBuild(repositoryDir, commit, version string, settings BuildSettings) bool {
|
func (receipt BuildReceipt) MatchesBuild(repositoryDir, commit, version string, settings BuildSettings) bool {
|
||||||
resolvedRepo, err := filepath.Abs(repositoryDir)
|
if !sameDirectory(receipt.RepositoryDir, repositoryDir) {
|
||||||
if err != nil || filepath.Clean(receipt.RepositoryDir) != filepath.Clean(resolvedRepo) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if receipt.CandidateCommit != commit || receipt.Version != version {
|
if receipt.CandidateCommit != commit || receipt.Version != version {
|
||||||
@@ -149,6 +148,15 @@ func (receipt BuildReceipt) MatchesBuild(repositoryDir, commit, version string,
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sameDirectory(first, second string) bool {
|
||||||
|
firstInfo, err := os.Stat(first)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
secondInfo, err := os.Stat(second)
|
||||||
|
return err == nil && os.SameFile(firstInfo, secondInfo)
|
||||||
|
}
|
||||||
|
|
||||||
func validateBuildReceipt(receipt BuildReceipt) error {
|
func validateBuildReceipt(receipt BuildReceipt) error {
|
||||||
if receipt.SchemaVersion != BuildReceiptSchemaVersion {
|
if receipt.SchemaVersion != BuildReceiptSchemaVersion {
|
||||||
return fmt.Errorf("unsupported or missing build receipt schema version %d", receipt.SchemaVersion)
|
return fmt.Errorf("unsupported or missing build receipt schema version %d", receipt.SchemaVersion)
|
||||||
|
|||||||
@@ -66,6 +66,23 @@ func TestBuildReceiptRoundTripBindsRootAndNestedModuleIdentity(t *testing.T) {
|
|||||||
if !loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
if !loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
||||||
t.Fatalf("receipt does not match normalized build settings: %#v", loaded)
|
t.Fatalf("receipt does not match normalized build settings: %#v", loaded)
|
||||||
}
|
}
|
||||||
|
aliases := t.TempDir()
|
||||||
|
repositoryAlias, outputAlias := filepath.Join(aliases, "repository"), filepath.Join(aliases, "output")
|
||||||
|
if err := os.Symlink(repositoryDir, repositoryAlias); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(outputDir, outputAlias); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !loaded.MatchesBuild(repositoryAlias, receipt.CandidateCommit, receipt.Version, settings) {
|
||||||
|
t.Fatal("receipt rejected an alias of its source repository")
|
||||||
|
}
|
||||||
|
if _, err := LoadBuildReceipt(filepath.Join(outputAlias, BuildReceiptName)); err != nil {
|
||||||
|
t.Fatalf("load receipt through output alias: %v", err)
|
||||||
|
}
|
||||||
|
if loaded.MatchesBuild(t.TempDir(), receipt.CandidateCommit, receipt.Version, settings) {
|
||||||
|
t.Fatal("receipt matched a different repository")
|
||||||
|
}
|
||||||
settings.BinaryName = "other"
|
settings.BinaryName = "other"
|
||||||
if loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
if loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
||||||
t.Fatal("receipt matched changed build settings")
|
t.Fatal("receipt matched changed build settings")
|
||||||
|
|||||||
@@ -128,7 +128,14 @@ still happen before hosted requests.
|
|||||||
|
|
||||||
## Issue 4: Compare receipt repository identity consistently through aliases (P2)
|
## Issue 4: Compare receipt repository identity consistently through aliases (P2)
|
||||||
|
|
||||||
Status: open. Category: regression from mixed path normalization.
|
Status: complete. Category: regression from mixed path normalization.
|
||||||
|
|
||||||
|
**Completion:** Receipt source and location comparisons use filesystem directory
|
||||||
|
identity, accepting aliases of the same checkout or output directory. Regression
|
||||||
|
tests build and deliver through a checkout symlink, retry through its physical
|
||||||
|
path, and reject different repositories and modules. Receipt structure, exact
|
||||||
|
release binding, and delivery-owned content checks remain intact. Receipt and
|
||||||
|
assembled prebuilt-delivery cases pass offline.
|
||||||
|
|
||||||
**Evidence:** Release identity canonicalizes repository symlinks, so generated
|
**Evidence:** Release identity canonicalizes repository symlinks, so generated
|
||||||
receipts record the physical repository directory.
|
receipts record the physical repository directory.
|
||||||
|
|||||||
Reference in New Issue
Block a user