Centralize managed state targets
This commit is contained in:
@@ -234,22 +234,10 @@ func (b *Backend) DeleteManagedBundle(ctx context.Context, bundlePath string, ma
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storage.ValidatePrefix(bundlePath); err != nil {
|
||||
return err
|
||||
}
|
||||
targets := make([]string, 0, len(managedOutputPaths)+1)
|
||||
for _, outputPath := range managedOutputPaths {
|
||||
target, err := storage.Join(bundlePath, outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets = append(targets, target)
|
||||
}
|
||||
statePath, err := storage.StatePath(bundlePath)
|
||||
targets, err := storage.ManagedBundleTargets(bundlePath, managedOutputPaths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets = append(targets, statePath)
|
||||
|
||||
for _, logicalPath := range targets {
|
||||
nativePath, err := b.nativePath(logicalPath, false)
|
||||
|
||||
@@ -145,16 +145,20 @@ func TestBackendManagedDeletion(t *testing.T) {
|
||||
backend := newBackend(t)
|
||||
mustWrite(t, backend, "bundle/report.html", "html")
|
||||
mustWrite(t, backend, "bundle/keep.txt", "keep")
|
||||
mustWrite(t, backend, "bundle/.distributor.json", "{}")
|
||||
statePath, err := storage.StatePath("bundle")
|
||||
if err != nil {
|
||||
t.Fatalf("StatePath() error = %v", err)
|
||||
}
|
||||
mustWrite(t, backend, statePath, "{}")
|
||||
|
||||
err := backend.DeleteManagedBundle(context.Background(), "bundle", []string{"report.html"}, storage.DeleteOptions{PruneEmptyDirs: true})
|
||||
err = backend.DeleteManagedBundle(context.Background(), "bundle", []string{"report.html"}, storage.DeleteOptions{PruneEmptyDirs: true})
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteManagedBundle() error = %v", err)
|
||||
}
|
||||
if _, err := backend.Stat(context.Background(), "bundle/report.html"); !storage.IsNotFound(err) {
|
||||
t.Fatalf("managed output stat error = %v, want not found", err)
|
||||
}
|
||||
if _, err := backend.Stat(context.Background(), "bundle/.distributor.json"); !storage.IsNotFound(err) {
|
||||
if _, err := backend.Stat(context.Background(), statePath); !storage.IsNotFound(err) {
|
||||
t.Fatalf("state stat error = %v, want not found", err)
|
||||
}
|
||||
if _, err := backend.Stat(context.Background(), "bundle/keep.txt"); err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/notify"
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/state"
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
||||
)
|
||||
|
||||
func TestRunDryRunPrintsConfigSummary(t *testing.T) {
|
||||
@@ -63,7 +64,7 @@ func TestRunPublishesNewLocalBundle(t *testing.T) {
|
||||
if _, err := os.Stat(filepath.Join(destinationRoot, "manifest.json")); !os.IsNotExist(err) {
|
||||
t.Fatalf("destination manifest stat error = %v, want not exist", err)
|
||||
}
|
||||
destinationState := readStateFile(t, filepath.Join(destinationRoot, ".distributor.json"))
|
||||
destinationState := readStateFile(t, filepath.Join(destinationRoot, storage.StateFileName))
|
||||
if destinationState.PipelineID != "reports" || destinationState.DestinationID != "archive" {
|
||||
t.Fatalf("state identity = %s/%s", destinationState.PipelineID, destinationState.DestinationID)
|
||||
}
|
||||
@@ -81,7 +82,7 @@ func TestRunNotifiesAfterPublication(t *testing.T) {
|
||||
writeSourceBundle(t, sourceRoot, "", testBundleOptions{})
|
||||
notifier := &recordingNotifier{
|
||||
check: func() {
|
||||
if _, err := os.Stat(filepath.Join(destinationRoot, ".distributor.json")); err != nil {
|
||||
if _, err := os.Stat(filepath.Join(destinationRoot, storage.StateFileName)); err != nil {
|
||||
t.Fatalf("state stat during notify: %v", err)
|
||||
}
|
||||
},
|
||||
@@ -217,7 +218,7 @@ func TestRunPublishesHTMLOnly(t *testing.T) {
|
||||
if _, err := os.Stat(filepath.Join(destinationRoot, "report.md")); !os.IsNotExist(err) {
|
||||
t.Fatalf("report.md stat error = %v, want not exist", err)
|
||||
}
|
||||
destinationState := readStateFile(t, filepath.Join(destinationRoot, ".distributor.json"))
|
||||
destinationState := readStateFile(t, filepath.Join(destinationRoot, storage.StateFileName))
|
||||
if got, want := len(destinationState.Outputs), 1; got != want {
|
||||
t.Fatalf("state output count = %d, want %d", got, want)
|
||||
}
|
||||
@@ -239,7 +240,7 @@ func TestRunPublishesSourceAndHTML(t *testing.T) {
|
||||
assertFile(t, filepath.Join(destinationRoot, "report.md"), "# Report\nSunny.\n")
|
||||
assertFileContains(t, filepath.Join(destinationRoot, "report.html"), "<p>Sunny.</p>")
|
||||
assertFile(t, filepath.Join(destinationRoot, "summary.txt"), "Summary\n")
|
||||
destinationState := readStateFile(t, filepath.Join(destinationRoot, ".distributor.json"))
|
||||
destinationState := readStateFile(t, filepath.Join(destinationRoot, storage.StateFileName))
|
||||
if got, want := len(destinationState.Outputs), 3; got != want {
|
||||
t.Fatalf("state output count = %d, want %d", got, want)
|
||||
}
|
||||
@@ -567,7 +568,7 @@ func writeDestinationState(t *testing.T, root, relative string, manifest bundle.
|
||||
t.Fatalf("marshal state: %v", err)
|
||||
}
|
||||
data = append(data, '\n')
|
||||
if err := os.WriteFile(filepath.Join(bundleRoot, ".distributor.json"), data, 0o600); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(bundleRoot, storage.StateFileName), data, 0o600); err != nil {
|
||||
t.Fatalf("write state: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
||||
)
|
||||
|
||||
func TestParseManifestValid(t *testing.T) {
|
||||
@@ -71,7 +73,7 @@ func TestParseManifestRejectsUnsafeFilePaths(t *testing.T) {
|
||||
`"path": "/report.md"`,
|
||||
`"path": "nested/../report.md"`,
|
||||
`"path": "manifest.json"`,
|
||||
`"path": ".distributor.json"`,
|
||||
`"path": "` + storage.StateFileName + `"`,
|
||||
}
|
||||
for _, replacement := range tests {
|
||||
t.Run(replacement, func(t *testing.T) {
|
||||
|
||||
@@ -12,7 +12,7 @@ func ValidateSourcePath(path string) error {
|
||||
return err
|
||||
}
|
||||
switch path {
|
||||
case ManifestName, ".distributor.json":
|
||||
case ManifestName, storage.StateFileName:
|
||||
return fmt.Errorf("%q is reserved", path)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
||||
)
|
||||
|
||||
func TestExecuteRootHelp(t *testing.T) {
|
||||
@@ -128,7 +130,7 @@ pipelines:
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(destinationRoot, ".distributor.json")); err != nil {
|
||||
if _, err := os.Stat(filepath.Join(destinationRoot, storage.StateFileName)); err != nil {
|
||||
t.Fatalf("state stat error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,22 +181,10 @@ func (b *Backend) DeleteManagedBundle(ctx context.Context, bundlePath string, ma
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storage.ValidatePrefix(bundlePath); err != nil {
|
||||
return err
|
||||
}
|
||||
targets := make([]string, 0, len(managedOutputPaths)+1)
|
||||
for _, outputPath := range managedOutputPaths {
|
||||
target, err := storage.Join(bundlePath, outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets = append(targets, target)
|
||||
}
|
||||
statePath, err := storage.StatePath(bundlePath)
|
||||
targets, err := storage.ManagedBundleTargets(bundlePath, managedOutputPaths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets = append(targets, statePath)
|
||||
|
||||
for _, target := range targets {
|
||||
if _, ok := b.dirs[target]; ok {
|
||||
|
||||
@@ -116,21 +116,28 @@ func TestBackendManagedDeletion(t *testing.T) {
|
||||
backend := New()
|
||||
mustWrite(t, backend, "bundle/report.html", "html")
|
||||
mustWrite(t, backend, "bundle/keep.txt", "keep")
|
||||
mustWrite(t, backend, "bundle/.distributor.json", "{}")
|
||||
statePath, err := storage.StatePath("bundle")
|
||||
if err != nil {
|
||||
t.Fatalf("StatePath() error = %v", err)
|
||||
}
|
||||
mustWrite(t, backend, statePath, "{}")
|
||||
|
||||
err := backend.DeleteManagedBundle(context.Background(), "bundle", []string{"report.html"}, storage.DeleteOptions{PruneEmptyDirs: true})
|
||||
err = backend.DeleteManagedBundle(context.Background(), "bundle", []string{"report.html"}, storage.DeleteOptions{PruneEmptyDirs: true})
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteManagedBundle() error = %v", err)
|
||||
}
|
||||
if _, err := backend.Stat(context.Background(), "bundle/report.html"); !storage.IsNotFound(err) {
|
||||
t.Fatalf("managed output stat error = %v, want not found", err)
|
||||
}
|
||||
if _, err := backend.Stat(context.Background(), "bundle/.distributor.json"); !storage.IsNotFound(err) {
|
||||
if _, err := backend.Stat(context.Background(), statePath); !storage.IsNotFound(err) {
|
||||
t.Fatalf("state stat error = %v, want not found", err)
|
||||
}
|
||||
if _, err := backend.Stat(context.Background(), "bundle/keep.txt"); err != nil {
|
||||
t.Fatalf("unlisted file stat error = %v", err)
|
||||
}
|
||||
if err := backend.DeleteManagedBundle(context.Background(), "bundle", []string{""}, storage.DeleteOptions{}); !storage.IsInvalidPath(err) {
|
||||
t.Fatalf("DeleteManagedBundle invalid output error = %v, want invalid path", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendHasAnyAndWalkStop(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const stateFileName = ".distributor.json"
|
||||
const StateFileName = ".distributor.json"
|
||||
|
||||
func ValidatePath(value string) error {
|
||||
if value == "" {
|
||||
@@ -37,9 +37,29 @@ func Join(base, child string) (string, error) {
|
||||
|
||||
func StatePath(bundlePath string) (string, error) {
|
||||
if bundlePath == "" {
|
||||
return stateFileName, nil
|
||||
return StateFileName, nil
|
||||
}
|
||||
return Join(bundlePath, stateFileName)
|
||||
return Join(bundlePath, StateFileName)
|
||||
}
|
||||
|
||||
func ManagedBundleTargets(bundlePath string, managedOutputPaths []string) ([]string, error) {
|
||||
if err := ValidatePrefix(bundlePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets := make([]string, 0, len(managedOutputPaths)+1)
|
||||
for _, outputPath := range managedOutputPaths {
|
||||
target, err := Join(bundlePath, outputPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets = append(targets, target)
|
||||
}
|
||||
statePath, err := StatePath(bundlePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets = append(targets, statePath)
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
func SortEntries(entries []Entry) {
|
||||
|
||||
@@ -49,6 +49,67 @@ func TestValidatePrefixAllowsRoot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatePath(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"": StateFileName,
|
||||
"bundle": "bundle/" + StateFileName,
|
||||
}
|
||||
for bundlePath, want := range tests {
|
||||
t.Run(bundlePath, func(t *testing.T) {
|
||||
got, err := StatePath(bundlePath)
|
||||
if err != nil {
|
||||
t.Fatalf("StatePath(%q) error = %v", bundlePath, err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("StatePath(%q) = %q, want %q", bundlePath, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagedBundleTargets(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bundlePath string
|
||||
outputs []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "root",
|
||||
outputs: []string{"report.html", "assets/style.css"},
|
||||
want: []string{"report.html", "assets/style.css", StateFileName},
|
||||
},
|
||||
{
|
||||
name: "nested",
|
||||
bundlePath: "daily",
|
||||
outputs: []string{"report.html"},
|
||||
want: []string{"daily/report.html", "daily/" + StateFileName},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ManagedBundleTargets(tt.bundlePath, tt.outputs)
|
||||
if err != nil {
|
||||
t.Fatalf("ManagedBundleTargets() error = %v", err)
|
||||
}
|
||||
if len(got) != len(tt.want) {
|
||||
t.Fatalf("targets = %v, want %v", got, tt.want)
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Fatalf("targets = %v, want %v", got, tt.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagedBundleTargetsRejectsInvalidOutputPath(t *testing.T) {
|
||||
if _, err := ManagedBundleTargets("bundle", []string{"../outside"}); !IsInvalidPath(err) {
|
||||
t.Fatalf("ManagedBundleTargets() error = %v, want invalid path", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSortsEntries(t *testing.T) {
|
||||
backend := walkBackend{
|
||||
entries: []Entry{
|
||||
|
||||
Reference in New Issue
Block a user