Reduce comparison bundle recognition reads
This commit is contained in:
@@ -73,6 +73,18 @@ var ErrUnrecognizedBundle = errors.New("unrecognized comparison bundle")
|
||||
|
||||
// PlanDestination performs the read-only comparison destination preflight.
|
||||
func PlanDestination(workingDirectory, target string, replace bool) (DestinationPlan, error) {
|
||||
return planDestination(workingDirectory, target, replace, RecognizeBundle)
|
||||
}
|
||||
|
||||
func planDestination(workingDirectory, target string, replace bool, recognize func(string) (Manifest, error)) (DestinationPlan, error) {
|
||||
plan, err := newDestinationPlan(workingDirectory, target, replace)
|
||||
if err != nil {
|
||||
return DestinationPlan{}, err
|
||||
}
|
||||
return inspectDestination(plan, recognize)
|
||||
}
|
||||
|
||||
func newDestinationPlan(workingDirectory, target string, replace bool) (DestinationPlan, error) {
|
||||
workingDirectory, err := absoluteCleanPath(workingDirectory)
|
||||
if err != nil {
|
||||
return DestinationPlan{}, newDestinationError(DestinationInvalidPath, workingDirectory, err)
|
||||
@@ -91,7 +103,19 @@ func PlanDestination(workingDirectory, target string, replace bool) (Destination
|
||||
return DestinationPlan{}, newDestinationError(DestinationInvalidPath, target, err)
|
||||
}
|
||||
|
||||
plan := DestinationPlan{WorkingDirectory: workingDirectory, Target: target, Replace: replace}
|
||||
return DestinationPlan{WorkingDirectory: workingDirectory, Target: target, Replace: replace}, nil
|
||||
}
|
||||
|
||||
func reauthorizeDestination(plan DestinationPlan) (DestinationPlan, error) {
|
||||
plan, err := newDestinationPlan(plan.WorkingDirectory, plan.Target, plan.Replace)
|
||||
if err != nil {
|
||||
return DestinationPlan{}, err
|
||||
}
|
||||
return inspectDestination(plan, nil)
|
||||
}
|
||||
|
||||
func inspectDestination(plan DestinationPlan, recognize func(string) (Manifest, error)) (DestinationPlan, error) {
|
||||
target := plan.Target
|
||||
info, err := os.Lstat(target)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
@@ -117,11 +141,13 @@ func PlanDestination(workingDirectory, target string, replace bool) (Destination
|
||||
plan.state = destinationEmpty
|
||||
return plan, nil
|
||||
}
|
||||
if !replace {
|
||||
if !plan.Replace {
|
||||
return DestinationPlan{}, newDestinationError(DestinationNotEmpty, target, nil)
|
||||
}
|
||||
if _, err := RecognizeBundle(target); err != nil {
|
||||
return DestinationPlan{}, newDestinationError(DestinationUnrecognized, target, err)
|
||||
if recognize != nil {
|
||||
if _, err := recognize(target); err != nil {
|
||||
return DestinationPlan{}, newDestinationError(DestinationUnrecognized, target, err)
|
||||
}
|
||||
}
|
||||
plan.state = destinationBundle
|
||||
return plan, nil
|
||||
@@ -188,7 +214,25 @@ func newDestinationError(kind DestinationErrorKind, target string, err error) er
|
||||
// RecognizeBundle verifies that directory contains exactly one valid current
|
||||
// comparison bundle. It never follows bundle entries through symlinks.
|
||||
func RecognizeBundle(directory string) (Manifest, error) {
|
||||
info, err := os.Lstat(directory)
|
||||
return recognizeBundle(directory, defaultRecognitionOperations)
|
||||
}
|
||||
|
||||
type recognitionOperations struct {
|
||||
lstat func(string) (os.FileInfo, error)
|
||||
readDir func(string) ([]os.DirEntry, error)
|
||||
open func(string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
var defaultRecognitionOperations = recognitionOperations{
|
||||
lstat: os.Lstat,
|
||||
readDir: os.ReadDir,
|
||||
open: func(path string) (io.ReadCloser, error) {
|
||||
return os.Open(path)
|
||||
},
|
||||
}
|
||||
|
||||
func recognizeBundle(directory string, operations recognitionOperations) (Manifest, error) {
|
||||
info, err := operations.lstat(directory)
|
||||
if err != nil {
|
||||
return Manifest{}, unrecognizedBundleError("inspect directory", err)
|
||||
}
|
||||
@@ -196,11 +240,11 @@ func RecognizeBundle(directory string) (Manifest, error) {
|
||||
return Manifest{}, unrecognizedBundleError("directory is not a real directory", nil)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(directory)
|
||||
entries, err := operations.readDir(directory)
|
||||
if err != nil {
|
||||
return Manifest{}, unrecognizedBundleError("read directory", err)
|
||||
}
|
||||
manifestData, err := readBundleFile(directory, ManifestFilename)
|
||||
manifestData, err := readBundleFile(directory, ManifestFilename, operations)
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
@@ -228,12 +272,15 @@ func RecognizeBundle(directory string) (Manifest, error) {
|
||||
if _, ok := expected[entry.Name()]; !ok {
|
||||
return Manifest{}, unrecognizedBundleError("directory has an undeclared entry", nil)
|
||||
}
|
||||
if _, err := readBundleFile(directory, entry.Name()); err != nil {
|
||||
if entry.Name() == ManifestFilename || entry.Name() == DataPackageFilename {
|
||||
continue
|
||||
}
|
||||
if err := inspectBundleFile(directory, entry.Name(), operations); err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
}
|
||||
|
||||
dataPackage, err := readBundleFile(directory, DataPackageFilename)
|
||||
dataPackage, err := readBundleFile(directory, DataPackageFilename, operations)
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
@@ -243,23 +290,47 @@ func RecognizeBundle(directory string) (Manifest, error) {
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
func readBundleFile(directory, name string) ([]byte, error) {
|
||||
func readBundleFile(directory, name string, operations recognitionOperations) ([]byte, error) {
|
||||
file, err := openBundleFile(directory, name, operations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, unrecognizedBundleError("read bundle entry", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func inspectBundleFile(directory, name string, operations recognitionOperations) error {
|
||||
file, err := openBundleFile(directory, name, operations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return unrecognizedBundleError("close bundle entry", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func openBundleFile(directory, name string, operations recognitionOperations) (io.ReadCloser, error) {
|
||||
if !isArtifactBasename(name) {
|
||||
return nil, unrecognizedBundleError("bundle entry name is unsafe", nil)
|
||||
}
|
||||
filePath := filepath.Join(directory, name)
|
||||
info, err := os.Lstat(filePath)
|
||||
info, err := operations.lstat(filePath)
|
||||
if err != nil {
|
||||
return nil, unrecognizedBundleError("inspect bundle entry", err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
||||
return nil, unrecognizedBundleError("bundle entry is not a regular file", nil)
|
||||
}
|
||||
data, err := os.ReadFile(filePath)
|
||||
file, err := operations.open(filePath)
|
||||
if err != nil {
|
||||
return nil, unrecognizedBundleError("read bundle entry", err)
|
||||
}
|
||||
return data, nil
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func decodeManifest(data []byte) (Manifest, error) {
|
||||
@@ -499,6 +570,7 @@ type publishOperations struct {
|
||||
rename func(string, string) error
|
||||
removeAll func(string) error
|
||||
beforeCommit func()
|
||||
recognize func(string) (Manifest, error)
|
||||
}
|
||||
|
||||
func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, operations publishOperations) (PublicationResult, error) {
|
||||
@@ -508,6 +580,9 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
|
||||
if operations.removeAll == nil {
|
||||
operations.removeAll = os.RemoveAll
|
||||
}
|
||||
if operations.recognize == nil {
|
||||
operations.recognize = RecognizeBundle
|
||||
}
|
||||
if err := bundle.Validate(); err != nil {
|
||||
return PublicationResult{}, fmt.Errorf("validate comparison bundle: %w", err)
|
||||
}
|
||||
@@ -518,7 +593,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
|
||||
if err := ctx.Err(); err != nil {
|
||||
return PublicationResult{}, err
|
||||
}
|
||||
plan, err = PlanDestination(plan.WorkingDirectory, plan.Target, plan.Replace)
|
||||
plan, err = reauthorizeDestination(plan)
|
||||
if err != nil {
|
||||
return PublicationResult{}, err
|
||||
}
|
||||
@@ -544,7 +619,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
|
||||
return PublicationResult{}, err
|
||||
}
|
||||
|
||||
currentPlan, err := PlanDestination(plan.WorkingDirectory, plan.Target, plan.Replace)
|
||||
currentPlan, err := reauthorizeDestination(plan)
|
||||
if err != nil {
|
||||
return PublicationResult{}, err
|
||||
}
|
||||
@@ -569,7 +644,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
|
||||
if err := operations.rename(currentPlan.Target, backupDirectory); err != nil {
|
||||
return PublicationResult{}, fmt.Errorf("back up comparison destination %q: %w", currentPlan.Target, err)
|
||||
}
|
||||
if err := authorizeMovedDestination(currentPlan, backupDirectory); err != nil {
|
||||
if err := authorizeMovedDestination(currentPlan, backupDirectory, operations.recognize); err != nil {
|
||||
return PublicationResult{}, restoreMovedDestination(operations, backupDirectory, currentPlan.Target, err)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
@@ -600,8 +675,8 @@ func inspectBackupRecovery(backupDirectory string) (BackupRecoveryState, string)
|
||||
return BackupRecoveryPartial, backupDirectory
|
||||
}
|
||||
|
||||
func authorizeMovedDestination(plan DestinationPlan, backupDirectory string) error {
|
||||
backupPlan, err := PlanDestination(plan.WorkingDirectory, backupDirectory, plan.Replace)
|
||||
func authorizeMovedDestination(plan DestinationPlan, backupDirectory string, recognize func(string) (Manifest, error)) error {
|
||||
backupPlan, err := planDestination(plan.WorkingDirectory, backupDirectory, plan.Replace, recognize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("authorize moved comparison destination %q: %w", backupDirectory, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user