215 lines
5.7 KiB
Go
215 lines
5.7 KiB
Go
|
package plan
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// ActionConditions adds condition support to actions.
|
||
|
type ActionConditions struct {
|
||
|
// Conditions are checkers run before executing the
|
||
|
// action. If any one fails (returns an error), the action
|
||
|
// itself is not executed.
|
||
|
Conditions []Check
|
||
|
}
|
||
|
|
||
|
// Check runs the conditions associated with the action and returns
|
||
|
// the first error (if any).
|
||
|
func (c ActionConditions) Check(path string, setup Setup) error {
|
||
|
if len(c.Conditions) > 0 {
|
||
|
setup.Logf("checking action conditions")
|
||
|
}
|
||
|
for _, condition := range c.Conditions {
|
||
|
err := condition.Check(path, setup)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// OverwriteFileAction is used to overwrite a file.
|
||
|
type OverwriteFileAction struct {
|
||
|
ActionConditions
|
||
|
Params struct {
|
||
|
// Create determines whether the target directory
|
||
|
// will be created if it does not exist.
|
||
|
Create bool `json:"create"`
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Run implements plan.Action.Run.
|
||
|
func (a OverwriteFileAction) Run(path string, setup Setup) error {
|
||
|
setup.Logf("overwriting file %q", path)
|
||
|
src := setup.PathInRepo(SourceRepo, path)
|
||
|
dst := setup.PathInRepo(TargetRepo, path)
|
||
|
|
||
|
dstInfo, err := os.Stat(dst)
|
||
|
switch {
|
||
|
case os.IsNotExist(err):
|
||
|
if !a.Params.Create {
|
||
|
return fmt.Errorf("path %q does not exist, not creating", dst)
|
||
|
}
|
||
|
case err != nil:
|
||
|
return fmt.Errorf("failed to check path %q: %v", dst, err)
|
||
|
case dstInfo.IsDir():
|
||
|
return fmt.Errorf("path %q is a directory", dst)
|
||
|
}
|
||
|
|
||
|
srcInfo, err := os.Stat(src)
|
||
|
if os.IsNotExist(err) {
|
||
|
return fmt.Errorf("file %q does not exist", src)
|
||
|
} else if err != nil {
|
||
|
return fmt.Errorf("failed to check path %q: %v", src, err)
|
||
|
}
|
||
|
if srcInfo.IsDir() {
|
||
|
return fmt.Errorf("path %q is a directory", src)
|
||
|
}
|
||
|
|
||
|
srcF, err := os.Open(src)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to open %q: %v", src, err)
|
||
|
}
|
||
|
defer srcF.Close()
|
||
|
dstF, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, srcInfo.Mode())
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to open %q: %v", src, err)
|
||
|
}
|
||
|
defer dstF.Close()
|
||
|
_, err = io.Copy(dstF, srcF)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to copy file %q: %v", path, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// OverwriteDirectoryAction is used to completely overwrite directories.
|
||
|
// If the target directory exists, it will be removed first.
|
||
|
type OverwriteDirectoryAction struct {
|
||
|
ActionConditions
|
||
|
Params struct {
|
||
|
// Create determines whether the target directory
|
||
|
// will be created if it does not exist.
|
||
|
Create bool `json:"create"`
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Run implements plan.Action.Run.
|
||
|
func (a OverwriteDirectoryAction) Run(path string, setup Setup) error {
|
||
|
setup.Logf("overwriting directory %q", path)
|
||
|
src := setup.PathInRepo(SourceRepo, path)
|
||
|
dst := setup.PathInRepo(TargetRepo, path)
|
||
|
|
||
|
dstInfo, err := os.Stat(dst)
|
||
|
switch {
|
||
|
case os.IsNotExist(err):
|
||
|
if !a.Params.Create {
|
||
|
return fmt.Errorf("path %q does not exist, not creating", dst)
|
||
|
}
|
||
|
case err != nil:
|
||
|
return fmt.Errorf("failed to check path %q: %v", dst, err)
|
||
|
default:
|
||
|
if !dstInfo.IsDir() {
|
||
|
return fmt.Errorf("path %q is not a directory", dst)
|
||
|
}
|
||
|
err = os.RemoveAll(dst)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to remove directory %q: %v", dst, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
srcInfo, err := os.Stat(src)
|
||
|
if os.IsNotExist(err) {
|
||
|
return fmt.Errorf("directory %q does not exist", src)
|
||
|
} else if err != nil {
|
||
|
return fmt.Errorf("failed to check path %q: %v", src, err)
|
||
|
}
|
||
|
if !srcInfo.IsDir() {
|
||
|
return fmt.Errorf("path %q is not a directory", src)
|
||
|
}
|
||
|
|
||
|
err = CopyDirectory(src, dst)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to copy path %q: %v", path, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// CopyDirectory copies the directory src to dst so that after
|
||
|
// a successful operation the contents of src and dst are equal.
|
||
|
func CopyDirectory(src, dst string) error {
|
||
|
copier := dirCopier{dst: dst, src: src}
|
||
|
return filepath.Walk(src, copier.Copy)
|
||
|
}
|
||
|
|
||
|
type dirCopier struct {
|
||
|
dst string
|
||
|
src string
|
||
|
}
|
||
|
|
||
|
// Convert a path in the source directory to a path in the destination
|
||
|
// directory.
|
||
|
func (d dirCopier) srcToDst(path string) (string, error) {
|
||
|
suff := strings.TrimPrefix(path, d.src)
|
||
|
if suff == path {
|
||
|
return "", fmt.Errorf("path %q is not in %q", path, d.src)
|
||
|
}
|
||
|
return filepath.Join(d.dst, suff), nil
|
||
|
}
|
||
|
|
||
|
// Copy is an implementation of filepatch.WalkFunc that copies the
|
||
|
// source directory to target with all subdirectories.
|
||
|
func (d dirCopier) Copy(srcPath string, info os.FileInfo, err error) error {
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to copy directory: %v", err)
|
||
|
}
|
||
|
trgPath, err := d.srcToDst(srcPath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if info.IsDir() {
|
||
|
err = os.MkdirAll(trgPath, info.Mode())
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to create directory %q: %v", trgPath, err)
|
||
|
}
|
||
|
err = os.Chtimes(trgPath, info.ModTime(), info.ModTime())
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to create directory %q: %v", trgPath, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
err = copyFile(srcPath, trgPath, info)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to copy file %q: %v", srcPath, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func copyFile(src, dst string, info os.FileInfo) error {
|
||
|
srcF, err := os.Open(src)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to open source file %q: %v", src, err)
|
||
|
}
|
||
|
defer srcF.Close()
|
||
|
dstF, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, info.Mode())
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to open destination file %q: %v", dst, err)
|
||
|
}
|
||
|
_, err = io.Copy(dstF, srcF)
|
||
|
if err != nil {
|
||
|
dstF.Close()
|
||
|
return fmt.Errorf("failed to copy file %q: %v", src, err)
|
||
|
}
|
||
|
if err = dstF.Close(); err != nil {
|
||
|
return fmt.Errorf("failed to close file %q: %v", dst, err)
|
||
|
}
|
||
|
err = os.Chtimes(dst, info.ModTime(), info.ModTime())
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to adjust file modification time for %q: %v", dst, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|