diff --git a/engine/resources/file.go b/engine/resources/file.go index 97782f9e..b80dd7e6 100644 --- a/engine/resources/file.go +++ b/engine/resources/file.go @@ -569,6 +569,52 @@ func (obj *FileRes) syncCheckApply(apply bool, src, dst string) (bool, error) { return checkOK, nil } +// state performs a CheckApply of the file state to create an empty file. +func (obj *FileRes) stateCheckApply(apply bool) (bool, error) { + if obj.State == "" { // state is not specified + return true, nil + } + + _, err := os.Stat(obj.path) + + if err != nil && !os.IsNotExist(err) { + return false, errwrap.Wrapf(err, "could not stat file") + } + + if obj.State == "absent" && os.IsNotExist(err) { + return true, nil + } + + if obj.State == "exists" && err == nil { + return true, nil + } + + // state is not okay, no work done, exit, but without error + if !apply { + return false, nil + } + + if obj.State == "absent" { + return false, nil // defer the work to contentCheckApply + } + + if obj.Content == nil && !obj.isDir { + // Create an empty file to ensure one exists. Don't O_TRUNC it, + // in case one is magically created right after our exists test. + // The chmod used is what is used by the os.Create function. + // TODO: is using O_EXCL okay? + f, err := os.OpenFile(obj.path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + return false, errwrap.Wrapf(err, "problem creating empty file") + } + if err := f.Close(); err != nil { + return false, errwrap.Wrapf(err, "problem closing empty file") + } + } + + return false, nil // defer the Content != nil and isDir work to later... +} + // contentCheckApply performs a CheckApply for the file existence and content. func (obj *FileRes) contentCheckApply(apply bool) (checkOK bool, _ error) { obj.init.Logf("contentCheckApply(%t)", apply) @@ -755,6 +801,12 @@ func (obj *FileRes) CheckApply(apply bool) (checkOK bool, _ error) { checkOK = true + // always run stateCheckApply before contentCheckApply, they go together + if c, err := obj.stateCheckApply(apply); err != nil { + return false, err + } else if !c { + checkOK = false + } if c, err := obj.contentCheckApply(apply); err != nil { return false, err } else if !c { diff --git a/engine/resources/resources_test.go b/engine/resources/resources_test.go index c3c8d849..53f8dabf 100644 --- a/engine/resources/resources_test.go +++ b/engine/resources/resources_test.go @@ -268,6 +268,54 @@ func TestResources1(t *testing.T) { cleanup: func() error { return os.Remove(f) }, }) } + { + r := makeRes("file", "r1") + res := r.(*FileRes) // if this panics, the test will panic + p := "/tmp/emptyfile" + res.Path = p + res.State = "exists" + + timeline := []Step{ + NewStartupStep(1000 * 60), // startup + NewChangedStep(1000*60, false), // did we do something? + fileExpect(p, ""), // check initial state + NewClearChangedStep(1000 * 15), // did we do something? + } + + testCases = append(testCases, test{ + name: "touch file", + res: res, + fail: false, + timeline: timeline, + expect: func() error { return nil }, + startup: func() error { return nil }, + cleanup: func() error { return os.Remove(p) }, + }) + } + { + r := makeRes("file", "r1") + res := r.(*FileRes) // if this panics, the test will panic + p := "/tmp/existingfile" + res.Path = p + res.State = "exists" + content := "some existing text\n" + + timeline := []Step{ + NewStartupStep(1000 * 60), // startup + NewChangedStep(1000*60, true), // did we do something? + fileExpect(p, content), // check initial state + } + + testCases = append(testCases, test{ + name: "existing file", + res: res, + fail: false, + timeline: timeline, + expect: func() error { return nil }, + startup: func() error { return ioutil.WriteFile(p, []byte(content), 0666) }, + cleanup: func() error { return os.Remove(p) }, + }) + } names := []string{} for index, tc := range testCases { // run all the tests diff --git a/examples/lang/exec0.mcl b/examples/lang/exec0.mcl index a184c93a..3c3d0023 100644 --- a/examples/lang/exec0.mcl +++ b/examples/lang/exec0.mcl @@ -4,11 +4,16 @@ exec "exec1" { args => ["-c", "print(\"i'm in python!\")",], } +file "/tmp/whatever" { + state => "exists", +} + exec "exec2" { cmd => "echo hello world > /tmp/whatever", shell => "/bin/bash", ifcmd => "! diff <(cat /tmp/whatever) <(echo hello world)", ifshell => "/bin/bash", - watchcmd => "touch /tmp/whatever && /usr/bin/inotifywait -e modify -m /tmp/whatever", - watchshell => "/bin/bash", + watchcmd => "/usr/bin/inotifywait -e modify -m /tmp/whatever", + + Depend => File["/tmp/whatever"], # so that inotifywait can startup }