diff --git a/modules.go b/modules.go index c0bb149..ee778d7 100644 --- a/modules.go +++ b/modules.go @@ -1401,8 +1401,19 @@ func (e *Executor) moduleGetURL(ctx context.Context, client sshExecutorClient, a return nil, coreerr.E("Executor.moduleGetURL", "url and dest required", nil) } + force := getBoolArg(args, "force", true) checksumSpec := corexTrimSpace(getStringArg(args, "checksum", "")) + if !force { + exists, err := client.FileExists(ctx, dest) + if err != nil { + return nil, err + } + if exists { + return &TaskResult{Changed: false, Msg: sprintf("skipped existing destination: %s", dest)}, nil + } + } + // Stream to stdout so we can validate checksums before writing the file. cmd := sprintf("curl -fsSL %q || wget -q -O - %q", url, url) stdout, stderr, rc, err := client.Run(ctx, cmd) diff --git a/modules_file_test.go b/modules_file_test.go index 741e652..b9dfaf2 100644 --- a/modules_file_test.go +++ b/modules_file_test.go @@ -1675,6 +1675,23 @@ func TestModulesFile_ModuleGetURL_Good_ChecksumFileURL(t *testing.T) { assert.Equal(t, []byte(payload), up.Content) } +func TestModulesFile_ModuleGetURL_Good_ForceFalseSkipsExistingDestination(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + mock.addFile("/tmp/app.tgz", []byte("existing artifact")) + + result, err := e.moduleGetURL(context.Background(), mock, map[string]any{ + "url": "https://downloads.example.com/app.tgz", + "dest": "/tmp/app.tgz", + "force": false, + }) + + require.NoError(t, err) + assert.False(t, result.Changed) + assert.Equal(t, "skipped existing destination: /tmp/app.tgz", result.Msg) + assert.Equal(t, 0, mock.commandCount()) + assert.Equal(t, 0, mock.uploadCount()) +} + func TestModulesFile_ModuleGetURL_Bad_ChecksumMismatch(t *testing.T) { e, mock := newTestExecutorWithMock("host1") mock.expectCommand(`curl.*https://downloads\.example\.com/app\.tgz`, "downloaded artifact", "", 0)