зеркало из https://github.com/golang/dep.git
Add WIP transactional safe writer
Very much incomplete, in the process of being adapted from the one I already wrote for glide.
This commit is contained in:
Родитель
082fe0003c
Коммит
ffd999ed29
|
@ -0,0 +1,225 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/sdboyer/gps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// safeWriter transactionalizes writes of manifest, lock, and vendor dir, both
|
||||||
|
// individually and in any combination, into a pseudo-atomic action with
|
||||||
|
// transactional rollback.
|
||||||
|
//
|
||||||
|
// It is not impervious to errors (writing to disk is hard), but it should
|
||||||
|
// guard against non-arcane failure conditions.
|
||||||
|
type safeWriter struct {
|
||||||
|
root string
|
||||||
|
conf *manifest // the manifest to write
|
||||||
|
lock *lock // a struct representation of the current lock, if any
|
||||||
|
nl gps.Lock // the new lock to write, if desired
|
||||||
|
sm gps.SourceManager
|
||||||
|
mpath, vendor string
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeAllSafe writes out some combination of config yaml, lock, and a vendor
|
||||||
|
// tree, to a temp dir, then moves them into place if and only if all the write
|
||||||
|
// operations succeeded. It also does its best to roll back if any moves fail.
|
||||||
|
//
|
||||||
|
// This mostly guarantees that dep cannot terminate with a partial write,
|
||||||
|
// resulting in an undefined disk state.
|
||||||
|
//
|
||||||
|
// - If a gw.conf is provided, it will be written to the standard manifest file
|
||||||
|
// name beneath gw.pr
|
||||||
|
// - If gw.lock is provided without a gw.nl, it will be written to
|
||||||
|
// `glide.lock` in the parent dir of gw.vendor
|
||||||
|
// - If gw.lock and gw.nl are both provided and are not equivalent,
|
||||||
|
// the nl will be written to the same location as above, and a vendor
|
||||||
|
// tree will be written to gw.vendor
|
||||||
|
// - If gw.nl is provided and gw.lock is not, it will write both a lock
|
||||||
|
// and vendor dir in the same way
|
||||||
|
//
|
||||||
|
// Any of the conf, lock, or result can be omitted; the grouped write operation
|
||||||
|
// will continue for whichever inputs are present.
|
||||||
|
func (gw safeWriter) writeAllSafe() error {
|
||||||
|
// Decide which writes we need to do
|
||||||
|
var writeConf, writeLock, writeVendor bool
|
||||||
|
|
||||||
|
if gw.conf != nil {
|
||||||
|
writeConf = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if gw.nl != nil {
|
||||||
|
if gw.lock == nil {
|
||||||
|
writeLock, writeVendor = true, true
|
||||||
|
} else {
|
||||||
|
rlf := lockFromInterface(gw.nl)
|
||||||
|
// This err really shouldn't occur, but could if we get an unpaired
|
||||||
|
// version back from gps somehow
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !locksAreEquivalent(rlf, gw.lock) {
|
||||||
|
writeLock, writeVendor = true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if gw.lock != nil {
|
||||||
|
writeLock = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !writeConf && !writeLock && !writeVendor {
|
||||||
|
// nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeConf && gw.mpath == "" {
|
||||||
|
return fmt.Errorf("Must provide a path if writing out a config yaml.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writeLock || writeVendor) && gw.vendor == "" {
|
||||||
|
return fmt.Errorf("Must provide a vendor dir if writing out a lock or vendor dir.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeVendor && gw.sm == nil {
|
||||||
|
return fmt.Errorf("Must provide a SourceManager if writing out a vendor dir.")
|
||||||
|
}
|
||||||
|
|
||||||
|
td, err := ioutil.TempDir(os.TempDir(), "glide")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error while creating temp dir for vendor directory: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
if writeConf {
|
||||||
|
if err := gw.conf.WriteFile(filepath.Join(td, "glide.yaml")); err != nil {
|
||||||
|
return fmt.Errorf("Failed to write glide YAML file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeLock {
|
||||||
|
if gw.nl == nil {
|
||||||
|
// the result lock is nil but the flag is on, so we must be writing
|
||||||
|
// the other one
|
||||||
|
if err := gw.lock.WriteFile(filepath.Join(td, gpath.LockFile)); err != nil {
|
||||||
|
return fmt.Errorf("Failed to write glide lock file: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rlf, err := lockFromInterface(gw.nl)
|
||||||
|
// As with above, this case really shouldn't get hit unless there's
|
||||||
|
// a bug in gps, or guarantees change
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rlf.WriteFile(filepath.Join(td, gpath.LockFile)); err != nil {
|
||||||
|
return fmt.Errorf("Failed to write glide lock file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeVendor {
|
||||||
|
err = gps.WriteDepTree(filepath.Join(td, "vendor"), gw.nl, gw.sm, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error while generating vendor tree: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the existing files and dirs to the temp dir while we put the new
|
||||||
|
// ones in, to provide insurance against errors for as long as possible
|
||||||
|
var fail bool
|
||||||
|
var failerr error
|
||||||
|
type pathpair struct {
|
||||||
|
from, to string
|
||||||
|
}
|
||||||
|
var restore []pathpair
|
||||||
|
|
||||||
|
if writeConf {
|
||||||
|
if _, err := os.Stat(gw.mpath); err == nil {
|
||||||
|
// move out the old one
|
||||||
|
tmploc := filepath.Join(td, "glide.yaml-old")
|
||||||
|
failerr = os.Rename(gw.mpath, tmploc)
|
||||||
|
if failerr != nil {
|
||||||
|
fail = true
|
||||||
|
} else {
|
||||||
|
restore = append(restore, pathpair{from: tmploc, to: gw.mpath})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move in the new one
|
||||||
|
failerr = os.Rename(filepath.Join(td, manifestName), gw.mpath)
|
||||||
|
if failerr != nil {
|
||||||
|
fail = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fail && writeLock {
|
||||||
|
tgt := filepath.Join(filepath.Dir(gw.vendor), gpath.LockFile)
|
||||||
|
if _, err := os.Stat(tgt); err == nil {
|
||||||
|
// move out the old one
|
||||||
|
tmploc := filepath.Join(td, "glide.lock-old")
|
||||||
|
|
||||||
|
failerr = os.Rename(tgt, tmploc)
|
||||||
|
if failerr != nil {
|
||||||
|
fail = true
|
||||||
|
} else {
|
||||||
|
restore = append(restore, pathpair{from: tmploc, to: tgt})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move in the new one
|
||||||
|
failerr = os.Rename(filepath.Join(td, gpath.LockFile), tgt)
|
||||||
|
if failerr != nil {
|
||||||
|
fail = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// have to declare out here so it's present later
|
||||||
|
var vendorbak string
|
||||||
|
if !fail && writeVendor {
|
||||||
|
if _, err := os.Stat(gw.vendor); err == nil {
|
||||||
|
// move out the old vendor dir. just do it into an adjacent dir, in
|
||||||
|
// order to mitigate the possibility of a pointless cross-filesystem move
|
||||||
|
vendorbak = gw.vendor + "-old"
|
||||||
|
if _, err := os.Stat(vendorbak); err == nil {
|
||||||
|
// Just in case that happens to exist...
|
||||||
|
vendorbak = filepath.Join(td, "vendor-old")
|
||||||
|
}
|
||||||
|
failerr = os.Rename(gw.vendor, vendorbak)
|
||||||
|
if failerr != nil {
|
||||||
|
fail = true
|
||||||
|
} else {
|
||||||
|
restore = append(restore, pathpair{from: vendorbak, to: gw.vendor})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move in the new one
|
||||||
|
failerr = os.Rename(filepath.Join(td, "vendor"), gw.vendor)
|
||||||
|
if failerr != nil {
|
||||||
|
fail = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we failed at any point, move all the things back into place, then bail
|
||||||
|
if fail {
|
||||||
|
for _, pair := range restore {
|
||||||
|
// Nothing we can do on err here, we're already in recovery mode
|
||||||
|
os.Rename(pair.from, pair.to)
|
||||||
|
}
|
||||||
|
return failerr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renames all went smoothly. The deferred os.RemoveAll will get the temp
|
||||||
|
// dir, but if we wrote vendor, we have to clean that up directly
|
||||||
|
|
||||||
|
if writeVendor {
|
||||||
|
// Again, kinda nothing we can do about an error at this point
|
||||||
|
os.RemoveAll(vendorbak)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче