From 2b12b8f860c01dee567d703e9e4ca1d1d2738c10 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 02:47:08 +0000 Subject: [PATCH] feat(ansible): support blockinfile newline padding Co-authored-by: Virgil --- mock_ssh_test.go | 13 ++++++++++--- modules.go | 13 ++++++++++--- modules_file_test.go | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/mock_ssh_test.go b/mock_ssh_test.go index b7d3f2f..9c6c694 100644 --- a/mock_ssh_test.go +++ b/mock_ssh_test.go @@ -931,6 +931,8 @@ func moduleBlockinfileWithClient(_ *Executor, client sshFileRunner, args map[str marker := getStringArg(args, "marker", "# {mark} ANSIBLE MANAGED BLOCK") state := getStringArg(args, "state", "present") create := getBoolArg(args, "create", false) + prependNewline := getBoolArg(args, "prepend_newline", false) + appendNewline := getBoolArg(args, "append_newline", false) beginMarker := replaceN(marker, "{mark}", "BEGIN", 1) endMarker := replaceN(marker, "{mark}", "END", 1) @@ -952,16 +954,21 @@ func moduleBlockinfileWithClient(_ *Executor, client sshFileRunner, args map[str // Remove existing block and add new one escapedBlock := replaceAll(block, "'", "'\\''") + blockContent := beginMarker + "\n" + escapedBlock + "\n" + endMarker + if prependNewline { + blockContent = "\n" + blockContent + } + if appendNewline { + blockContent += "\n" + } cmd := sprintf(` sed -i '/%s/,/%s/d' %q 2>/dev/null || true cat >> %q << 'BLOCK_EOF' %s -%s -%s BLOCK_EOF `, replaceAll(beginMarker, "/", "\\/"), replaceAll(endMarker, "/", "\\/"), - path, path, beginMarker, escapedBlock, endMarker) + path, path, blockContent) stdout, stderr, rc, err := client.RunScript(context.Background(), cmd) if err != nil || rc != 0 { diff --git a/modules.go b/modules.go index 573a9e1..a1e3a61 100644 --- a/modules.go +++ b/modules.go @@ -2371,6 +2371,8 @@ func (e *Executor) moduleBlockinfile(ctx context.Context, client sshExecutorClie marker := getStringArg(args, "marker", "# {mark} ANSIBLE MANAGED BLOCK") state := getStringArg(args, "state", "present") create := getBoolArg(args, "create", false) + prependNewline := getBoolArg(args, "prepend_newline", false) + appendNewline := getBoolArg(args, "append_newline", false) beginMarker := replaceN(marker, "{mark}", "BEGIN", 1) endMarker := replaceN(marker, "{mark}", "END", 1) @@ -2392,16 +2394,21 @@ func (e *Executor) moduleBlockinfile(ctx context.Context, client sshExecutorClie // Remove existing block and add new one escapedBlock := replaceAll(block, "'", "'\\''") + blockContent := beginMarker + "\n" + escapedBlock + "\n" + endMarker + if prependNewline { + blockContent = "\n" + blockContent + } + if appendNewline { + blockContent += "\n" + } cmd := sprintf(` sed -i '/%s/,/%s/d' %q 2>/dev/null || true cat >> %q << 'BLOCK_EOF' %s -%s -%s BLOCK_EOF `, replaceAll(beginMarker, "/", "\\/"), replaceAll(endMarker, "/", "\\/"), - path, path, beginMarker, escapedBlock, endMarker) + path, path, blockContent) stdout, stderr, rc, err := client.RunScript(ctx, cmd) if err != nil || rc != 0 { diff --git a/modules_file_test.go b/modules_file_test.go index 256830f..4ce4ded 100644 --- a/modules_file_test.go +++ b/modules_file_test.go @@ -657,6 +657,23 @@ func TestModulesFile_ModuleBlockinfile_Good_CustomMarkers(t *testing.T) { assert.True(t, mock.hasExecutedMethod("RunScript", "# END managed by devops")) } +func TestModulesFile_ModuleBlockinfile_Good_NewlinePadding(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + + result, err := moduleBlockinfileWithClient(e, mock, map[string]any{ + "path": "/etc/hosts", + "block": "10.0.0.5 db01", + "prepend_newline": true, + "append_newline": true, + }) + + require.NoError(t, err) + assert.True(t, result.Changed) + + cmd := mock.lastCommand().Cmd + assert.Contains(t, cmd, "\n\n# BEGIN ANSIBLE MANAGED BLOCK\n10.0.0.5 db01\n# END ANSIBLE MANAGED BLOCK\n") +} + func TestModulesFile_ModuleBlockinfile_Good_RemoveBlock(t *testing.T) { e, mock := newTestExecutorWithMock("host1")