Read delivery tokens from files and check selected paths before release
This commit is contained in:
@@ -27,6 +27,8 @@ const (
|
||||
MarkdownLinks Check = "markdown-links"
|
||||
// SharedFiles identifies a requested shared file that is unavailable.
|
||||
SharedFiles Check = "shared-files"
|
||||
// UntrackedFiles identifies selected paths present in the committed source.
|
||||
UntrackedFiles Check = "untracked-files"
|
||||
)
|
||||
|
||||
// Finding describes a validation observation at an optional source location.
|
||||
|
||||
24
internal/validation/untracked_files.go
Normal file
24
internal/validation/untracked_files.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func checkUntrackedFiles(tree *CandidateTree, names []string) error {
|
||||
var findings []Finding
|
||||
for _, name := range names {
|
||||
clean := path.Clean(name)
|
||||
if name == "" || path.IsAbs(name) || clean == "." || clean == ".." || strings.HasPrefix(clean, "../") {
|
||||
findings = append(findings, Finding{Path: name, Message: "expected a repository-relative file path"})
|
||||
continue
|
||||
}
|
||||
if _, exists := tree.Entry(clean); exists {
|
||||
findings = append(findings, Finding{Path: clean, Message: "file must be absent from the committed candidate"})
|
||||
}
|
||||
}
|
||||
if len(findings) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &Failure{Check: UntrackedFiles, Findings: findings}
|
||||
}
|
||||
49
internal/validation/untracked_files_test.go
Normal file
49
internal/validation/untracked_files_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUntrackedFilesUsesCapturedRepositoryPaths(t *testing.T) {
|
||||
repository := newTreeRepository(t)
|
||||
writeTreeFile(t, repository, "go.mod", []byte("module example.com/root\n"))
|
||||
writeTreeFile(t, repository, "nested/go.mod", []byte("module example.com/nested\n"))
|
||||
writeTreeFile(t, repository, "nested/token", []byte("fixture-secret\n"))
|
||||
writeTreeFile(t, repository, ".gitignore", []byte(".releasekit/\n"))
|
||||
gitTree(t, repository, "add", ".")
|
||||
gitTree(t, repository, "commit", "-m", "capture a committed credential path")
|
||||
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
|
||||
writeTreeFile(t, repository, ".releasekit/token", []byte("local-secret\n"))
|
||||
if err := os.Remove(filepath.Join(repository, "nested/token")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
installGoShim(t, "", false)
|
||||
request := Request{RepositoryDir: repository, Commit: commit}
|
||||
if err := Run(context.Background(), request); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request.Settings.UntrackedFiles = []string{".releasekit/token", "absent-token"}
|
||||
if err := Run(context.Background(), request); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request.Settings.UntrackedFiles = []string{"nested/./token"}
|
||||
err := Run(context.Background(), request)
|
||||
failure, ok := err.(*Failure)
|
||||
if !ok || failure.Check != UntrackedFiles || len(failure.Findings) != 1 || failure.Findings[0].Path != "nested/token" {
|
||||
t.Fatalf("untracked-files error = %v, want committed nested path despite worktree deletion", err)
|
||||
}
|
||||
if strings.Contains(err.Error(), "fixture-secret") {
|
||||
t.Fatal("validation exposed committed token bytes")
|
||||
}
|
||||
for _, invalid := range []string{"", "../token", "/token"} {
|
||||
request.Settings.UntrackedFiles = []string{invalid}
|
||||
if err := Run(context.Background(), request); err == nil {
|
||||
t.Fatalf("invalid path %q was accepted", invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ type Settings struct {
|
||||
FullVet bool
|
||||
CompileTargets []Target
|
||||
SharedFiles []string
|
||||
UntrackedFiles []string
|
||||
GoFormat bool
|
||||
Markdown *MarkdownSettings
|
||||
ModuleDistribution ModuleDistributionSettings
|
||||
@@ -134,7 +135,7 @@ func Run(ctx context.Context, request Request) (result error) {
|
||||
}
|
||||
}
|
||||
needsSharedSelection := len(request.Settings.SharedFiles) != 0
|
||||
if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles || request.Settings.ModuleDistribution.LocalReplacements || request.Settings.FileSize != nil || request.Settings.Markdown != nil || needsSharedSelection {
|
||||
if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles || request.Settings.ModuleDistribution.LocalReplacements || request.Settings.FileSize != nil || request.Settings.Markdown != nil || needsSharedSelection || len(request.Settings.UntrackedFiles) != 0 {
|
||||
tree, err := request.ReadCandidateTree(ctx)
|
||||
if err != nil {
|
||||
var failure *Failure
|
||||
@@ -154,6 +155,9 @@ func Run(ctx context.Context, request Request) (result error) {
|
||||
if request.Settings.GoFormat {
|
||||
check = GoFormat
|
||||
}
|
||||
if len(request.Settings.UntrackedFiles) != 0 {
|
||||
check = UntrackedFiles
|
||||
}
|
||||
return &Failure{Check: check, Cause: err}
|
||||
}
|
||||
lastContentCheck := Check("")
|
||||
@@ -166,6 +170,9 @@ func Run(ctx context.Context, request Request) (result error) {
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err := checkUntrackedFiles(tree, request.Settings.UntrackedFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Settings.GoFormat {
|
||||
readsBefore := tree.blobReads
|
||||
if err := checkGoFormat(ctx, tree); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user