diff --git a/modules.go b/modules.go index 54711ea..d64ee22 100644 --- a/modules.go +++ b/modules.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "regexp" "sort" "strconv" "time" @@ -1447,10 +1448,19 @@ func (e *Executor) moduleWaitFor(ctx context.Context, client sshExecutorClient, path := getStringArg(args, "path", "") host := getStringArg(args, "host", "127.0.0.1") state := getStringArg(args, "state", "started") + searchRegex := getStringArg(args, "search_regex", "") timeout := 300 if t, ok := args["timeout"].(int); ok { timeout = t } + var compiledRegex *regexp.Regexp + if searchRegex != "" { + var err error + compiledRegex, err = regexp.Compile(searchRegex) + if err != nil { + return nil, coreerr.E("Executor.moduleWaitFor", "compile search_regex", err) + } + } if path != "" { deadline := time.NewTimer(time.Duration(timeout) * time.Second) @@ -1464,9 +1474,26 @@ func (e *Executor) moduleWaitFor(ctx context.Context, client sshExecutorClient, return &TaskResult{Failed: true, Msg: err.Error()}, nil } - satisfied := exists - if state == "absent" { + satisfied := false + switch state { + case "absent": satisfied = !exists + if exists && compiledRegex != nil { + data, err := client.Download(ctx, path) + if err == nil { + satisfied = !compiledRegex.Match(data) + } + } + default: + satisfied = exists + if satisfied && compiledRegex != nil { + data, err := client.Download(ctx, path) + if err != nil { + satisfied = false + } else { + satisfied = compiledRegex.Match(data) + } + } } if satisfied { return &TaskResult{Changed: false}, nil diff --git a/modules_adv_test.go b/modules_adv_test.go index daaee3a..4dc085f 100644 --- a/modules_adv_test.go +++ b/modules_adv_test.go @@ -763,6 +763,32 @@ func TestModulesAdv_ModuleWaitFor_Good_WaitsForPathAbsent(t *testing.T) { assert.GreaterOrEqual(t, elapsed, 150*time.Millisecond) } +func TestModulesAdv_ModuleWaitFor_Good_WaitsForPathRegexMatch(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + mock.addFile("/tmp/config", []byte("ready=false\n")) + + go func() { + time.Sleep(150 * time.Millisecond) + mock.mu.Lock() + mock.files["/tmp/config"] = []byte("ready=true\n") + mock.mu.Unlock() + }() + + start := time.Now() + result, err := e.moduleWaitFor(context.Background(), mock, map[string]any{ + "path": "/tmp/config", + "search_regex": "ready=true", + "timeout": 2, + }) + elapsed := time.Since(start) + + require.NoError(t, err) + assert.NotNil(t, result) + assert.False(t, result.Failed) + assert.False(t, result.Changed) + assert.GreaterOrEqual(t, elapsed, 150*time.Millisecond) +} + func TestModulesAdv_ModuleWaitFor_Good_WaitsForPortAbsent(t *testing.T) { e, mock := newTestExecutorWithMock("host1") mock.expectCommand(`timeout 2 bash -c 'until ! nc -z 127.0.0.1 8080; do sleep 1; done'`, "", "", 0)