Merge pull request #140 from jonboulle/atomic
fix(system): write all files atomically
This commit is contained in:
commit
f127406d01
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
||||||
|
|
||||||
@ -221,11 +220,11 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range cfg.WriteFiles {
|
for _, file := range cfg.WriteFiles {
|
||||||
file.Path = path.Join(env.Root(), file.Path)
|
path, err := system.WriteFile(&file, env.Root())
|
||||||
if err := system.WriteFile(&file); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Wrote file %s to filesystem", file.Path)
|
log.Printf("Wrote file %s to filesystem", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := make(map[string]string, 0)
|
commands := make(map[string]string, 0)
|
||||||
|
@ -50,9 +50,7 @@ func TestEtcHostsWrittenToDisk(t *testing.T) {
|
|||||||
t.Fatalf("manageEtcHosts returned nil file unexpectedly")
|
t.Fatalf("manageEtcHosts returned nil file unexpectedly")
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Path = path.Join(dir, f.Path)
|
if _, err := system.WriteFile(f, dir); err != nil {
|
||||||
|
|
||||||
if err := system.WriteFile(f); err != nil {
|
|
||||||
t.Fatalf("Error writing EtcHosts: %v", err)
|
t.Fatalf("Error writing EtcHosts: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,7 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
|||||||
t.Fatalf("OEMRelease returned nil file unexpectedly")
|
t.Fatalf("OEMRelease returned nil file unexpectedly")
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Path = path.Join(dir, f.Path)
|
if _, err := system.WriteFile(f, dir); err != nil {
|
||||||
if err := system.WriteFile(f); err != nil {
|
|
||||||
t.Fatalf("Writing of OEMRelease failed: %v", err)
|
t.Fatalf("Writing of OEMRelease failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +205,7 @@ func TestUpdateConfWrittenToDisk(t *testing.T) {
|
|||||||
t.Fatal("Unexpectedly got nil updateconfig file")
|
t.Fatal("Unexpectedly got nil updateconfig file")
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Path = path.Join(dir, f.Path)
|
if _, err := system.WriteFile(f, dir); err != nil {
|
||||||
if err := system.WriteFile(f); err != nil {
|
|
||||||
t.Fatalf("Error writing update config: %v", err)
|
t.Fatalf("Error writing update config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
@ -28,21 +29,23 @@ func PersistScriptInWorkspace(script system.Script, workspace string) (string, e
|
|||||||
}
|
}
|
||||||
tmp.Close()
|
tmp.Close()
|
||||||
|
|
||||||
|
relpath := strings.TrimPrefix(tmp.Name(), workspace)
|
||||||
|
|
||||||
file := system.File{
|
file := system.File{
|
||||||
Path: tmp.Name(),
|
Path: relpath,
|
||||||
RawFilePermissions: "0744",
|
RawFilePermissions: "0744",
|
||||||
Content: string(script),
|
Content: string(script),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = system.WriteFile(&file)
|
return system.WriteFile(&file, workspace)
|
||||||
return file.Path, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PersistUnitNameInWorkspace(name string, workspace string) error {
|
func PersistUnitNameInWorkspace(name string, workspace string) error {
|
||||||
file := system.File{
|
file := system.File{
|
||||||
Path: path.Join(workspace, "scripts", "unit-name"),
|
Path: path.Join("scripts", "unit-name"),
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
Content: name,
|
Content: name,
|
||||||
}
|
}
|
||||||
return system.WriteFile(&file)
|
_, err := system.WriteFile(&file, workspace)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -31,33 +31,55 @@ func (f *File) Permissions() (os.FileMode, error) {
|
|||||||
return os.FileMode(perm), nil
|
return os.FileMode(perm), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteFile(f *File) error {
|
func WriteFile(f *File, root string) (string, error) {
|
||||||
if f.Encoding != "" {
|
if f.Encoding != "" {
|
||||||
return fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
return "", fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(path.Dir(f.Path), os.FileMode(0755)); err != nil {
|
fullpath := path.Join(root, f.Path)
|
||||||
return err
|
dir := path.Dir(fullpath)
|
||||||
|
|
||||||
|
if err := EnsureDirectoryExists(dir); err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
perm, err := f.Permissions()
|
perm, err := f.Permissions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(f.Path, []byte(f.Content), perm); err != nil {
|
var tmp *os.File
|
||||||
return err
|
// Create a temporary file in the same directory to ensure it's on the same filesystem
|
||||||
|
if tmp, err = ioutil.TempFile(dir, "cloudinit-temp"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(tmp.Name(), []byte(f.Content), perm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmp.Close(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the permissions are as requested (since WriteFile can be affected by sticky bit)
|
||||||
|
if err := os.Chmod(tmp.Name(), perm); err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Owner != "" {
|
if f.Owner != "" {
|
||||||
// We shell out since we don't have a way to look up unix groups natively
|
// We shell out since we don't have a way to look up unix groups natively
|
||||||
cmd := exec.Command("chown", f.Owner, f.Path)
|
cmd := exec.Command("chown", f.Owner, tmp.Name())
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if err := os.Rename(tmp.Name(), fullpath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullpath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnsureDirectoryExists(dir string) error {
|
func EnsureDirectoryExists(dir string) error {
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,18 +12,22 @@ func TestWriteFileUnencodedContent(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer syscall.Rmdir(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
fullPath := path.Join(dir, "tmp", "foo")
|
fn := "foo"
|
||||||
|
fullPath := path.Join(dir, fn)
|
||||||
|
|
||||||
wf := File{
|
wf := File{
|
||||||
Path: fullPath,
|
Path: fn,
|
||||||
Content: "bar",
|
Content: "bar",
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := WriteFile(&wf); err != nil {
|
path, err := WriteFile(&wf, dir)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Processing of WriteFile failed: %v", err)
|
t.Fatalf("Processing of WriteFile failed: %v", err)
|
||||||
|
} else if path != fullPath {
|
||||||
|
t.Fatalf("WriteFile returned bad path: want %s, got %s", fullPath, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(fullPath)
|
fi, err := os.Stat(fullPath)
|
||||||
@ -51,15 +54,15 @@ func TestWriteFileInvalidPermission(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer syscall.Rmdir(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
wf := File{
|
wf := File{
|
||||||
Path: path.Join(dir, "tmp", "foo"),
|
Path: path.Join(dir, "tmp", "foo"),
|
||||||
Content: "bar",
|
Content: "bar",
|
||||||
RawFilePermissions: "pants",
|
RawFilePermissions: "pants",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := WriteFile(&wf); err == nil {
|
if _, err := WriteFile(&wf, dir); err == nil {
|
||||||
t.Fatalf("Expected error to be raised when writing file with invalid permission")
|
t.Fatalf("Expected error to be raised when writing file with invalid permission")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,17 +72,21 @@ func TestWriteFilePermissions(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer syscall.Rmdir(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
fullPath := path.Join(dir, "tmp", "foo")
|
fn := "foo"
|
||||||
|
fullPath := path.Join(dir, fn)
|
||||||
|
|
||||||
wf := File{
|
wf := File{
|
||||||
Path: fullPath,
|
Path: fn,
|
||||||
RawFilePermissions: "0755",
|
RawFilePermissions: "0755",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := WriteFile(&wf); err != nil {
|
path, err := WriteFile(&wf, dir)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Processing of WriteFile failed: %v", err)
|
t.Fatalf("Processing of WriteFile failed: %v", err)
|
||||||
|
} else if path != fullPath {
|
||||||
|
t.Fatalf("WriteFile returned bad path: want %s, got %s", fullPath, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(fullPath)
|
fi, err := os.Stat(fullPath)
|
||||||
@ -97,15 +104,15 @@ func TestWriteFileEncodedContent(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer syscall.Rmdir(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
wf := File{
|
wf := File{
|
||||||
Path: path.Join(dir, "tmp", "foo"),
|
Path: path.Join(dir, "tmp", "foo"),
|
||||||
Content: "",
|
Content: "",
|
||||||
Encoding: "base64",
|
Encoding: "base64",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := WriteFile(&wf); err == nil {
|
if _, err := WriteFile(&wf, dir); err == nil {
|
||||||
t.Fatalf("Expected error to be raised when writing file with encoding")
|
t.Fatalf("Expected error to be raised when writing file with encoding")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,12 +78,12 @@ func PlaceUnit(u *Unit, dst string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file := File{
|
file := File{
|
||||||
Path: dst,
|
Path: filepath.Base(dst),
|
||||||
Content: u.Content,
|
Content: u.Content,
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := WriteFile(&file)
|
_, err := WriteFile(&file, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user