Finish output directory follow-up work
This commit is contained in:
@@ -67,14 +67,54 @@ func resolveOutputDir(workingDir, override string) (string, error) {
|
||||
directory = filepath.Join(workingDir, directory)
|
||||
}
|
||||
directory = filepath.Clean(directory)
|
||||
if info, err := os.Stat(directory); err == nil && !info.IsDir() {
|
||||
return "", fmt.Errorf("output directory %q is not a directory", directory)
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("inspect output directory %q: %w", directory, err)
|
||||
if err := preflightOutputDirectory(directory); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return directory, nil
|
||||
}
|
||||
|
||||
func preflightOutputDirectory(directory string) error {
|
||||
info, err := os.Stat(directory)
|
||||
if err == nil {
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("output directory %q is not a directory", directory)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("inspect output directory %q: %w", directory, err)
|
||||
}
|
||||
|
||||
// A missing directory is valid, but os.Stat also reports ErrNotExist for a
|
||||
// dangling symlink. Walk to the first existing component so invalid links
|
||||
// fail preflight instead of being discovered only during publication.
|
||||
for component := directory; ; component = filepath.Dir(component) {
|
||||
componentInfo, componentErr := os.Lstat(component)
|
||||
if componentErr == nil {
|
||||
if componentInfo.Mode()&os.ModeSymlink != 0 {
|
||||
targetInfo, targetErr := os.Stat(component)
|
||||
if targetErr != nil {
|
||||
return fmt.Errorf("inspect output directory %q at %q: %w", directory, component, targetErr)
|
||||
}
|
||||
if !targetInfo.IsDir() {
|
||||
return fmt.Errorf("output directory %q has non-directory path component %q", directory, component)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !componentInfo.IsDir() {
|
||||
return fmt.Errorf("output directory %q has non-directory path component %q", directory, component)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !os.IsNotExist(componentErr) {
|
||||
return fmt.Errorf("inspect output directory %q at %q: %w", directory, component, componentErr)
|
||||
}
|
||||
if filepath.Dir(component) == component {
|
||||
return fmt.Errorf("inspect output directory %q: no existing directory ancestor", directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveOutputPath(workingDir, override, defaultName string) (string, error) {
|
||||
workingDir, err := validateWorkingDir(workingDir)
|
||||
if err != nil {
|
||||
|
||||
38
internal/app/output_test.go
Normal file
38
internal/app/output_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResolveOutputDirRejectsDanglingSymlinkComponents(t *testing.T) {
|
||||
workingDir := t.TempDir()
|
||||
dangling := filepath.Join(workingDir, "dangling")
|
||||
if err := os.Symlink(filepath.Join(workingDir, "missing"), dangling); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, directory := range []string{dangling, filepath.Join(dangling, "reports")} {
|
||||
t.Run(filepath.Base(directory), func(t *testing.T) {
|
||||
if _, err := resolveOutputDir(workingDir, directory); err == nil {
|
||||
t.Fatalf("resolveOutputDir(%q) error = nil, want dangling symlink error", directory)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveOutputDirAllowsMissingDirectoryBelowValidSymlink(t *testing.T) {
|
||||
workingDir := t.TempDir()
|
||||
target := t.TempDir()
|
||||
link := filepath.Join(workingDir, "linked")
|
||||
if err := os.Symlink(target, link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
directory := filepath.Join(link, "reports")
|
||||
got, err := resolveOutputDir(workingDir, directory)
|
||||
if err != nil || got != directory {
|
||||
t.Fatalf("resolveOutputDir() = %q, %v, want %q, nil", got, err, directory)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user