engine: resources: Fix missing file when specified without content

If the file res was defined with state => "exists" but no content
specified, it was not created. This patch fixes this bug and adds a test
and an example.
This commit is contained in:
James Shubin
2019-03-05 11:51:04 -05:00
parent 11fc55d679
commit 426b15313e
3 changed files with 107 additions and 2 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}